我是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?
}
}
我还有另一个疑问,我如何获取用于调用方法的实例?
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]
如何从someField
节点访问Assigment
?
而且,MethodInvocation
的哪些属性为我提供了用于调用方法的实例?
编辑1:根据我收到的答案,我的问题显然不清楚。我不想解决这个特定的表达方式。我希望能够,在给定任何作业的情况下,找出它被分配到的名称,以及分配给第一个名称的名称(如果不是右值)。
因此,例如,方法调用的参数可以是字段访问或先前声明的局部变量。
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注释无效。
答案 0 :(得分:2)
首先引用我发布的答案:
您必须使用
bindings
。要使绑定可用,这意味着resolveBinding()
不返回null
,我发布的possibly additional steps是必要的。
以下访客应该帮助您做您想做的事:
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
}
}
}
答案 1 :(得分:1)
您可以进一步访问node.getLeftHandSide()
。
我认为在Sharpen(Java2C#translation)代码中可以找到一个很好的例子:
https://github.com/mono/sharpen/blob/master/src/main/sharpen/core/CSharpBuilder.java#L2848
这里有一个简单的示例项目: https://github.com/revaultch/jdt-sample
Junit-Test here: https://github.com/revaultch/jdt-sample/blob/master/src/test/java/ch/revault/jdt/test/VisitorTest.java
答案 2 :(得分:1)
访问者是一个非常好的工具,但是针对特定问题的正确解决方案并不总是让一位访问者耐心等待,直到调用其访问方法...您要问的问题就是这种情况的一个例子。
让我们改写你想要做的事情:
您希望识别以识别每项作业(即leftSide = rightSide
)
对于每个作业,您要确定左侧的性质(即,它是本地变量还是字段访问),如果它确实是字段访问,则您要构建a"路径"对应于该字段(即源对象,后跟一系列方法调用或字段访问,并以字段访问结束)。
对于每个作业,您要确定类似的"路径"对应右手边。
我认为您已经解决了第1点:您只需创建一个扩展org.eclipse.jdt.core.dom.ASTVisitor
的类;在那里,您覆盖#visit(Assignment)
方法。最后,在适当的地方,您实例化您的访问者类,并让它访问AST树,从哪个节点开始,以满足您的需求(很可能是CompilationUnit
,TypeDeclaration
的实例或MethodDeclaration
)。
#visit(Assignment)
方法确实收到Assignment
个节点。直接在该对象上,您可以获得左侧和右侧表达式(assignment.getLeftHandSide()
和assignment.getRightHandSide()
)。正如你所提到的,两者都是Expression
,这可能会变得相当复杂,所以我们如何才能提取一条干净,线性的路径"出于这些子树?访问者肯定是这样做的最佳方式,但是这里有一个问题,应该使用不同的访问者来完成,而不是让您的第一个访问者(一个访问者Assignment
s)继续下降任何一方的表达。技术上可以使用单个访问者完成所有操作,但这将涉及访问者内部的重要状态管理。无论如何,我非常相信这种管理的复杂程度如此之高,以至于这种实施实际上效率会低于不同的访问者。
所以我们可以想象这样的事情:
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);
出于同样的原因,在表达式线性化期间,只访问赋值的右侧,因为&#34;结果值&#34;任务的右边是右边的。在这种情况下,左手边只是一个可以安全忽略的副作用。
鉴于我不了解您的具体情况所需的信息的性质,我将不再进一步描述路径的实际建模方式。您可能更适合为左侧表达式和右侧表达式分别创建不同的访问者类,例如为了更好地处理右侧可能实际涉及多个变量/字段/方法调用的事实通过二元运算符。这将是你的决定。
关于要讨论的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;
}
在前面的示例中,这些方法将在path
中收集以下序列:this
,someMethod()
,fieldB
。我认为,这非常接近你所寻找的。如果您想收集所有字段访问/方法调用序列(例如,您希望访问者同时返回this,someMethod(),fieldB
和this,fieldA
),那么您可以重写visit(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源代码;虽然这个特定的访问者比大多数其他ASTVisitor
大得多,但它更容易理解。
对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;
}
}
希望有所帮助。