com.google.dart.java2dart.Context.java Source code

Java tutorial

Introduction

Here is the source code for com.google.dart.java2dart.Context.java

Source

/*
 * Copyright (c) 2012, 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;

import com.google.common.collect.ImmutableMap;
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.ArgumentList;
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.CompilationUnitMember;
import com.google.dart.engine.ast.ConstructorDeclaration;
import com.google.dart.engine.ast.Expression;
import com.google.dart.engine.ast.FieldDeclaration;
import com.google.dart.engine.ast.FormalParameter;
import com.google.dart.engine.ast.FormalParameterList;
import com.google.dart.engine.ast.Identifier;
import com.google.dart.engine.ast.InstanceCreationExpression;
import com.google.dart.engine.ast.ListLiteral;
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.SimpleIdentifier;
import com.google.dart.engine.ast.SuperConstructorInvocation;
import com.google.dart.engine.ast.ThisExpression;
import com.google.dart.engine.ast.VariableDeclaration;
import com.google.dart.engine.ast.VariableDeclarationList;
import com.google.dart.engine.ast.visitor.GeneralizingASTVisitor;
import com.google.dart.engine.ast.visitor.RecursiveASTVisitor;
import com.google.dart.engine.scanner.Keyword;
import com.google.dart.engine.scanner.KeywordToken;
import com.google.dart.engine.scanner.TokenType;
import com.google.dart.java2dart.util.Bindings;
import com.google.dart.java2dart.util.JavaUtils;

import static com.google.dart.java2dart.util.ASTFactory.assignmentExpression;
import static com.google.dart.java2dart.util.ASTFactory.block;
import static com.google.dart.java2dart.util.ASTFactory.blockFunctionBody;
import static com.google.dart.java2dart.util.ASTFactory.compilationUnit;
import static com.google.dart.java2dart.util.ASTFactory.constructorDeclaration;
import static com.google.dart.java2dart.util.ASTFactory.expressionStatement;
import static com.google.dart.java2dart.util.ASTFactory.formalParameterList;
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 static com.google.dart.java2dart.util.TokenFactory.token;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.FileASTRequestor;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;

import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Context information for Java to Dart translation.
 */
public class Context {
    /**
     * Information about constructor and its usages.
     */
    class ConstructorDescription {
        final IMethodBinding binding;
        final List<SuperConstructorInvocation> superInvocations = Lists.newArrayList();
        final List<InstanceCreationExpression> instanceCreations = Lists.newArrayList();
        final List<SimpleIdentifier> implInvocations = Lists.newArrayList();
        String declName;
        String implName;

        public ConstructorDescription(IMethodBinding binding) {
            this.binding = binding;
        }
    }

    private static final String[] JAVA_EXTENSION = { "java" };
    private final List<File> classpathFiles = Lists.newArrayList();
    private final List<File> sourceFolders = Lists.newArrayList();
    private final List<File> sourceFiles = Lists.newArrayList();

    private final Map<String, String> renameMap = Maps.newHashMap();

    private final CompilationUnit dartUniverse = compilationUnit();
    private final Map<File, List<CompilationUnitMember>> fileToMembers = Maps.newHashMap();
    private final Map<CompilationUnitMember, File> memberToFile = Maps.newHashMap();
    // information about names
    private static final Set<String> forbiddenNames = Sets.newHashSet();
    private final Set<String> usedNames = Sets.newHashSet();
    private final Set<ClassMember> privateClassMembers = Sets.newHashSet();
    private final Map<SimpleIdentifier, String> identifierToName = Maps.newHashMap();
    private final Map<String, Object> signatureToBinding = Maps.newHashMap();
    private final Map<Object, List<SimpleIdentifier>> bindingToIdentifiers = Maps.newHashMap();
    private final Map<ASTNode, Object> nodeToBinding = Maps.newHashMap();
    private final Map<ASTNode, ITypeBinding> nodeToTypeBinding = Maps.newHashMap();
    private final Map<InstanceCreationExpression, ClassDeclaration> anonymousDeclarations = Maps.newHashMap();
    private final Set<SimpleIdentifier> innerClassNames = Sets.newHashSet();
    // information about constructors
    private int technicalConstructorIndex;
    private final Map<IMethodBinding, ConstructorDescription> bindingToConstructor = Maps.newHashMap();
    private final Map<ConstructorDeclaration, IMethodBinding> constructorToBinding = Maps.newHashMap();
    // information about inner classes
    private int technicalInnerClassIndex;
    private int technicalAnonymousClassIndex;

    static {
        for (Keyword keyword : Keyword.values()) {
            if (!keyword.isPseudoKeyword()) {
                forbiddenNames.add(keyword.getSyntax());
            }
        }
    }

    /**
     * Specifies that given {@link File} should be added to Java classpath.
     */
    public void addClasspathFile(File file) {
        Assert.isLegal(file.exists(), "File '" + file + "' does not exist.");
        Assert.isLegal(file.isFile(), "File '" + file + "' is not a regular file.");
        file = file.getAbsoluteFile();
        classpathFiles.add(file);
    }

    /**
     * Specifies that field with given signature should be renamed before normalizing member names.
     */
    public void addRename(String signature, String newName) {
        renameMap.put(signature, newName);
    }

    /**
     * Specifies that given {@link File} should be translated.
     */
    public void addSourceFile(File file) {
        Assert.isLegal(file.exists(), "File '" + file + "' does not exist.");
        Assert.isLegal(file.isFile(), "File '" + file + "' is not a regular file.");
        file = file.getAbsoluteFile();
        sourceFiles.add(file);
    }

    /**
     * Specifies that all files in given folder should be translated.
     */
    public void addSourceFiles(File folder) {
        Assert.isLegal(folder.exists(), "Folder '" + folder + "' does not exist.");
        Assert.isLegal(folder.isDirectory(), "Folder '" + folder + "' is not a folder.");
        folder = folder.getAbsoluteFile();
        Collection<File> folderFiles = FileUtils.listFiles(folder, JAVA_EXTENSION, true);
        sourceFiles.addAll(folderFiles);
    }

    /**
     * Specifies that given folder is a source folder (root of Java packages hierarchy).
     */
    public void addSourceFolder(File folder) {
        Assert.isLegal(folder.exists(), "Folder '" + folder + "' does not exist.");
        Assert.isLegal(folder.isDirectory(), "Folder '" + folder + "' is not a folder.");
        folder = folder.getAbsoluteFile();
        sourceFolders.add(folder);
    }

    /**
     * In Java we can have method parameter "foo" and invoke method named "foo", and parameter will
     * not shadow invoked method. But in Dart it will.
     */
    public void ensureMethodParameterDoesNotHide(CompilationUnit unit) {
        unit.accept(new RecursiveASTVisitor<Void>() {
            @Override
            public Void visitMethodDeclaration(MethodDeclaration node) {
                FormalParameterList parameterList = node.getParameters();
                if (parameterList != null) {
                    for (FormalParameter parameter : parameterList.getParameters()) {
                        final String parameterName = parameter.getIdentifier().getName();
                        final Object parameterBinding = getNodeBinding(parameter.getIdentifier());
                        final AtomicBoolean hasHiding = new AtomicBoolean();
                        node.accept(new RecursiveASTVisitor<Void>() {
                            @Override
                            public Void visitSimpleIdentifier(SimpleIdentifier node) {
                                if (node.getName().equals(parameterName)
                                        && getNodeBinding(node) != parameterBinding) {
                                    hasHiding.set(true);
                                }
                                return super.visitSimpleIdentifier(node);
                            }
                        });
                        if (hasHiding.get()) {
                            Set<String> used = getSuperMembersNames(node);
                            String newName = generateUniqueParameterName(used, parameterName);
                            renameIdentifier(parameter.getIdentifier(), newName);
                        }
                    }
                }
                return super.visitMethodDeclaration(node);
            }

            private String generateUniqueParameterName(Set<String> used, String name) {
                int index = 2;
                while (true) {
                    String newName = name + index;
                    if (!used.contains(newName)) {
                        return newName;
                    }
                    index++;
                }
            }
        });
    }

    public void ensureNoVariableNameReferenceFromInitializer(CompilationUnit unit) {
        unit.accept(new RecursiveASTVisitor<Void>() {
            private Set<String> hierarchyNames;
            private Set<String> methodNames;
            private String currentVariableName = null;
            private boolean hasNameReference = false;

            @Override
            public Void visitClassDeclaration(ClassDeclaration node) {
                hierarchyNames = null;
                try {
                    return super.visitClassDeclaration(node);
                } finally {
                    hierarchyNames = null;
                }
            }

            @Override
            public Void visitMethodDeclaration(MethodDeclaration node) {
                methodNames = null;
                try {
                    return super.visitMethodDeclaration(node);
                } finally {
                    methodNames = null;
                }
            }

            @Override
            public Void visitSimpleIdentifier(SimpleIdentifier node) {
                hasNameReference |= node.getName().equals(currentVariableName);
                return super.visitSimpleIdentifier(node);
            }

            @Override
            public Void visitVariableDeclaration(VariableDeclaration node) {
                String oldVariableName = currentVariableName;
                try {
                    currentVariableName = node.getName().getName();
                    hasNameReference = false;
                    Expression initializer = node.getInitializer();
                    if (initializer != null) {
                        initializer.accept(this);
                    }
                    if (hasNameReference || forbiddenNames.contains(currentVariableName)) {
                        ensureHierarchyNames(node);
                        ensureMethodNames(node);
                        String newName = generateUniqueVariableName(currentVariableName);
                        renameIdentifier(node.getName(), newName);
                    }
                } finally {
                    currentVariableName = oldVariableName;
                }
                return null;
            }

            private void ensureHierarchyNames(ASTNode node) {
                if (hierarchyNames != null) {
                    return;
                }
                hierarchyNames = getSuperMembersNames(node);
            }

            private 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}.
             */
            private String generateUniqueVariableName(String name) {
                int index = 2;
                while (true) {
                    String newName = name + index;
                    if (!hierarchyNames.contains(newName) && !methodNames.contains(newName)
                            && !forbiddenNames.contains(newName)) {
                        methodNames.add(newName);
                        return newName;
                    }
                    index++;
                }
            }
        });
    }

    public void ensureUniqueClassMemberNames(CompilationUnit unit) {
        unit.accept(new RecursiveASTVisitor<Void>() {
            private final Set<ClassMember> untouchableMethods = Sets.newHashSet();
            private final Map<String, ClassMember> usedClassMembers = Maps.newHashMap();
            private final Set<String> superNames = Sets.newHashSet();
            private final Map<String, List<IMethodBinding>> superMembers = Maps.newHashMap();

            @Override
            public Void visitClassDeclaration(ClassDeclaration node) {
                untouchableMethods.clear();
                usedClassMembers.clear();
                superNames.clear();
                superMembers.clear();
                // fill "static" methods from super classes
                {
                    org.eclipse.jdt.core.dom.ITypeBinding binding = getNodeTypeBinding(node);
                    if (binding != null) {
                        binding = binding.getSuperclass();
                        while (binding != null) {
                            for (org.eclipse.jdt.core.dom.IMethodBinding method : binding.getDeclaredMethods()) {
                                if (org.eclipse.jdt.core.dom.Modifier.isStatic(method.getModifiers())) {
                                    usedClassMembers.put(method.getName(), null);
                                } else {
                                    addSuperMember(method);
                                }
                            }
                            binding = binding.getSuperclass();
                        }
                    }
                }
                // fill "untouchable" methods
                for (ClassMember member : node.getMembers()) {
                    if (member instanceof MethodDeclaration) {
                        MethodDeclaration methodDeclaration = (MethodDeclaration) member;
                        Object binding = nodeToBinding.get(member);
                        if (JavaUtils.isMethodDeclaredInClass(binding, "java.lang.Object")) {
                            untouchableMethods.add(member);
                            usedClassMembers.put(methodDeclaration.getName().getName(), null);
                        }
                    }
                }
                // ensure unique method names (and prefer to keep method name over field name)
                for (ClassMember member : node.getMembers()) {
                    if (member instanceof MethodDeclaration) {
                        MethodDeclaration method = (MethodDeclaration) member;
                        // untouchable
                        if (untouchableMethods.contains(method)) {
                            continue;
                        }
                        // getter/setter can share name
                        {
                            ClassMember otherMember = usedClassMembers.get(method.getName().getName());
                            if (otherMember instanceof MethodDeclaration) {
                                MethodDeclaration otherMethod = (MethodDeclaration) otherMember;
                                if (method.isGetter() && otherMethod.isSetter()
                                        || method.isSetter() && otherMethod.isGetter()) {
                                    continue;
                                }
                            }
                        }
                        // may be overloaded method
                        {
                            Object binding = nodeToBinding.get(method);
                            if (binding instanceof IMethodBinding) {
                                IMethodBinding methodBinding = (IMethodBinding) binding;
                                String name = methodBinding.getName();
                                if (superNames.contains(name)) {
                                    IMethodBinding over = Bindings.findOverriddenMethod(methodBinding, false);
                                    if (over == null) {
                                        usedClassMembers.put(name, null);
                                    }
                                }
                            }
                        }
                        // ensure unique name
                        ensureUniqueName(method.getName(), method);
                    }
                }
                // ensure unique field names (if name is already used be method)
                for (ClassMember member : node.getMembers()) {
                    if (member instanceof FieldDeclaration) {
                        FieldDeclaration fieldDeclaration = (FieldDeclaration) member;
                        for (VariableDeclaration field : fieldDeclaration.getFields().getVariables()) {
                            ensureUniqueName(field.getName(), fieldDeclaration);
                        }
                    }
                }
                // no recursion
                return null;
            }

            private void addSuperMember(IMethodBinding binding) {
                String name = binding.getName();
                superNames.add(name);
                List<IMethodBinding> members = superMembers.get(name);
                if (members == null) {
                    members = Lists.newArrayList();
                    superMembers.put(name, members);
                }
                members.add(binding);
            }

            private void ensureUniqueName(Identifier declarationName, ClassMember member) {
                if (declarationName instanceof SimpleIdentifier) {
                    SimpleIdentifier declarationIdentifier = (SimpleIdentifier) declarationName;
                    String name = declarationIdentifier.getName();
                    if (!isUniqueClassMemberName(name)) {
                        String newName = generateUniqueName(name);
                        // rename binding
                        if (!newName.equals(name)) {
                            renameIdentifier(declarationIdentifier, newName);
                            name = newName;
                        }
                    }
                    // remember that name is used
                    usedClassMembers.put(name, member);
                }
            }

            private String generateUniqueName(String name) {
                if (!isGloballyUniqueClassMemberName(name)) {
                    int index = 2;
                    while (true) {
                        String newName = name + index;
                        if (isGloballyUniqueClassMemberName(newName)) {
                            usedNames.add(newName);
                            return newName;
                        }
                        index++;
                    }
                }
                return name;
            }

            private boolean isGloballyUniqueClassMemberName(String name) {
                return isUniqueClassMemberName(name) && !usedNames.contains(name);
            }

            private boolean isUniqueClassMemberName(String name) {
                return !forbiddenNames.contains(name) && !usedClassMembers.containsKey(name);
            }
        });
    }

    /**
     * @return the artificial {@link ClassDeclaration}created for Java creation of anonymous class
     *         declaration.
     */
    public ClassDeclaration getAnonymousDeclaration(InstanceCreationExpression creation) {
        return anonymousDeclarations.get(creation);
    }

    public Map<File, List<CompilationUnitMember>> getFileToMembers() {
        return fileToMembers;
    }

    /**
     * We rename {@link SimpleIdentifier}s, but sometimes we need to know original name.
     */
    public String getIdentifierOriginalName(SimpleIdentifier identifier) {
        String name = identifierToName.get(identifier);
        if (name == null) {
            name = identifier.getName();
        }
        return name;
    }

    public Map<CompilationUnitMember, File> getMemberToFile() {
        return memberToFile;
    }

    /**
     * @return some Java binding for the given Dart {@link ASTNode}.
     */
    public Object getNodeBinding(ASTNode node) {
        return nodeToBinding.get(node);
    }

    /**
     * @return some Java {@link ITypeBinding} for the given Dart {@link ASTNode}.
     */
    public ITypeBinding getNodeTypeBinding(ASTNode node) {
        return nodeToTypeBinding.get(node);
    }

    /**
     * @return the Dart {@link ClassMember} which are generated from private Java elements.
     */
    public Set<ClassMember> getPrivateClassMembers() {
        return privateClassMembers;
    }

    /**
     * @return all references (actual references and declarations)
     */
    public List<SimpleIdentifier> getReferences(SimpleIdentifier target) {
        Object binding = nodeToBinding.get(target);
        List<SimpleIdentifier> references = bindingToIdentifiers.get(binding);
        return references != null ? references : Lists.<SimpleIdentifier>newArrayList();
    }

    public void renameConstructor(ConstructorDeclaration node, String name) {
        IMethodBinding binding = constructorToBinding.get(node);
        //
        SimpleIdentifier newIdentifier;
        if (name == null) {
            newIdentifier = null;
        } else {
            newIdentifier = identifier(name);
        }
        // rename constructor
        node.setName(newIdentifier);
        // update references
        ConstructorDescription constructorDescription = bindingToConstructor.get(binding);
        if (constructorDescription != null) {
            // set name in InstanceCreationExpression
            {
                List<InstanceCreationExpression> creations = constructorDescription.instanceCreations;
                for (InstanceCreationExpression creation : creations) {
                    creation.getConstructorName().setName(newIdentifier);
                }
            }
            // set name in SuperConstructorInvocation
            {
                List<SuperConstructorInvocation> invocations = constructorDescription.superInvocations;
                for (SuperConstructorInvocation invocation : invocations) {
                    invocation.setConstructorName(newIdentifier);
                }
            }
            // set name in invocation of implementation
            {
                List<SimpleIdentifier> invocations = constructorDescription.implInvocations;
                for (SimpleIdentifier identifier : invocations) {
                    identifier.setToken(token(TokenType.IDENTIFIER, constructorDescription.implName));
                }
            }
        }
    }

    /**
     * Sets the {@link SimpleIdentifier} name and updates all references.
     */
    public void renameIdentifier(SimpleIdentifier declarationIdentifier, String newName) {
        // move identifiers to the new signature
        Object binding = nodeToBinding.get(declarationIdentifier);
        List<SimpleIdentifier> identifiers = bindingToIdentifiers.get(binding);
        // update identifiers to the new name
        for (SimpleIdentifier identifier : identifiers) {
            identifier.setToken(token(TokenType.IDENTIFIER, newName));
        }
    }

    public CompilationUnit translate() throws Exception {
        // sort source files
        Collections.sort(sourceFiles);
        // perform syntax translation
        translateSyntax();
        // perform configured renames
        for (Entry<String, String> renameEntry : renameMap.entrySet()) {
            String signature = renameEntry.getKey();
            Object binding = signatureToBinding.get(signature);
            List<SimpleIdentifier> identifiers = bindingToIdentifiers.get(binding);
            if (identifiers != null) {
                String newName = renameEntry.getValue();
                for (SimpleIdentifier identifier : identifiers) {
                    identifier.setToken(token(TokenType.IDENTIFIER, newName));
                }
            }
        }
        // run processors
        {
            replaceInnerClassReferences(dartUniverse);
            unwrapVarArgIfAlreadyArray(dartUniverse);
            ensureFieldInitializers(dartUniverse);
            dontUseThisInFieldInitializers(dartUniverse);
            ensureUniqueClassMemberNames(dartUniverse);
            ensureNoVariableNameReferenceFromInitializer(dartUniverse);
            ensureMethodParameterDoesNotHide(dartUniverse);
            renameConstructors(dartUniverse);
        }
        // done
        return dartUniverse;
    }

    /**
     * @return the "technical" name for the top-level Dart class for Java anonymous class.
     */
    int generateTechnicalAnonymousClassIndex() {
        return technicalAnonymousClassIndex++;
    }

    /**
     * @return the "technical" name for the Dart constructor.
     */
    String generateTechnicalConstructorName() {
        return "jtd_constructor_" + technicalConstructorIndex++;
    }

    /**
     * @return the "technical" name for the top-level Dart class for Java inner class.
     */
    String generateTechnicalInnerClassName() {
        return "JtdClass_" + technicalInnerClassIndex++;
    }

    /**
     * @return the not <code>null</code> {@link ConstructorDescription}, may be just added.
     */
    ConstructorDescription getConstructorDescription(IMethodBinding binding) {
        ConstructorDescription description = bindingToConstructor.get(binding);
        if (description == null) {
            description = new ConstructorDescription(binding);
            bindingToConstructor.put(binding, description);
        }
        return description;
    }

    /**
     * Remembers artificial {@link ClassDeclaration} created for Java creation of anonymous class
     * declaration.
     */
    void putAnonymousDeclaration(InstanceCreationExpression creation, ClassDeclaration declaration) {
        if (declaration != null) {
            anonymousDeclarations.put(creation, declaration);
        }
    }

    /**
     * Remembers that given {@link SimpleIdentifier} used as name of the named
     * {@link ConstructorDeclaration} is reference to the given Java signature.
     */
    void putConstructorBinding(ConstructorDeclaration node, IMethodBinding binding) {
        constructorToBinding.put(node, binding);
    }

    /**
     * Remembers that given {@link SimpleIdentifier} is the name of inner class and all references to
     * it should be renamed.
     */
    void putInnerClassName(SimpleIdentifier identifier) {
        innerClassNames.add(identifier);
    }

    /**
     * Remembers some Java binding for the given Dart {@link ASTNode}.
     */
    void putNodeBinding(ASTNode node, Object binding) {
        nodeToBinding.put(node, binding);
    }

    /**
     * Remembers Java {@link ITypeBinding} for the given Dart {@link ASTNode}.
     */
    void putNodeTypeBinding(ASTNode node, ITypeBinding binding) {
        nodeToTypeBinding.put(node, binding);
    }

    /**
     * Remembers that given {@link ClassMember} was created for private Java element.
     */
    void putPrivateClassMember(ClassMember member) {
        privateClassMembers.add(member);
    }

    /**
     * Remembers that "identifier" is reference to the given Java binding.
     */
    void putReference(SimpleIdentifier identifier, Object binding, String bindingSignature) {
        if (binding != null) {
            signatureToBinding.put(bindingSignature, binding);
            identifierToName.put(identifier, identifier.getName());
            // remember binding for reference
            nodeToBinding.put(identifier, binding);
            // add reference to binding
            List<SimpleIdentifier> identifiers = bindingToIdentifiers.get(binding);
            if (identifiers == null) {
                identifiers = Lists.newLinkedList();
                bindingToIdentifiers.put(binding, identifiers);
            }
            identifiers.add(identifier);
        }
        // remember global name
        usedNames.add(identifier.getName());
    }

    private void dontUseThisInFieldInitializers(CompilationUnit unit) {
        unit.accept(new RecursiveASTVisitor<Void>() {
            @Override
            public Void visitClassDeclaration(ClassDeclaration node) {
                processClass(node);
                return super.visitClassDeclaration(node);
            }

            private void addAssignmentsToBlock(Block block, Map<SimpleIdentifier, Expression> initializers) {
                int index = 0;
                for (Entry<SimpleIdentifier, Expression> entry : initializers.entrySet()) {
                    block.getStatements().add(index++, expressionStatement(assignmentExpression(
                            propertyAccess(thisExpression(), entry.getKey()), TokenType.EQ, entry.getValue())));
                }
            }

            private void processClass(final ClassDeclaration classDeclaration) {
                final Map<SimpleIdentifier, Expression> thisInitializers = Maps.newLinkedHashMap();
                // find field initializers which use "this"
                classDeclaration.accept(new RecursiveASTVisitor<Void>() {
                    @Override
                    public Void visitVariableDeclaration(VariableDeclaration node) {
                        if (node.getParent().getParent() instanceof FieldDeclaration) {
                            if (hasThisExpression(node)) {
                                thisInitializers.put(node.getName(), node.getInitializer());
                                node.setInitializer(null);
                            }
                        }
                        return super.visitVariableDeclaration(node);
                    }

                    private boolean hasThisExpression(ASTNode node) {
                        final AtomicBoolean result = new AtomicBoolean();
                        node.accept(new GeneralizingASTVisitor<Void>() {
                            @Override
                            public Void visitThisExpression(ThisExpression node) {
                                result.set(true);
                                return super.visitThisExpression(node);
                            }
                        });
                        return result.get();
                    }
                });
                // add field assignment for each "this" field initializer
                if (thisInitializers.isEmpty()) {
                    return;
                }
                ConstructorDeclaration singleConstructor = null;
                boolean hasImpl = false;
                for (ClassMember classMember : classDeclaration.getMembers()) {
                    if (classMember instanceof ConstructorDeclaration) {
                        singleConstructor = (ConstructorDeclaration) classMember;
                    }
                    if (classMember instanceof MethodDeclaration) {
                        MethodDeclaration method = (MethodDeclaration) classMember;
                        String methodName = method.getName().getName();
                        if (methodName.startsWith("_jtd_constructor_") && methodName.endsWith("_impl")) {
                            hasImpl = true;
                            Block block = ((BlockFunctionBody) method.getBody()).getBlock();
                            addAssignmentsToBlock(block, thisInitializers);
                        }
                    }
                }
                // no "_impl", add assignments to the single constructor
                if (!hasImpl && singleConstructor != null) {
                    Block block = ((BlockFunctionBody) singleConstructor.getBody()).getBlock();
                    addAssignmentsToBlock(block, thisInitializers);
                }
                // no "_impl", generate default constructor
                if (singleConstructor == null) {
                    Block block = block();
                    addAssignmentsToBlock(block, thisInitializers);
                    ConstructorDeclaration constructor = constructorDeclaration(classDeclaration.getName(), null,
                            formalParameterList(), null, blockFunctionBody(block));
                    classDeclaration.getMembers().add(constructor);
                }
            }
        });
    }

    private void ensureFieldInitializers(CompilationUnit unit) {
        unit.accept(new RecursiveASTVisitor<Void>() {
            @Override
            public Void visitFieldDeclaration(FieldDeclaration node) {
                VariableDeclarationList fields = node.getFields();
                // final fields should be initialized as part of the constructor 
                // or already have an initializer
                if (fields.getKeyword() instanceof KeywordToken) {
                    KeywordToken token = (KeywordToken) fields.getKeyword();
                    if (token.getKeyword() == Keyword.FINAL) {
                        return super.visitFieldDeclaration(node);
                    }
                }
                String typeName = fields.getType().toString();
                for (VariableDeclaration variable : fields.getVariables()) {
                    if (variable.getInitializer() == null) {
                        Expression initializer = SyntaxTranslator.getPrimitiveTypeDefaultValue(typeName);
                        if (initializer != null) {
                            variable.setInitializer(initializer);
                        }
                    }
                }
                return super.visitFieldDeclaration(node);
            }
        });
    }

    /**
     * @return the name of member declared in enclosing {@link ClassDeclaration} and its super
     *         classes.
     */
    private Set<String> getSuperMembersNames(ASTNode node) {
        Set<String> hierarchyNames = Sets.newHashSet();
        ClassDeclaration classDeclaration = node.getAncestor(ClassDeclaration.class);
        org.eclipse.jdt.core.dom.ITypeBinding binding = getNodeTypeBinding(classDeclaration);
        if (binding != null) {
            binding = binding.getSuperclass();
            while (binding != null) {
                for (org.eclipse.jdt.core.dom.IVariableBinding field : binding.getDeclaredFields()) {
                    hierarchyNames.add(field.getName());
                }
                for (org.eclipse.jdt.core.dom.IMethodBinding method : binding.getDeclaredMethods()) {
                    hierarchyNames.add(method.getName());
                }
                binding = binding.getSuperclass();
            }
        }
        return hierarchyNames;
    }

    /**
     * @return the Java AST of the given Java {@link File} in context of {@link #sourceFolders}.
     */
    private Map<File, CompilationUnit> parseJavaFiles(final List<File> javaFiles) throws Exception {
        String paths[] = new String[javaFiles.size()];
        final Map<String, File> pathToFile = Maps.newHashMap();
        for (int i = 0; i < javaFiles.size(); i++) {
            File javaFile = javaFiles.get(i);
            String javaPath = javaFile.getAbsolutePath();
            paths[i] = javaPath;
            pathToFile.put(javaPath, javaFile);
        }
        // prepare Java parser
        ASTParser parser = ASTParser.newParser(AST.JLS4);
        {
            String[] classpathEntries = new String[classpathFiles.size()];
            for (int i = 0; i < classpathFiles.size(); i++) {
                classpathEntries[i] = classpathFiles.get(i).getAbsolutePath();
            }
            String[] sourceEntries = new String[sourceFolders.size()];
            for (int i = 0; i < sourceFolders.size(); i++) {
                sourceEntries[i] = sourceFolders.get(i).getAbsolutePath();
            }
            parser.setEnvironment(classpathEntries, sourceEntries, null, true);
        }
        parser.setResolveBindings(true);
        parser.setCompilerOptions(ImmutableMap.of(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_5,
                JavaCore.COMPILER_DOC_COMMENT_SUPPORT, JavaCore.ENABLED));
        // do parse
        final Map<File, CompilationUnit> units = Maps.newLinkedHashMap();
        parser.createASTs(paths, null, ArrayUtils.EMPTY_STRING_ARRAY, new FileASTRequestor() {
            @Override
            public void acceptAST(String sourceFilePath, org.eclipse.jdt.core.dom.CompilationUnit javaUnit) {
                File astFile = pathToFile.get(sourceFilePath);
                CompilationUnit dartUnit = SyntaxTranslator.translate(Context.this, javaUnit);
                units.put(astFile, dartUnit);
            }
        }, null);
        return units;
    }

    private void renameConstructors(CompilationUnit unit) {
        unit.accept(new RecursiveASTVisitor<Void>() {
            private final Set<String> memberNamesInClass = Sets.newHashSet();
            private int numConstructors;

            @Override
            public Void visitClassDeclaration(ClassDeclaration node) {
                memberNamesInClass.clear();
                numConstructors = 0;
                NodeList<ClassMember> members = node.getMembers();
                for (ClassMember member : members) {
                    if (member instanceof ConstructorDeclaration) {
                        numConstructors++;
                    }
                    if (member instanceof MethodDeclaration) {
                        String name = ((MethodDeclaration) member).getName().getName();
                        memberNamesInClass.add(name);
                    }
                    if (member instanceof FieldDeclaration) {
                        FieldDeclaration fieldDeclaration = (FieldDeclaration) member;
                        NodeList<VariableDeclaration> variables = fieldDeclaration.getFields().getVariables();
                        for (VariableDeclaration variable : variables) {
                            String name = variable.getName().getName();
                            memberNamesInClass.add(name);
                        }
                    }
                }
                return super.visitClassDeclaration(node);
            }

            @Override
            public Void visitConstructorDeclaration(ConstructorDeclaration node) {
                IMethodBinding binding = constructorToBinding.get(node);
                String bindingSignature = JavaUtils.getJdtSignature(binding);
                // prepare name
                String name = renameMap.get(bindingSignature);
                if (name == null) {
                    if (numConstructors == 1 || node.getParameters().getParameters().isEmpty()) {
                        // don't set name, use unnamed constructor
                    } else {
                        int index = 1;
                        while (true) {
                            name = "con" + index++;
                            if (!memberNamesInClass.contains(name)) {
                                break;
                            }
                        }
                    }
                }
                memberNamesInClass.add(name);
                // apply name
                renameConstructor(node, name);
                // continue
                return super.visitConstructorDeclaration(node);
            }
        });
    }

    private void replaceInnerClassReferences(CompilationUnit unit) {
        for (SimpleIdentifier identifier : innerClassNames) {
            renameIdentifier(identifier, identifier.getName());
        }
    }

    /**
     * Translate {@link #sourceFiles} into Dart AST in {@link #dartUnits}.
     */
    private void translateSyntax() throws Exception {
        Map<File, CompilationUnit> unitMap = parseJavaFiles(sourceFiles);
        for (Entry<File, CompilationUnit> entry : unitMap.entrySet()) {
            File javaFile = entry.getKey();
            CompilationUnit dartUnit = entry.getValue();
            List<CompilationUnitMember> dartDeclarations = dartUnit.getDeclarations();
            // add to the Dart universe
            dartUniverse.getDeclarations().addAll(dartDeclarations);
            // add to the CompilationUnitMember map
            for (CompilationUnitMember member : dartDeclarations) {
                memberToFile.put(member, javaFile);
            }
            // add to the File map
            {
                List<CompilationUnitMember> fileMembers = fileToMembers.get(javaFile);
                if (fileMembers == null) {
                    fileMembers = Lists.newArrayList();
                    fileToMembers.put(javaFile, fileMembers);
                }
                fileMembers.addAll(dartDeclarations);
            }
        }
    }

    private void unwrapVarArgIfAlreadyArray(CompilationUnit unit) {
        unit.accept(new RecursiveASTVisitor<Void>() {
            @Override
            public Void visitInstanceCreationExpression(InstanceCreationExpression node) {
                process(node, node.getArgumentList());
                return super.visitInstanceCreationExpression(node);
            }

            @Override
            public Void visitMethodInvocation(MethodInvocation node) {
                process(node, node.getArgumentList());
                return super.visitMethodInvocation(node);
            }

            @Override
            public Void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
                process(node, node.getArgumentList());
                return super.visitSuperConstructorInvocation(node);
            }

            private void process(ASTNode node, ArgumentList argumentList) {
                Object binding = nodeToBinding.get(node);
                if (binding instanceof IMethodBinding) {
                    IMethodBinding methodBinding = (IMethodBinding) binding;
                    if (methodBinding.isVarargs()) {
                        List<Expression> args = argumentList.getArguments();
                        if (!args.isEmpty() && args.get(args.size() - 1) instanceof ListLiteral) {
                            ListLiteral listLiteral = (ListLiteral) args.get(args.size() - 1);
                            List<Expression> elements = listLiteral.getElements();
                            if (elements.size() == 1) {
                                Expression element = elements.get(0);
                                if (nodeToTypeBinding.get(element) instanceof ITypeBinding) {
                                    ITypeBinding elementTypeBinding = nodeToTypeBinding.get(element);
                                    if (elementTypeBinding.isArray()) {
                                        args.set(args.size() - 1, element);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        });
    }
}