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:
Si desidera identificare identificare ogni incarico (cioè leftSide = rightSide
)
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).
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.
Cosa biblioteca stai usando qui? (PMD o qualcosa del genere?) – JavaHopper
eclipse jdt per lo sviluppo di plugin. – FinnTheHuman
ok, quindi vuoi raggiungere la foglia dell'AST formata? – JavaHopper