Chromium Code Reviews| Index: editor/util/plugins/com.google.dart.java2dart/src/com/google/dart/java2dart/processor/PropertySemanticProcessor.java |
| diff --git a/editor/util/plugins/com.google.dart.java2dart/src/com/google/dart/java2dart/processor/PropertySemanticProcessor.java b/editor/util/plugins/com.google.dart.java2dart/src/com/google/dart/java2dart/processor/PropertySemanticProcessor.java |
| index 2be66ec006a830000a7f58fa7086f8be824755d5..1e9e7b964353fd279f584e3ef80db4964ae6ed3b 100644 |
| --- a/editor/util/plugins/com.google.dart.java2dart/src/com/google/dart/java2dart/processor/PropertySemanticProcessor.java |
| +++ b/editor/util/plugins/com.google.dart.java2dart/src/com/google/dart/java2dart/processor/PropertySemanticProcessor.java |
| @@ -14,13 +14,18 @@ |
| package com.google.dart.java2dart.processor; |
| +import com.google.common.collect.ImmutableList; |
| +import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Sets; |
| +import com.google.dart.engine.ast.ASTNode; |
| import com.google.dart.engine.ast.AssignmentExpression; |
| +import com.google.dart.engine.ast.Block; |
| import com.google.dart.engine.ast.BlockFunctionBody; |
| import com.google.dart.engine.ast.ClassDeclaration; |
| import com.google.dart.engine.ast.ClassMember; |
| import com.google.dart.engine.ast.CompilationUnit; |
| +import com.google.dart.engine.ast.ConstructorDeclaration; |
| import com.google.dart.engine.ast.Expression; |
| import com.google.dart.engine.ast.ExpressionFunctionBody; |
| import com.google.dart.engine.ast.ExpressionStatement; |
| @@ -28,13 +33,17 @@ import com.google.dart.engine.ast.FieldDeclaration; |
| import com.google.dart.engine.ast.FormalParameter; |
| import com.google.dart.engine.ast.MethodDeclaration; |
| import com.google.dart.engine.ast.MethodInvocation; |
| +import com.google.dart.engine.ast.NodeList; |
| import com.google.dart.engine.ast.PropertyAccess; |
| +import com.google.dart.engine.ast.RedirectingConstructorInvocation; |
| import com.google.dart.engine.ast.SimpleFormalParameter; |
| import com.google.dart.engine.ast.SimpleIdentifier; |
| import com.google.dart.engine.ast.Statement; |
| +import com.google.dart.engine.ast.SuperConstructorInvocation; |
| import com.google.dart.engine.ast.ThisExpression; |
| import com.google.dart.engine.ast.TypeName; |
| import com.google.dart.engine.ast.VariableDeclaration; |
| +import com.google.dart.engine.ast.VariableDeclarationList; |
| import com.google.dart.engine.ast.visitor.RecursiveASTVisitor; |
| import com.google.dart.engine.scanner.Keyword; |
| import com.google.dart.engine.scanner.TokenType; |
| @@ -42,18 +51,21 @@ import com.google.dart.java2dart.Context; |
| import com.google.dart.java2dart.util.Bindings; |
| import static com.google.dart.java2dart.util.ASTFactory.assignmentExpression; |
| +import static com.google.dart.java2dart.util.ASTFactory.emptyFunctionBody; |
| +import static com.google.dart.java2dart.util.ASTFactory.fieldFormalParameter; |
| import static com.google.dart.java2dart.util.ASTFactory.propertyAccess; |
| import static com.google.dart.java2dart.util.TokenFactory.token; |
| import org.apache.commons.lang3.StringUtils; |
| import org.eclipse.jdt.core.dom.IBinding; |
| import org.eclipse.jdt.core.dom.IMethodBinding; |
| -import org.eclipse.jdt.core.dom.ITypeBinding; |
| -import org.eclipse.jdt.core.dom.IVariableBinding; |
| +import java.lang.reflect.Field; |
| +import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| +import java.util.concurrent.atomic.AtomicBoolean; |
| /** |
| * {@link SemanticProcessor} for converting <code>getX()</code> or <code>setX(x)</code> into getter |
| @@ -61,17 +73,59 @@ import java.util.Set; |
| */ |
| public class PropertySemanticProcessor extends SemanticProcessor { |
| private static class FieldPropertyInfo { |
| + final ClassDeclaration clazz; |
| final String name; |
| MethodDeclaration getter; |
| MethodDeclaration setter; |
| - String getterField; |
| - String setterField; |
| + VariableDeclaration getterField; |
| + VariableDeclaration setterField; |
| + VariableDeclaration field; |
| + SimpleIdentifier fieldName; |
| + List<SimpleIdentifier> fieldAssignmentReferences; |
| - public FieldPropertyInfo(String name) { |
| + public FieldPropertyInfo(ClassDeclaration clazz, String name) { |
| + this.clazz = clazz; |
| this.name = name; |
| } |
| } |
| + public static final String KEY_ORIGINAL_PARAMETER = "original-formal-parameter"; |
| + |
| + private static void clearNodeParent(ASTNode node) { |
| + // TODO(scheglov) should ASTNode support this? |
|
Brian Wilkerson
2013/12/10 14:57:41
Why do we need to do this at all? If the parent's
scheglov
2013/12/10 18:21:09
I think you are right.
It is not nice to force eac
|
| + try { |
| + Field field = ASTNode.class.getDeclaredField("parent"); |
| + field.setAccessible(true); |
| + field.set(node, null); |
| + } catch (Throwable e) { |
| + } |
| + } |
| + |
| + private static VariableDeclaration getFieldDeclaration(ClassDeclaration clazz, String name) { |
|
Brian Wilkerson
2013/12/10 14:57:41
This, on the other hand, clearly belongs as a util
scheglov
2013/12/10 18:21:09
Good idea.
https://codereview.chromium.org/1081730
|
| + for (ClassMember classMember : clazz.getMembers()) { |
| + if (classMember instanceof FieldDeclaration) { |
| + FieldDeclaration fieldDeclaration = (FieldDeclaration) classMember; |
| + List<VariableDeclaration> variables = fieldDeclaration.getFields().getVariables(); |
| + for (VariableDeclaration field : variables) { |
| + if (field.getName().getName().equals(name)) { |
| + return field; |
| + } |
| + } |
| + } |
| + } |
| + return null; |
| + } |
| + |
| + private static int getNumConstructors(ClassDeclaration classDeclaration) { |
| + int numConstructors = 0; |
| + for (ClassMember member : classDeclaration.getMembers()) { |
| + if (member instanceof ConstructorDeclaration) { |
| + numConstructors++; |
| + } |
| + } |
| + return numConstructors; |
| + } |
| + |
| private static boolean hasPrefix(String name, String prefix) { |
| // should start with prefix |
| if (!name.startsWith(prefix)) { |
| @@ -87,16 +141,165 @@ public class PropertySemanticProcessor extends SemanticProcessor { |
| return Character.isUpperCase(nextChar); |
| } |
| + /** |
| + * @return {@code true} if given {@link ASTNode} is not a part of a {@link CompilationUnit}. |
| + */ |
| + private static boolean isDanglingNode(ASTNode node) { |
| + return node.getAncestor(CompilationUnit.class) == null; |
| + } |
| + |
| + private static boolean isValidFieldProperty(FieldPropertyInfo property) { |
| + // we need getter |
| + VariableDeclaration getField = property.getterField; |
| + if (property.getter == null || getField == null) { |
| + return false; |
| + } |
| + // setter should be valid |
| + VariableDeclaration setField = property.setterField; |
| + if (property.setter != null && setField == null) { |
| + return false; |
| + } |
| + // if there are both getter and setter, their fields should be the same |
| + if (getField != null && setField != null && getField != setField) { |
| + return false; |
| + } |
| + property.field = getField; |
| + property.fieldName = property.field.getName(); |
| + // OK |
| + return true; |
| + } |
| + |
| private static boolean isValidSetterType(TypeName type) { |
| return type.getName().getName().equals("void"); |
| } |
| + /** |
| + * Checks if {@link CompilationUnit} declares field with name "onlyBasicGettersSetters", removes |
| + * it and return {@code true}. |
| + */ |
| + private static boolean onlyBasicGettersSetters(CompilationUnit unit) { |
| + final AtomicBoolean result = new AtomicBoolean(); |
| + unit.accept(new RecursiveASTVisitor<Void>() { |
| + @Override |
| + public Void visitVariableDeclaration(VariableDeclaration node) { |
| + if (node.getName().getName().equals("_onlyBasicGettersSetters")) { |
| + FieldDeclaration fieldDeclaration = (FieldDeclaration) node.getParent().getParent(); |
| + ClassDeclaration clazz = (ClassDeclaration) fieldDeclaration.getParent(); |
| + clazz.getMembers().remove(fieldDeclaration); |
| + result.set(true); |
| + } |
| + return null; |
| + } |
| + }); |
| + return result.get(); |
| + } |
| + |
| + /** |
| + * Removes given {@link Statement} from its {@link Block}. |
| + */ |
| + private static NodeList<Statement> removeBlockStatement(Statement statement) { |
| + NodeList<Statement> statements = ((Block) statement.getParent()).getStatements(); |
| + statements.remove(statement); |
| + clearNodeParent(statement); |
| + return statements; |
| + } |
| + |
| + /** |
| + * Removes the given {@link ClassMember} from its parent {@link ClassDeclaration}. |
| + */ |
| + private static void removeClassMember(ClassMember member) { |
| + ClassDeclaration clazz = (ClassDeclaration) member.getParent(); |
| + List<ClassMember> members = clazz.getMembers(); |
| + members.remove(member); |
| + } |
| + |
| public PropertySemanticProcessor(Context context) { |
| super(context); |
| } |
| + public void convertToField(FieldPropertyInfo property) { |
| + MethodDeclaration getter = property.getter; |
| + MethodDeclaration setter = property.setter; |
| + VariableDeclaration field = property.field; |
| + SimpleIdentifier fieldName = property.fieldName; |
| + int numConstructors = getNumConstructors(property.clazz); |
| + // analyze field assignments |
| + boolean canBeFinal; |
| + { |
| + getAssignmentReferencesInConstructors(property); |
| + // analyze constructor assignments |
| + List<SimpleIdentifier> references = property.fieldAssignmentReferences; |
| + canBeFinal = references != null && references.size() == numConstructors; |
| + // if there are no setter and we cannot make the field final, then keep getter/setter |
| + if (setter == null && !canBeFinal) { |
| + return; |
| + } |
| + // convert to "this.property" in constructors |
| + if (references != null) { |
| + convertToFieldFormalInitializers(property, references); |
| + } |
| + } |
| + // update field |
| + { |
| + context.renameIdentifier(fieldName, property.name); |
| + // mark "final" if no writes |
| + boolean readOnly = isReadOnlyField(fieldName); |
| + if (readOnly) { |
| + ((VariableDeclarationList) field.getParent()).setKeyword(token(Keyword.FINAL)); |
| + if (canBeFinal && numConstructors != 0) { |
| + field.setInitializer(null); |
| + } |
| + } |
| + } |
| + // remove getter, update field |
| + if (getter != null) { |
| + removeClassMember(getter); |
| + } |
| + // remove setter |
| + if (setter != null) { |
| + removeClassMember(setter); |
| + } |
| + // now these are field references |
| + { |
| + IBinding fieldBinding = context.getNodeBinding(fieldName); |
| + replaceMethodReferencesWithFieldBindings(getter, fieldBinding); |
| + replaceMethodReferencesWithFieldBindings(setter, fieldBinding); |
| + } |
| + } |
| + |
| + public void convertToFields(CompilationUnit unit) { |
| + final Set<IBinding> overriddenMethods = getOverriddenMethods(unit); |
| + unit.accept(new RecursiveASTVisitor<Void>() { |
| + @Override |
| + public Void visitClassDeclaration(ClassDeclaration node) { |
| + List<FieldPropertyInfo> properties = getFieldProperties(node); |
| + removeOverriddenProperties(overriddenMethods, properties); |
| + for (FieldPropertyInfo property : properties) { |
| + if (!isValidFieldProperty(property)) { |
| + continue; |
| + } |
| + convertToField(property); |
| + } |
| + return null; |
| + } |
| + }); |
| + } |
| + |
| @Override |
| - public void process(final CompilationUnit unit) { |
| + public void process(CompilationUnit unit) { |
| + convertGettersSetters(unit); |
| + if (onlyBasicGettersSetters(unit)) { |
| + return; |
| + } |
| + convertToFields(unit); |
| + } |
| + |
| + /** |
| + * Converts every <code>getX()</code> into <code>get x</code> and <code>setX(v)</code> into |
| + * <code>set x(v)</code> with corresponding replacement of {@link MethodInvocation} into |
| + * {@link PropertyAccess} in references. |
| + */ |
| + private void convertGettersSetters(CompilationUnit unit) { |
| unit.accept(new RecursiveASTVisitor<Void>() { |
| @Override |
| public Void visitFieldDeclaration(FieldDeclaration node) { |
| @@ -182,197 +385,314 @@ public class PropertySemanticProcessor extends SemanticProcessor { |
| return super.visitMethodDeclaration(node); |
| } |
| }); |
| - // remember all overridden and overriding methods |
| - final Set<IMethodBinding> ignoredMethods = Sets.newHashSet(); |
| - unit.accept(new RecursiveASTVisitor<Void>() { |
| - @Override |
| - public Void visitMethodDeclaration(MethodDeclaration node) { |
| - IMethodBinding binding = (IMethodBinding) context.getNodeBinding(node); |
| - if (binding != null) { |
| - IMethodBinding superBinding = Bindings.findOverriddenMethod(binding, true); |
| - if (superBinding != null) { |
| - ignoredMethods.add(binding); |
| - ignoredMethods.add(superBinding); |
| + } |
| + |
| + private void convertToFieldFormalInitializers(FieldPropertyInfo property, |
| + List<SimpleIdentifier> references) { |
| + for (SimpleIdentifier reference : references) { |
| + // prepare constructor |
| + ConstructorDeclaration constructor = reference.getAncestor(ConstructorDeclaration.class); |
| + // convert "formal parameter" into "field initializing formal parameter" |
| + List<FormalParameter> parameters = constructor.getParameters().getParameters(); |
| + for (FormalParameter parameter : parameters) { |
| + if (parameter instanceof SimpleFormalParameter) { |
| + SimpleFormalParameter simpleParameter = (SimpleFormalParameter) parameter; |
| + SimpleIdentifier parameterName = simpleParameter.getIdentifier(); |
| + if (parameterName.getName().equals(property.name)) { |
| + FormalParameter ffp = fieldFormalParameter(null, null, parameterName); |
| + ffp.setProperty(KEY_ORIGINAL_PARAMETER, parameter); |
| + replaceNode(simpleParameter, ffp); |
| + break; |
| } |
| } |
| - return null; |
| } |
| - }); |
| - // try to convert properties into fields |
| - unit.accept(new RecursiveASTVisitor<Void>() { |
| - private final Map<String, FieldPropertyInfo> properties = Maps.newHashMap(); |
| + // remove assignment statement |
| + Statement statement = (Statement) reference.getParent().getParent().getParent(); |
| + List<Statement> statements = removeBlockStatement(statement); |
| + if (statements.isEmpty()) { |
| + constructor.setBody(emptyFunctionBody()); |
| + } |
| + } |
| + } |
| - @Override |
| - public Void visitClassDeclaration(ClassDeclaration node) { |
| - properties.clear(); |
| - super.visitClassDeclaration(node); |
| - // simplify field properties |
| - for (FieldPropertyInfo property : properties.values()) { |
| - MethodDeclaration getter = property.getter; |
| - MethodDeclaration setter = property.setter; |
| - // no getter or not field |
| - if (getter == null || property.getterField == null) { |
| - continue; |
| - } |
| - // setter is not field |
| - if (setter != null && !StringUtils.equals(property.getterField, property.setterField)) { |
| - continue; |
| - } |
| - // remove getter, update field |
| - if (getter != null) { |
| - ClassDeclaration clazz = (ClassDeclaration) getter.getParent(); |
| - List<ClassMember> members = clazz.getMembers(); |
| - members.remove(getter); |
| - for (ClassMember member : members) { |
| - if (member instanceof FieldDeclaration) { |
| - FieldDeclaration fieldDeclaration = (FieldDeclaration) member; |
| - List<VariableDeclaration> variables = fieldDeclaration.getFields().getVariables(); |
| - for (VariableDeclaration field : variables) { |
| - SimpleIdentifier fieldName = field.getName(); |
| - if (fieldName.getName().equals(property.getterField)) { |
| - context.renameIdentifier(fieldName, property.name); |
| - // mark "final" is no writes |
| - { |
| - boolean readOnly = true; |
| - List<SimpleIdentifier> references = context.getReferences(fieldName); |
| - for (SimpleIdentifier reference : references) { |
| - readOnly &= !reference.inSetterContext(); |
| - } |
| - if (readOnly) { |
| - fieldDeclaration.getFields().setKeyword(token(Keyword.FINAL)); |
| - } |
| - } |
| - // now these are field references |
| - { |
| - IBinding fieldBinding = context.getNodeBinding(fieldName); |
| - replaceMethodReferencesWithFieldBindings(getter, fieldBinding); |
| - replaceMethodReferencesWithFieldBindings(setter, fieldBinding); |
| - } |
| - // done |
| - break; |
| - } |
| - } |
| - } |
| - } |
| - } |
| - // remove setter |
| - if (setter != null) { |
| - ClassDeclaration clazz = (ClassDeclaration) setter.getParent(); |
| - List<ClassMember> members = clazz.getMembers(); |
| - members.remove(setter); |
| - } |
| + /** |
| + * If the given {@link SimpleIdentifier} is the field reference and: |
| + * <ul> |
| + * <li>The field is assigned only in constructors.</li> |
| + * <li>The field is assigned only once in each constructor.</li> |
| + * <li>The assignment looks like <code>this.name = name;</code> statement.</li> |
| + * </ul> |
| + * Then returns all references of the field in these simple assignments. Otherwise returns |
| + * {@code null}. |
| + */ |
| + private void getAssignmentReferencesInConstructors(FieldPropertyInfo property) { |
| + Set<ConstructorDeclaration> constructorsWithAssignments = Sets.newHashSet(); |
| + List<SimpleIdentifier> assignReferences = Lists.newArrayList(); |
| + List<SimpleIdentifier> references = context.getReferences(property.fieldName); |
| + for (SimpleIdentifier reference : references) { |
| + // not in setter context |
| + if (!reference.inSetterContext()) { |
| + continue; |
| + } |
| + // should be in constructor |
| + ConstructorDeclaration constructor = reference.getAncestor(ConstructorDeclaration.class); |
| + if (constructor == null) { |
| + if (reference.getAncestor(MethodDeclaration.class) == property.setter) { |
| + continue; |
| } |
| - // done |
| - return null; |
| + return; |
| + } |
| + // may be more than one assignment |
| + if (constructorsWithAssignments.contains(constructor)) { |
| + return; |
| + } |
| + constructorsWithAssignments.add(constructor); |
| + // should be "this.name" |
| + if (!(reference.getParent() instanceof PropertyAccess)) { |
| + return; |
| + } |
| + PropertyAccess propertyAccess = (PropertyAccess) reference.getParent(); |
| + if (!(propertyAccess.getTarget() instanceof ThisExpression)) { |
| + return; |
| + } |
| + // should be assignment |
| + if (!(propertyAccess.getParent() instanceof AssignmentExpression)) { |
| + return; |
| + } |
| + AssignmentExpression assignment = (AssignmentExpression) propertyAccess.getParent(); |
| + // should be "this.name = _name" |
| + if (assignment.getRightHandSide() instanceof SimpleIdentifier) { |
| + SimpleIdentifier right = (SimpleIdentifier) assignment.getRightHandSide(); |
| + if (isReferencedInSuperConstructorInvocation(right)) { |
| + continue; |
| + } |
| + String rightName = right.getName(); |
| + String leftName = reference.getName(); |
| + if (!leftName.equals("_" + rightName)) { |
| + return; |
| + } |
| + } else { |
| + return; |
| } |
| + // should be simple, just in statement |
| + if (!(assignment.getParent() instanceof ExpressionStatement)) { |
| + return; |
| + } |
| + // should be just "=" |
| + if (assignment.getOperator().getType() != TokenType.EQ) { |
| + return; |
| + } |
| + // OK, valid assignment reference |
| + assignReferences.add(reference); |
| + } |
| + // OK, no other assignments found |
| + property.fieldAssignmentReferences = assignReferences; |
| + } |
| + private List<FieldPropertyInfo> getFieldProperties(final ClassDeclaration classDeclaration) { |
| + final Map<String, FieldPropertyInfo> properties = Maps.newHashMap(); |
| + classDeclaration.accept(new RecursiveASTVisitor<Void>() { |
| @Override |
| public Void visitMethodDeclaration(MethodDeclaration node) { |
| - IMethodBinding binding = (IMethodBinding) context.getNodeBinding(node); |
| - // don't remove method if it overrides |
| - if (binding == null) { |
| - return null; |
| - } |
| - if (ignoredMethods.contains(binding)) { |
| + // we need getter or setter |
| + if (!node.isGetter() && !node.isSetter()) { |
| return null; |
| } |
| - ITypeBinding declaringClass = binding.getDeclaringClass(); |
| + // prepare property |
| + String propertyName = node.getName().getName(); |
| + FieldPropertyInfo property = getProperty(propertyName); |
| // getter |
| if (node.isGetter()) { |
| - String name = node.getName().getName(); |
| - FieldPropertyInfo property = getProperty(name); |
| - property.getter = node; |
| - if (node.getBody() instanceof ExpressionFunctionBody) { |
| - ExpressionFunctionBody body = (ExpressionFunctionBody) node.getBody(); |
| - Expression expression = body.getExpression(); |
| - if (expression instanceof SimpleIdentifier) { |
| - SimpleIdentifier identifier = (SimpleIdentifier) expression; |
| - // may be not a local field |
| - IBinding identifierBinding = context.getNodeBinding(identifier); |
| - if (identifierBinding instanceof IVariableBinding) { |
| - IVariableBinding fieldBinding = (IVariableBinding) identifierBinding; |
| - if (fieldBinding.getDeclaringClass() != declaringClass) { |
| - return null; |
| - } |
| - } else { |
| - return null; |
| - } |
| - // OK, remember the field |
| - property.getterField = identifier.getName(); |
| - } |
| - } |
| + processGetter(property, node); |
| } |
| // setter |
| if (node.isSetter()) { |
| - String name = node.getName().getName(); |
| - FieldPropertyInfo property = getProperty(name); |
| - property.setter = node; |
| - // block body |
| - if (!(node.getBody() instanceof BlockFunctionBody)) { |
| - return null; |
| - } |
| - // single statement |
| - BlockFunctionBody body = (BlockFunctionBody) node.getBody(); |
| - List<Statement> statements = body.getBlock().getStatements(); |
| - if (statements.size() != 1) { |
| - return null; |
| - } |
| - Statement statement = statements.get(0); |
| - // prepare expression |
| - if (!(statement instanceof ExpressionStatement)) { |
| - return null; |
| - } |
| - Expression expression = ((ExpressionStatement) statement).getExpression(); |
| - // should be assignment |
| - if (!(expression instanceof AssignmentExpression)) { |
| - return null; |
| - } |
| - AssignmentExpression assignment = (AssignmentExpression) expression; |
| - // simple assignment |
| - if (assignment.getOperator().getType() != TokenType.EQ) { |
| - return null; |
| - } |
| - // RHS should be just parameter name |
| - Expression rhs = assignment.getRightHandSide(); |
| - if (!(rhs instanceof SimpleIdentifier)) { |
| - return null; |
| - } |
| - SimpleIdentifier rhsName = (SimpleIdentifier) rhs; |
| - String parameterName = ((SimpleFormalParameter) node.getParameters().getParameters().get( |
| - 0)).getIdentifier().getName(); |
| - if (!rhsName.getName().equals(parameterName)) { |
| - return null; |
| - } |
| - // LHS |
| - Expression lhs = assignment.getLeftHandSide(); |
| - if (lhs instanceof PropertyAccess) { |
| - PropertyAccess access = (PropertyAccess) lhs; |
| - if (access.getTarget() instanceof ThisExpression) { |
| - property.setterField = access.getPropertyName().getName(); |
| - } |
| - } |
| + processSetter(property, node); |
| } |
| + // done |
| return null; |
| } |
| private FieldPropertyInfo getProperty(String name) { |
| FieldPropertyInfo property = properties.get(name); |
| if (property == null) { |
| - property = new FieldPropertyInfo(name); |
| + property = new FieldPropertyInfo(classDeclaration, name); |
| properties.put(name, property); |
| } |
| return property; |
| } |
| - private void replaceMethodReferencesWithFieldBindings(MethodDeclaration method, |
| - IBinding fieldBinding) { |
| - if (method == null) { |
| + private boolean isFieldWithPropertyName(String fieldName, String propertyName) { |
| + String prefix = "_" + propertyName; |
| + if (!fieldName.startsWith(prefix)) { |
| + return false; |
| + } |
| + String rest = fieldName.substring(prefix.length()); |
| + if (!StringUtils.isNumericSpace(rest)) { |
| + return false; |
| + } |
| + return true; |
| + } |
| + |
| + private void processGetter(FieldPropertyInfo property, MethodDeclaration node) { |
| + property.getter = node; |
| + if (node.getBody() instanceof ExpressionFunctionBody) { |
| + ExpressionFunctionBody body = (ExpressionFunctionBody) node.getBody(); |
| + Expression expression = body.getExpression(); |
| + if (expression instanceof SimpleIdentifier) { |
| + SimpleIdentifier identifier = (SimpleIdentifier) expression; |
| + String fieldName = identifier.getName(); |
| + // may be not a local field |
| + VariableDeclaration field = getFieldDeclaration(classDeclaration, fieldName); |
| + if (field == null) { |
| + return; |
| + } |
| + // maybe field doesn't have the same name as the property |
| + if (!isFieldWithPropertyName(fieldName, property.name)) { |
| + return; |
| + } |
| + // OK, remember the field |
| + property.getterField = field; |
| + } |
| + } |
| + } |
| + |
| + private void processSetter(FieldPropertyInfo property, MethodDeclaration node) { |
| + property.setter = node; |
| + // block body |
| + if (!(node.getBody() instanceof BlockFunctionBody)) { |
| + return; |
| + } |
| + // single statement |
| + BlockFunctionBody body = (BlockFunctionBody) node.getBody(); |
| + List<Statement> statements = body.getBlock().getStatements(); |
| + if (statements.size() != 1) { |
| + return; |
| + } |
| + Statement statement = statements.get(0); |
| + // prepare expression |
| + if (!(statement instanceof ExpressionStatement)) { |
| + return; |
| + } |
| + Expression expression = ((ExpressionStatement) statement).getExpression(); |
| + // should be assignment |
| + if (!(expression instanceof AssignmentExpression)) { |
| + return; |
| + } |
| + AssignmentExpression assignment = (AssignmentExpression) expression; |
| + // simple assignment |
| + if (assignment.getOperator().getType() != TokenType.EQ) { |
| + return; |
| + } |
| + // RHS should be just parameter name |
| + Expression rhs = assignment.getRightHandSide(); |
| + if (!(rhs instanceof SimpleIdentifier)) { |
| + return; |
| + } |
| + SimpleIdentifier rhsName = (SimpleIdentifier) rhs; |
| + List<FormalParameter> parameters = node.getParameters().getParameters(); |
| + String parameterName = ((SimpleFormalParameter) parameters.get(0)).getIdentifier().getName(); |
| + if (!rhsName.getName().equals(parameterName)) { |
| return; |
| } |
| - for (SimpleIdentifier reference : context.getReferences(method.getName())) { |
| - context.putReference(reference, fieldBinding, null); |
| + // LHS |
| + Expression lhs = assignment.getLeftHandSide(); |
| + if (lhs instanceof PropertyAccess) { |
| + PropertyAccess access = (PropertyAccess) lhs; |
| + if (access.getTarget() instanceof ThisExpression) { |
| + String fieldName = access.getPropertyName().getName(); |
| + // may be not a local field |
| + VariableDeclaration field = getFieldDeclaration(classDeclaration, fieldName); |
| + if (field == null) { |
| + return; |
| + } |
| + // may be field has not the same name as the property |
| + if (!isFieldWithPropertyName(fieldName, property.name)) { |
| + return; |
| + } |
| + // OK, remember the field |
| + property.setterField = field; |
| + } |
| } |
| } |
| }); |
| + return Lists.newArrayList(properties.values()); |
| + } |
| + |
| + private Set<IBinding> getOverriddenMethods(CompilationUnit unit) { |
| + final Set<IBinding> overriddenMethods = Sets.newHashSet(); |
| + unit.accept(new RecursiveASTVisitor<Void>() { |
| + @Override |
| + public Void visitMethodDeclaration(MethodDeclaration node) { |
| + IMethodBinding binding = (IMethodBinding) context.getNodeBinding(node); |
| + if (binding != null) { |
| + IMethodBinding superBinding = Bindings.findOverriddenMethod(binding, true); |
| + if (superBinding != null) { |
| + overriddenMethods.add(superBinding); |
| + } |
| + } |
| + return null; |
| + } |
| + }); |
| + return overriddenMethods; |
| + } |
| + |
| + private boolean isOverridden(Set<IBinding> overriddenMethods, MethodDeclaration method) { |
| + if (method == null) { |
| + return false; |
| + } |
| + IBinding binding = context.getNodeBinding(method); |
| + return overriddenMethods.contains(binding); |
| + } |
| + |
| + private boolean isReadOnlyField(SimpleIdentifier fieldName) { |
| + List<SimpleIdentifier> references = context.getReferences(fieldName); |
| + for (SimpleIdentifier reference : references) { |
| + if (isDanglingNode(reference)) { |
|
Brian Wilkerson
2013/12/10 14:57:41
Instead of depending on the parent chain being bro
|
| + continue; |
| + } |
| + if (reference.inSetterContext()) { |
| + return false; |
| + } |
| + } |
| + return true; |
| + } |
| + |
| + /** |
| + * @return {@code true} if the given parameter is referenced |
| + * {@link RedirectingConstructorInvocation} or {@link SuperConstructorInvocation}. |
| + */ |
| + private boolean isReferencedInSuperConstructorInvocation(SimpleIdentifier parameter) { |
| + List<SimpleIdentifier> references = context.getReferences(parameter); |
| + for (SimpleIdentifier reference : references) { |
| + if (reference.getAncestor(SuperConstructorInvocation.class) != null) { |
| + return true; |
| + } |
| + } |
| + return false; |
| + } |
| + |
| + private void removeOverriddenProperties(Set<IBinding> overriddenMethods, |
| + List<FieldPropertyInfo> properties) { |
| + for (Iterator<FieldPropertyInfo> I = properties.iterator(); I.hasNext();) { |
| + FieldPropertyInfo property = I.next(); |
| + if (isOverridden(overriddenMethods, property.getter) |
| + || isOverridden(overriddenMethods, property.setter)) { |
| + I.remove(); |
| + } |
| + } |
| + } |
| + |
| + private void replaceMethodReferencesWithFieldBindings(MethodDeclaration method, |
| + IBinding fieldBinding) { |
| + if (method == null) { |
| + return; |
| + } |
| + SimpleIdentifier name = method.getName(); |
| + List<SimpleIdentifier> references = context.getReferences(name); |
| + references = ImmutableList.copyOf(references); |
| + for (SimpleIdentifier reference : references) { |
| + context.putReference(reference, fieldBinding, null); |
| + } |
| } |
| } |