2016-04-24 24 views
7

Sono nuovo ad AST (la mia prima volta che scrivo un plug-in). Le espressioni possono essere abbastanza complesse nella vita reale. Volevo sapere come risolvere il lato sinistro e destro di un asse, per esempio.Come raggiungere la fine di un'espressione AST

class Visitor extends ASTVisitor 
{ 
    @Override 
    public boolean visit(Assignment node) 
    { 
     //here, how do I get the final name to each each side of the assignment resolves? 
    } 
} 

Ho anche un altro dubbio, come ottengo l'istanza utilizzata per invocare un metodo?

public boolean visit(MethodInvocation node) 
{ 
    //how do I get to know the object used to invoke this method? 
    //like, for example, MyClass is a class, and it has a field called myField 
    //the type of myField has a method called myMethod. 
    //how do I find myField? or for that matter some myLocalVariable used in the same way. 
} 

supporre la seguente assegnazione

SomeType.someStaticMethod(params).someInstanceMethod(moreParams).someField = 
    [another expression with arbitrary complexity] 

Come faccio ad arrivare a someField dal nodo Assigment?

E inoltre, quale proprietà di un MethodInvocation mi fornisce l'istanza utilizzata per richiamare il metodo?

EDIT 1: La mia domanda era apparentemente poco chiara, data la risposta che ho ricevuto. Non voglio risolvere questa espressione particolare. Voglio essere in grado, dato qualsiasi incarico, di scoprire il nome a cui è assegnato, e il nome (se non un rvalore) che assegna al primo.

Così, ad esempio, i parametri delle invocazioni del metodo potrebbero essere gli accessi al campo o le variabili locali precedentemente dichiarate.

SomeType.someStaticMethod(instance.field).someInstanceMethod(type.staticField, localVariable, localField).Field.destinationField 

Quindi, ecco la domanda si spera obiettivo: Dato un istruzione di assegnamento sia con il lato sinistro e lato destro avendo complessità arbitraria, come ottenere il campo/variabile finale che viene assegnato, e l'ultimo (se qualsiasi) campo/variabile che lo assegna.

EDIT 2: per essere più precisi, quello che voglio realizzare è immutabilità, tramite un @Const della nota:

/** 
* When Applied to a method, ensures the method doesn't change in any 
* way the state of the object used to invoke it, i.e., all the fields 
* of the object must remain the same, and no field may be returned, 
* unless the field itself is marked as {@code @Const} or the field is 
* a primitive non-array type. A method annotated with {@code @Const} 
* can only invoke other {@code @Const} methods of its class, can only 
* use the class's fields to invoke {@code @Const} methods of the fields 
* classes and can only pass fields as parameters to methods that 
* annotate that formal parameter as {@code @Const}. 
* 
* When applied to a formal parameter, ensures the method will not 
* modify the value referenced by the formal parameter. A formal 
* parameter annotated as {@code @Const} will not be aliased inside the 
* body of the method. The method is not allowed to invoke another 
* method and pass the annotated parameter, save if the other method 
* also annotates the formal parameter as {@code @Const}. The method is 
* not allowed to use the parameter to invoke any of its type's methods, 
* unless the method being invoked is also annotated as {@code @Const} 
* 
* When applied to a field, ensures the field cannot be aliased and that 
* no code can alter the state of that field, either from inside the 
* class that owns the field or from outside it. Any constructor in any 
* derived class is allowed to set the value of the field and invoke any 
* methods using it. As for methods, only those annotated as 
* {@code @Const} may be invoked using the field. The field may only be 
* passed as a parameter to a method if the method annotates the 
* corresponding formal parameter as {@code @Const} 
* 
* When applied to a local variable, ensures neither the block where the 
* variable is declared or any nested block will alter the value of that 
* local variable. The local variable may be defined only once, at any 
* point where it is in scope and cannot be aliased. Only methods 
* annotated as {@code @Const} may be invoked using this variable, and 
* the variable may only be passed as a parameter to another method if 
* said method annotates its corresponding formal parameter as 
* {@code @Const} 
* 
*/ 
@Retention(RetentionPolicy.SOURCE) 
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, 
ElementType.LOCAL_VARIABLE}) 
@Inherited 
public @interface Const 
{ 

} 

Per raggiungere questo obiettivo, la prima cosa che devo fare è casi in cui la sinistra lato di un incarico è contrassegnato come @Const (abbastanza facile). Devo anche rilevare quando il lato destro di expression e è un campo contrassegnato come @Const, nel qual caso può essere assegnato solo alla definizione di una variabile @Const dello stesso tipo.

Il problema è che sto avendo davvero difficoltà a trovare il campo finale sul lato destro dell'espressione, per evitare l'aliasing del campo e il rendering dell'annotazione @Const inutile.

+0

Cosa biblioteca stai usando qui? (PMD o qualcosa del genere?) – JavaHopper

+0

eclipse jdt per lo sviluppo di plugin. – FinnTheHuman

+0

ok, quindi vuoi raggiungere la foglia dell'AST formata? – JavaHopper

risposta

1

I visitatori sono uno strumento davvero eccezionale, ma la soluzione corretta per un problema specifico non è sempre quella di avere un singolo visitatore che attenda pazientemente che i metodi di visita vengano richiamati ... La domanda che stai ponendo è una domanda ampio di una tale situazione.

Diamo riformulare ciò che si sta cercando di fare:

  1. Si desidera identificare identificare ogni incarico (cioè leftSide = rightSide)

  2. per ogni assegnazione, si vuole determinare la natura del lato sinistro (ovvero, è una variabile locale o un accesso al campo), e se è effettivamente un accesso al campo, si vuole costruire un "percorso" corrispondente a quel campo (cioè l'oggetto sorgente, seguito da un serie di entrambi i metodi call o field access e termina con un accesso al campo).

  3. Per ogni assegnazione, si desidera determinare un "percorso" simile corrispondente al lato destro.

Credo che avete già risolto Punto numero 1: è sufficiente creare una classe che estende org.eclipse.jdt.core.dom.ASTVisitor; lì, si ignora il metodo #visit(Assignment). Infine, dovunque sia appropriato, istanziate la classe del visitatore e fatevi visitare un albero AST, a partire dal quale nodo sempre corrispondente alle vostre esigenze (molto probabilmente un'istanza di CompilationUnit, TypeDeclaration o MethodDeclaration).

E allora? Il metodo #visit(Assignment) riceve infatti un nodo Assignment. Direttamente su quell'oggetto, è possibile ottenere sia le espressioni sul lato sinistro che sul lato destro (assignment.getLeftHandSide() e assignment.getRightHandSide()). Come hai detto, entrambi sono Expression s, il che può risultare piuttosto complicato, quindi come possiamo estrarre un "percorso" pulito e lineare da questi sottoalberi? Un visitatore è sicuramente il modo migliore per farlo, ma ecco il trucco, dovrebbe essere fatto usando distinti visitatori, piuttosto che avere il tuo primo visitatore (quello che cattura Assignment s) continua la sua discesa da entrambe le espressioni laterali. È tecnicamente possibile fare tutto utilizzando un singolo visitatore, ma ciò implicherebbe una significativa gestione dello stato all'interno del visitatore. Sono abbastanza o meno abbastanza convinto che la complessità di tale gestione sarebbe così alta che tale implementazione sarebbe in realtà meno efficiente che i diversi visitatori si avvicinano.

così potevamo immagine qualcosa di simile:

class MyAssignmentListVisitor extends ASTVisitor { 
    @Override 
    public boolean visit(Assignment assignment) { 
     FieldAccessLineralizationVisitor leftHandSideVisitor = new FieldAccessLineralizationVisitor(); 
     assignment.getLeftHandSide().accept(leftHandSideVisitor); 
     LinearFieldAccess leftHandSidePath = leftHandSideVisitor.asLinearFieldAccess(); 

     FieldAccessLineralizationVisitor rightHandSideVisitor = new FieldAccessLineralizationVisitor(); 
     assignment.getRightHandSide().accept(rightHandSideVisitor); 
     LinearFieldAccess rightHandSidePath = rightHandSideVisitor.asLinearFieldAccess(); 

     processAssigment(leftHandSidePath, rightHandSidePath); 

     return true; 
    } 
} 

class FieldAccessLineralizationVisitor extends ASTVisitor { 

    List<?> significantFieldAccessParts = [...]; 

    // ... various visit method expecting concrete subtypes of Expression ... 

    @Override 
    public boolean visit(Assignment assignment) { 
     // Found an assignment inside an assignment; ignore its 
     // left hand side, as it does not affect the "path" for 
     // the assignment currently being investigated 

     assignment.getRightHandSide().accept(this); 

     return false; 
    } 
} 

Nota in questo codice che MyAssignmentListVisitor.visit(Assignment) rendimenti true, per indicare che i bambini della assegnazione devono essere ispezionati in modo ricorsivo. Ciò può sembrare inizialmente non necessario, il linguaggio Java supporta infatti diverse costruzioni in cui un compito può contenere altri compiti; si pensi ad esempio il seguente caso estremo:

(varA = someObject).someField = varB = (varC = new SomeClass(varD = "string").someField); 

Per lo stesso motivo, solo il lato destro di un'assegnazione viene visitato durante linearizzazione di un'espressione, dato che il "valore risultante" di un'assegnazione è la sua mano destra lato. Il lato sinistro è, in questa situazione, un semplice effetto collaterale che può essere tranquillamente ignorato.

Non andrò oltre nella prototipazione di come i percorsi sono effettivamente modellati, dato che non conosco la natura delle informazioni che sono necessarie per la tua situazione specifica. Potrebbe anche essere più appropriato per te creare distinte classi di visitatori rispettivamente per l'espressione sul lato sinistro e l'espressione sul lato destro, ad esempio per gestire meglio il fatto che il lato destro potrebbe effettivamente coinvolgere più variabili/campi/invocazioni del metodo combinate attraverso operatori binari. Questa sarà la tua decisione.

Ci sono ancora alcune importanti preoccupazioni riguardo al traversal del visitatore dell'albero AST da discutere, ovvero che, facendo affidamento sull'ordine di attraversamento del nodo predefinito, si perde l'opportunità di ottenere informazioni sulla relazione tra ciascun nodo. Ad esempio, dato espressione this.someMethod(this.fieldA).fieldB, vedrete qualcosa di simile alla seguente sequenza:

FieldAccess  => corresponding to the whole expression 
MethodInvovation => corresponding to this.someMethod(this.fieldA) 
ThisExpression 
SimpleName ("someMethod") 
FieldAccess  => corresponding to this.fieldA 
ThisExpression 
SimpleName ("fieldA") 
SimpleName ("fieldB") 

Non semplicemente senso si può effettivamente dedurre l'espressione linearizzata da questa sequenza di eventi. Si preferisce invece intercettare esplicitamente ogni nodo e ricorrere esplicitamente ai figli di un nodo solo se appropriato e in ordine appropriato. Ad esempio, abbiamo potuto fare il seguente:

@Override 
    public boolean visit(FieldAccess fieldAccess) { 
     // FieldAccess :: <expression>.<name> 

     // First descend on the "subject" of the field access 
     fieldAccess.getExpression().accept(this); 

     // Then append the name of the accessed field itself 
     this.path.append(fieldAccess.getName().getIdentifier()); 

     return false; 
    } 

    @Override 
    public boolean visit(MethodInvocation methodInvocation) { 
     // MethodInvocation :: <expression>.<methodName><<typeArguments>>(arguments) 

     // First descend on the "subject" of the method invocation 
     methodInvocation.getExpression().accept(this); 

     // Then append the name of the accessed field itself 
     this.path.append(methodAccess.getName().getIdentifier() + "()"); 

     return false; 
    } 

    @Override 
    public boolean visit(ThisExpression thisExpression) { 
     // ThisExpression :: [<qualifier>.] this 

     // I will ignore the qualifier part for now, it will be up 
     // to you to determine if it is pertinent 
     this.path.append("this"); 

     return false; 
    } 

Questi metodi avrebbero, dato l'esempio precedente, raccogliere in path seguente sequenza: this, someMethod(), fieldB. Questo è, credo, abbastanza vicino a quello che stai cercando. Se si vuole raccogliere tutte le sequenze di accesso campo/chiamate di metodi (ad esempio, si desidera che il visitatore a tornare sia this,someMethod(),fieldB e this,fieldA), allora si potrebbe riscrivere il metodo visit(MethodInvocation) grosso modo simile a questo:

@Override 
    public boolean visit(MethodInvocation methodInvocation) { 
     // MethodInvocation :: <expression>.<methodName><<typeArguments>>(arguments) 

     // First descend on the "subject" of the method invocation 
     methodInvocation.getExpression().accept(this); 

     // Then append the name of the accessed field itself 
     this.path.append(methodAccess.getName().getIdentifier() + "()"); 

     // Now deal with method arguments, each within its own, distinct access chain 
     for (Expression arg : methodInvocation.getArguments()) { 
      LinearPath orginalPath = this.path; 
      this.path = new LinearPath(); 

      arg.accept(this); 

      this.collectedPaths.append(this.path); 
      this.path = originalPath; 
     } 

     return false; 
    } 

Infine, Se si è interessati a conoscere il tipo di valori in ogni fase del percorso, sarà necessario esaminare gli oggetti di associazione associati a ciascun nodo, ad esempio: methodInvocation.resolveMethodBinding().getDeclaringClass(). Si noti tuttavia che la risoluzione di binding deve essere stata esplicitamente richiesta alla costruzione dell'albero AST.

Esistono molti altri costrutti di linguaggio che non verranno gestiti correttamente dal codice precedente; comunque, credo che dovresti essere in grado di risolvere da solo questi problemi rimanenti. Se avete bisogno di un'implementazione di riferimento da esaminare, date un'occhiata alla classe org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteFlattener, che ricostruisce fondamentalmente il codice sorgente Java da un albero AST esistente; sebbene questo visitatore specifico sia molto più grande della maggior parte degli altri ASTVisitor s, è in qualche modo molto più facile da capire.

AGGIORNAMENTO IN RISPOSTA A OP'S EDIT # 2

Ecco un punto di aggiornamento di partenza seguendo il più recente modifica. Ci sono ancora molti casi da gestire, ma questo è più in linea con il tuo problema specifico. Nota anche che ho usato numerosi controlli instanceof (perché per me è più facile al momento, dato che sto scrivendo il codice in un semplice editor di testo, e non ho il completamento del codice sulle costanti ASTNode), puoi optare invece per una dichiarazione switch su node.getNodeType(), che di solito sarà più efficiente.

class ConstCheckVisitor extends ASTVisitor { 

    @Override 
    public boolean visit(MethodInvocation methodInvocation) {  
     if (isConst(methodInvocation.getExpression())) { 
      if (isConst(methodInvocation.resolveMethodBinding().getMethodDeclaration())) 
       reportInvokingNonConstMethodOnConstSubject(methodInvocation); 
     } 

     return true; 
    } 

    @Override 
    public boolean visit(Assignment assignment) { 
     if (isConst(assignment.getLeftHandSide())) { 
      if (/* assignment to @Const value is not acceptable in the current situation */) 
       reportAssignmentToConst(assignment.getLeftHandSide()); 

      // FIXME: I assume here that aliasing a @Const value to 
      //  another @Const value is acceptable. Is that right? 

     } else if (isImplicitelyConst(assigment.getLeftHandSide())) { 
      reportAssignmentToImplicitConst(assignment.getLeftHandSide());   

     } else if (isConst(assignment.getRightHandSide())) { 
      reportAliasing(assignment.getRightHandSide()); 
     } 

     return true; 
    } 

    private boolean isConst(Expression expression) { 
     if (expression instanceof FieldAccess) 
      return (isConst(((FieldAccess) expression).resolveFieldBinding())); 

     if (expression instanceof SuperFieldAccess) 
      return isConst(((SuperFieldAccess) expression).resolveFieldBinding()); 

     if (expression instanceof Name) 
      return isConst(((Name) expression).resolveBinding()); 

     if (expression instanceof ArrayAccess) 
      return isConst(((ArrayAccess) expression).getArray()); 

     if (expression instanceof Assignment) 
      return isConst(((Assignment) expression).getRightHandSide()); 

     return false; 
    } 

    private boolean isImplicitConst(Expression expression) { 
     // Check if field is actually accessed through a @Const chain 
     if (expression instanceof FieldAccess) 
      return isConst((FieldAccess expression).getExpression()) || 
        isimplicitConst((FieldAccess expression).getExpression()); 

     // FIXME: Not sure about the effect of MethodInvocation, assuming 
     //  that its subject is const or implicitly const 

     return false; 
    } 

    private boolean isConst(IBinding binding) { 
     if ((binding instanceof IVariableBinding) || (binding instanceof IMethodBinding)) 
      return containsConstAnnotation(binding.getAnnotations()); 

     return false; 
    } 
} 

Spero che questo aiuti.

+0

Tutta la classe dipende dal fatto che io abbia un'istanza RewriteEventStore. Non ho idea di come ottenerne uno e sospetto che questa non sia la strada da percorrere. Aggiornerò la mia domanda con maggiori dettagli – FinnTheHuman

+0

Non ci dovrebbe essere un tale requisito ... In che modo stai iniettando il tuo codice in JDT? – jwatkins

+0

@FinnTheHuman Grazie per il tuo aggiornamento, questo aiuta molto. L'aggiunta di avvertimenti o errori di compilazione extra in JDT viene essenzialmente eseguita implementando e registrando un CompilationParticipant. È possibile ottenere un esempio di punto di partenza per farlo qui: http://stackoverflow.com/questions/24195142/custom-compiler-errors-through-compilation-participants. In 'reconcile()', puoi ottenere l'AST del file corrente usando 'reconcileContext.getAST8()'; quindi lo navighi attraverso un visitatore. Usa 'reconcileContext.putProblems()' per aggiungere marcatori di problemi che appariranno nell'editor. – jwatkins

1

È inoltre possibile visitare node.getLeftHandSide(). Penso che un buon esempio può essere trovato nel codice Sharpen (Java2C# traduzione):

https://github.com/mono/sharpen/blob/master/src/main/sharpen/core/CSharpBuilder.java#L2848

Un semplice progetto di esempio qui: https://github.com/revaultch/jdt-sample

Junit test: https://github.com/revaultch/jdt-sample/blob/master/src/test/java/ch/revault/jdt/test/VisitorTest.java

+0

se lo faccio non otterrei l'intera espressione? Voglio la foglia finale dell'albero secondario. – FinnTheHuman

+0

Se visiti la LHS più in basso troverai eventualmente un 'visita (FieldAccess)' o una 'visita (QualifiedName)' –

+0

e come posso farlo? non c'è visitatore per Expression. Ok, ma anche se riesco a visitare un accesso al campo, come faccio a sapere che è il nodo foglia dell'espressione? – FinnTheHuman

2

Prima citando una risposta che ho postato:

Si dovrà lavorare con bindings. Per avere collegamenti disponibili, ciò significa che resolveBinding() non restituisce null, possibly additional steps I post sono necessari.

Il seguente visitatore dovrebbe aiutare a fare quello che vuoi:

class AssignmentVisitor extends ASTVisitor { 

    public boolean visit(Assignment node) { 
     ensureConstAnnotationNotViolated(node); 
     return super.visit(node); 
    } 

    private void ensureConstAnnotationNotViolated(Assignment node) { 
     Expression leftHandSide = node.getLeftHandSide(); 
     if (leftHandSide.getNodeType() == ASTNode.FIELD_ACCESS) { 
      FieldAccess fieldAccess = (FieldAccess) leftHandSide; 
      // access field IVariableBinding 
      fieldAccess.resolveFieldBinding(); 
      // access IAnnotationBindings e.g. your @const 
      fieldAccess.resolveFieldBinding().getAnnotations(); 
      // access field ITypeBinding 
      fieldAccess.getExpression().resolveTypeBinding(); 
     } else { 
      // TODO: check possible other cases 
     } 

    } 
}