/* * Copyright (c) 2013, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.java2dart.processor; import com.google.common.collect.Sets; import com.google.dart.engine.ast.AstNode; import com.google.dart.engine.ast.CatchClause; import com.google.dart.engine.ast.ClassDeclaration; import com.google.dart.engine.ast.CompilationUnit; import com.google.dart.engine.ast.ConstructorDeclaration; import com.google.dart.engine.ast.DeclaredIdentifier; import com.google.dart.engine.ast.Expression; import com.google.dart.engine.ast.ForEachStatement; 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.PrefixedIdentifier; import com.google.dart.engine.ast.PropertyAccess; import com.google.dart.engine.ast.SimpleIdentifier; import com.google.dart.engine.ast.VariableDeclaration; import com.google.dart.engine.ast.visitor.GeneralizingAstVisitor; import com.google.dart.engine.ast.visitor.RecursiveAstVisitor; import com.google.dart.java2dart.Context; import com.google.dart.java2dart.SyntaxTranslator; import static com.google.dart.java2dart.util.AstFactory.identifier; import static com.google.dart.java2dart.util.AstFactory.propertyAccess; import static com.google.dart.java2dart.util.AstFactory.thisExpression; import org.eclipse.jdt.core.dom.IBinding; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.IVariableBinding; import org.eclipse.jdt.core.dom.Modifier; import java.util.Set; /** * {@link SemanticProcessor} for correcting Java vs. Dart local variables semantic differences. */ public class LocalVariablesSemanticProcessor extends SemanticProcessor { private class ClassSensetiveVisitor extends RecursiveAstVisitor<Void> { private Set<String> hierarchyNames; private Set<String> methodNames; @Override public Void visitClassDeclaration(ClassDeclaration node) { hierarchyNames = null; try { return super.visitClassDeclaration(node); } finally { hierarchyNames = null; } } @Override public Void visitForEachStatement(ForEachStatement node) { DeclaredIdentifier loopVariable = node.getLoopVariable(); if (loopVariable != null) { SimpleIdentifier nameNode = loopVariable.getIdentifier(); String variableName = nameNode.getName(); if (Context.FORBIDDEN_NAMES.contains(variableName)) { ensureHierarchyNames(node); ensureMethodNames(node); String newName = generateUniqueVariableName(variableName); context.renameIdentifier(nameNode, newName); } } return super.visitForEachStatement(node); } @Override public Void visitMethodDeclaration(MethodDeclaration node) { methodNames = null; try { return super.visitMethodDeclaration(node); } finally { methodNames = null; } } void ensureHierarchyNames(AstNode node) { if (hierarchyNames != null) { return; } hierarchyNames = context.getSuperMembersNames(node); } void ensureMethodNames(AstNode node) { methodNames = Sets.newHashSet(); MethodDeclaration method = node.getAncestor(MethodDeclaration.class); if (method != null) { method.accept(new RecursiveAstVisitor<Void>() { @Override public Void visitVariableDeclaration(VariableDeclaration node) { methodNames.add(node.getName().getName()); return super.visitVariableDeclaration(node); } }); } } /** * @return the new name for variable which does not conflict with name of any member in super * classes - {@link #hierarchyNames}. */ String generateUniqueVariableName(String name) { int index = 2; while (true) { String newName = name + index; if (!hierarchyNames.contains(newName) && !methodNames.contains(newName) && !Context.FORBIDDEN_NAMES.contains(newName)) { methodNames.add(newName); return newName; } index++; } } } private static boolean isMethodInvocationName(AstNode node) { AstNode parent = node.getParent(); return parent instanceof MethodInvocation && ((MethodInvocation) parent).getMethodName() == node; } private static boolean isQualifiedName(SimpleIdentifier node) { AstNode parent = node.getParent(); if (parent instanceof PropertyAccess) { return ((PropertyAccess) parent).getPropertyName() == node; } if (parent instanceof PrefixedIdentifier) { return ((PrefixedIdentifier) parent).getIdentifier() == node; } if (parent instanceof MethodInvocation) { MethodInvocation invocation = (MethodInvocation) parent; return invocation.getTarget() != null && invocation.getMethodName() == node; } return false; } public LocalVariablesSemanticProcessor(Context context) { super(context); } @Override public void process(CompilationUnit unit) { qualifyShadowedMembers(unit); ensureNameNotReferencedInInitializer(unit); } private void ensureNameNotReferencedInInitializer(CompilationUnit unit) { unit.accept(new ClassSensetiveVisitor() { @Override public Void visitVariableDeclaration(VariableDeclaration node) { String name = node.getName().getName(); Expression initializer = node.getInitializer(); if (initializer != null) { initializer.accept(this); } if (Context.FORBIDDEN_NAMES.contains(name)) { ensureHierarchyNames(node); ensureMethodNames(node); String newName = generateUniqueVariableName(name); context.renameIdentifier(node.getName(), newName); } return null; } }); } private Expression getMemberQualifier(AstNode node) { IBinding binding = context.getNodeBinding(node); if (!Modifier.isStatic(binding.getModifiers())) { return thisExpression(); } if (binding instanceof IVariableBinding) { IVariableBinding variableBinding = (IVariableBinding) binding; String className = variableBinding.getDeclaringClass().getName(); return identifier(className); } if (binding instanceof IMethodBinding) { IMethodBinding methodBinding = (IMethodBinding) binding; String className = methodBinding.getDeclaringClass().getName(); return identifier(className); } throw new IllegalArgumentException("Field or method expected: " + binding); } private void qualifyShadowedMembers(CompilationUnit unit) { unit.accept(new RecursiveAstVisitor<Void>() { @Override public Void visitConstructorDeclaration(ConstructorDeclaration node) { qualifyShadowedNodes(node, node.getBody()); return null; } @Override public Void visitMethodDeclaration(MethodDeclaration node) { qualifyShadowedNodes(node, node.getBody()); return null; } private void qualifyShadowedNodes(AstNode executableNode, AstNode body) { if (body == null) { return; } // prepare names of local variables defined in the same block final Set<String> definedVars = Sets.newHashSet(); executableNode.accept(new GeneralizingAstVisitor<Void>() { @Override public Void visitCatchClause(CatchClause node) { String name = node.getExceptionParameter().getName(); definedVars.add(name); return super.visitCatchClause(node); } @Override public Void visitFormalParameter(FormalParameter node) { String name = node.getIdentifier().getName(); definedVars.add(name); return null; } @Override public Void visitVariableDeclaration(VariableDeclaration node) { String name = node.getName().getName(); definedVars.add(name); return null; } }); // check all field references that could be shadowed body.accept(new RecursiveAstVisitor<Void>() { @Override public Void visitSimpleIdentifier(SimpleIdentifier node) { // check if shadowed String name = node.getName(); if (!definedVars.contains(name)) { return null; } // may be qualified if (isQualifiedName(node)) { return null; } // should be field or accessor if (!context.isFieldBinding(node) && !context.isMethodBinding(node)) { return null; } // replace shadowed unqualified reference with qualified one AstNode parent = node.getParent(); Expression qualifier = getMemberQualifier(node); if (isMethodInvocationName(node)) { MethodInvocation invocation = (MethodInvocation) parent; invocation.setTarget(qualifier); } else { SyntaxTranslator.replaceNode(parent, node, propertyAccess(qualifier, node)); } // done return null; } }); } }); } }