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? } }
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. }
假设以下任务
SomeType.someStaticMethod(params).someInstanceMethod(moreParams).someField = [another expression with arbitrary complexity]
如何从Assigment节点访问someField?
而且,MethodInvocation的哪个属性为我提供了用于调用方法的实例?
编辑1:鉴于我收到的答案,我的问题显然不清楚.我不想解决这个特殊的表达方式.我希望能够在给定任何赋值的情况下找出它所分配的名称,以及分配给第一个名称的名称(如果不是rvalue).
因此,例如,方法调用的参数可以是字段访问或先前声明的局部变量.
SomeType.someStaticMethod(instance.field).someInstanceMethod(type.staticField,localVariable,localField).Field.destinationField
所以,这是一个充满希望的客观问题:给定左侧和右侧具有任意复杂性的任何赋值语句,如何获得分配给的最终字段/变量,以及分配的最终(如果有)字段/变量它.
编辑2:更具体地说,我想要实现的是不变性,通过注释@Const:
/** * 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 { }
要做到这一点,我要做的第一件事就是将作业的左侧标记为@Const(足够简单).我还必须检测何时和表达式的右侧是标记为@Const的字段,在这种情况下,它只能在相同类型的@Const变量的定义中分配.
问题是我很难找到表达式右侧的终极字段,以避免使字段混叠并使@Const注释无效.
解决方法
让我们改写你想要做的事情:
>您想要识别以识别每个任务(即leftSide = rightSide)
>对于每个赋值,您要确定左侧的性质(即,它是局部变量还是字段访问),如果它确实是字段访问,则您要构建对应的“路径”到该字段(即源对象,后跟一系列方法调用或字段访问,以字段访问结束).
>对于每个作业,您要确定与右侧对应的类似“路径”.
我认为你已经解决了第1点:你只需创建一个扩展org.eclipse.jdt.core.dom.ASTVisitor的类;在那里,你重写#visit(Assignment)方法.最后,在适当的地方,您实例化您的访问者类,并让它访问AST树,从哪个节点开始匹配您的需求(很可能是CompilationUnit,TypeDeclaration或MethodDeclaration的实例).
那又怎样? #visit(Assignment)方法确实接收Assignment节点.直接在该对象上,您可以获得左侧和右侧表达式(assignment.getLeftHandSide()和assignment.getRightHandSide()).正如你所提到的,两个都是表达式,它们可能变得相当复杂,那么我们如何从这些子树中提取干净,线性的“路径”呢?访问者肯定是这样做的最好方式,但这里有捕获,应该使用不同的访问者来完成,而不是让你的第一个访问者(一个捕获的分配)继续下降任何一方的表达.技术上可以使用单个访问者完成所有操作,但这将涉及访问者内部的重要状态管理.无论如何,我非常相信这种管理的复杂程度如此之高,以至于这种实施实际上效率会低于不同的访问者.
所以我们可以像这样形象:
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; } }
请注意,此代码中MyAssignmentListVisitor.visit(Assignment)返回true,表示应递归检查赋值的子项.这听起来可能没必要,Java语言确实支持几种结构,其中赋值可能包含其他赋值;例如考虑以下极端情况:
(varA = someObject).someField = varB = (varC = new SomeClass(varD = "string").someField);
出于同样的原因,在表达式的线性化期间仅访问赋值的右侧,因为赋值的“结果值”是其右侧.在这种情况下,左手边只是一个可以安全忽略的副作用.
鉴于我不知道您的特定情况所需的信息的性质,我将不再进一步描述路径实际建模的原型.您可能更适合为左侧表达式和右侧表达式分别创建不同的访问者类,例如为了更好地处理右侧可能实际涉及多个变量/字段/方法调用的事实通过二元运算符.这将是你的决定.
关于要讨论的AST树的访问者遍历仍然存在一些主要问题,即通过依赖于默认节点遍历顺序,您失去了获取每个节点之间关系的信息的机会.例如,给定表达式this.someMethod(this.fieldA).fieldB,您将看到类似于以下序列的内容:
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")
根本无法从这一系列事件中推断出线性化表达式.您将希望明确拦截每个节点,并仅在适当的情况下以适当的顺序显式递归节点的子节点.例如,我们可以做到以下几点:
@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; }
在前面的示例中,这些方法将在路径中收集以下序列:this,someMethod(),fieldB.我认为,这非常接近你所寻找的.如果您想收集所有字段访问/方法调用序列(例如,您希望访问者返回this,fieldB和this,fieldA),那么您可以重写访问(MethodInvocation)方法大致类似于这个:
@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; }
最后,如果您想知道路径中每一步的值类型,则必须查看与每个节点关联的绑定对象,例如:methodInvocation.resolveMethodBinding().getDeclaringClass().但请注意,必须在构建AST树时明确请求绑定解析.
上面的代码将无法正确处理更多的语言结构;不过,我相信你应该能够自己解决这些遗留问题.如果您需要查看引用实现,请查看类org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteFlattener,它基本上从现有的AST树重构Java源代码;虽然这个特定的访问者比大多数其他ASTVisitors大得多,但它更容易理解.
对OP编辑#2的响应更新
这是您最近编辑后的更新起点.仍有许多情况需要处理,但这更符合您的具体问题.另请注意,尽管我使用了大量的instanceof检查(因为这对我来说比较容易,因为我在简单的文本编辑器中编写代码,并且没有在ASTNode常量上完成代码),您可以选择node.getNodeType()上的switch语句,通常效率更高.
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; } }
希望有所帮助.