/*
* 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.common.io.Files;
import com.google.dart.engine.ast.ArgumentList;
import com.google.dart.engine.ast.AstNode;
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.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.RedirectingConstructorInvocation;
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.ast.visitor.UnifyingAstVisitor;
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.processor.ConstructorSemanticProcessor;
import com.google.dart.java2dart.processor.LocalVariablesSemanticProcessor;
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.Charsets;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
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.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
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.
*/
public static class ConstructorDescription {
final IMethodBinding binding;
public final List<RedirectingConstructorInvocation> redirectingInvocations = Lists.newArrayList();
public final List<SuperConstructorInvocation> superInvocations = Lists.newArrayList();
public final List<InstanceCreationExpression> instanceCreations = Lists.newArrayList();
public boolean isEnum;
public boolean insertEnclosingTypeRef;
String declName;
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 Set<String> notPropertySet = Sets.newHashSet();
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
public static final Set<String> FORBIDDEN_NAMES = 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, IBinding> 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();
private final Map<AstNode, List<ParsedAnnotation>> nodeAnnotations = Maps.newHashMap();
// 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()) {
FORBIDDEN_NAMES.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 the method with given signature should not be converted to getter/setter.
*/
public void addNotProperty(String signature) {
notPropertySet.add(signature);
}
/**
* Specifies that method 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);
}
public void applyLocalVariableSemanticChanges(CompilationUnit unit) {
new LocalVariablesSemanticProcessor(this).process(unit);
}
/**
* @return {@code true} if the method with the given signature (which could be made getter or
* setter) is allowed to be converted into getter/setter.
*/
public boolean canMakeProperty(SimpleIdentifier identifier) {
IBinding binding = getNodeBinding(identifier);
String signature = JavaUtils.getJdtSignature(binding);
return !notPropertySet.contains(signature);
}
/**
* Clears information about the given {@link AstNode} and its children.
*/
public void clearNodes(AstNode node) {
if (node != null) {
node.accept(new UnifyingAstVisitor<Void>() {
@Override
public Void visitNode(AstNode node) {
clearNode(node);
return super.visitNode(node);
}
});
}
}
public void ensureUniqueClassMemberNames() {
dartUniverse.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 !FORBIDDEN_NAMES.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);
}
/**
* @return some Java binding for the given Dart {@link ConstructorDeclaration}.
*/
public IMethodBinding getConstructorBinding(ConstructorDeclaration node) {
return constructorToBinding.get(node);
}
/**
* @return the not <code>null</code> {@link ConstructorDescription}, may be just added.
*/
public ConstructorDescription getConstructorDescription(ConstructorDeclaration node) {
IMethodBinding binding = getConstructorBinding(node);
return getConstructorDescription(binding);
}
/**
* @return the not <code>null</code> {@link ConstructorDescription}, may be just added.
*/
public ConstructorDescription getConstructorDescription(IMethodBinding binding) {
binding = (IMethodBinding) Bindings.getDeclaration(binding);
ConstructorDescription description = bindingToConstructor.get(binding);
if (description == null) {
description = new ConstructorDescription(binding);
bindingToConstructor.put(binding, description);
}
return description;
}
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 List<MethodInvocation> getInvocations(MethodDeclaration method) {
List<MethodInvocation> invocations = Lists.newArrayList();
SimpleIdentifier methodName = method.getName();
List<SimpleIdentifier> references = getReferences(methodName);
for (SimpleIdentifier reference : references) {
if (reference.getParent() instanceof MethodInvocation) {
MethodInvocation invocation = (MethodInvocation) reference.getParent();
if (invocation.getMethodName() == reference) {
invocations.add(invocation);
}
}
}
return invocations;
}
public Map<CompilationUnitMember, File> getMemberToFile() {
return memberToFile;
}
/**
* Returns all node annotations. Used for testing.
*/
public Map<AstNode, List<ParsedAnnotation>> getNodeAnnotations() {
return nodeAnnotations;
}
/**
* Returns {@link ParsedAnnotation}s object for the Java annotation specified on the Java node of
* the given {@link AstNode}, maybe {@code null}.
*/
public List<ParsedAnnotation> getNodeAnnotations(AstNode node) {
return nodeAnnotations.get(node);
}
/**
* @return some Java binding for the given Dart {@link AstNode}.
*/
public IBinding 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();
}
/**
* @return the name of member declared in enclosing {@link ClassDeclaration} and its super
* classes.
*/
public 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;
}
public boolean isFieldBinding(AstNode node) {
IBinding binding = getNodeBinding(node);
if (binding instanceof IVariableBinding) {
return ((IVariableBinding) binding).isField();
}
return false;
}
public boolean isMethodBinding(AstNode node) {
IBinding binding = getNodeBinding(node);
return binding instanceof IMethodBinding;
}
/**
* Remembers that "identifier" is reference to the given Java binding.
*/
public void putReference(SimpleIdentifier identifier, IBinding binding, String bindingSignature) {
String name = identifier.getName();
if (binding != null) {
signatureToBinding.put(bindingSignature, binding);
identifierToName.put(identifier, name);
// 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(name);
}
/**
* Removes recorded identifier reference.
*/
public void removeReference(SimpleIdentifier identifier) {
IBinding binding = getNodeBinding(identifier);
List<SimpleIdentifier> identifiers = bindingToIdentifiers.get(binding);
if (identifiers != null) {
identifiers.remove(identifier);
}
}
/**
* Specifies that the given {@link File} should not be translated.
*/
public void removeSourceFile(File file) {
Assert.isLegal(file.exists(), "File '" + file + "' does not exist.");
file = file.getAbsoluteFile();
sourceFiles.remove(file);
}
/**
* Specifies that all files in given folder should not be translated.
*/
public void removeSourceFiles(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.removeAll(folderFiles);
}
public void renameConstructor(ConstructorDeclaration node, String name) {
IMethodBinding binding = getConstructorBinding(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 RedirectingConstructorInvocation
{
List<RedirectingConstructorInvocation> invocations = constructorDescription.redirectingInvocations;
for (RedirectingConstructorInvocation invocation : invocations) {
invocation.setConstructorName(newIdentifier);
}
}
// set name in SuperConstructorInvocation
{
List<SuperConstructorInvocation> invocations = constructorDescription.superInvocations;
for (SuperConstructorInvocation invocation : invocations) {
invocation.setConstructorName(newIdentifier);
}
}
// set name in InstanceCreationExpression
{
List<InstanceCreationExpression> creations = constructorDescription.instanceCreations;
for (InstanceCreationExpression creation : creations) {
creation.getConstructorName().setName(newIdentifier);
}
}
}
}
/**
* 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);
if (identifiers == null) {
return;
}
// 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);
renameAnonymousClassDeclarations();
renamePrivateClassMembers();
new ConstructorSemanticProcessor(this).process(dartUniverse);
insertEnclosingTypeForInstanceCreationArguments(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++;
}
/**
* 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) {
binding = (IMethodBinding) Bindings.getDeclaration(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 the parsed annotation for the given node.
*/
void putNodeAnnotation(AstNode node, ParsedAnnotation parsedAnnotation) {
List<ParsedAnnotation> annotations = nodeAnnotations.get(node);
if (annotations == null) {
annotations = Lists.newArrayList();
nodeAnnotations.put(node, annotations);
}
annotations.add(parsedAnnotation);
}
/**
* Remembers some Java binding for the given Dart {@link AstNode}.
*/
void putNodeBinding(AstNode node, IBinding 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);
}
/**
* Clears information about the given {@link AstNode}.
*/
private void clearNode(AstNode node) {
if (node instanceof SimpleIdentifier) {
removeReference((SimpleIdentifier) node);
}
nodeToBinding.remove(node);
nodeToTypeBinding.remove(node);
}
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;
}
boolean hasConstructor = false;
for (ClassMember classMember : classDeclaration.getMembers()) {
if (classMember instanceof ConstructorDeclaration) {
ConstructorDeclaration constructor = (ConstructorDeclaration) classMember;
hasConstructor = true;
Block block = ((BlockFunctionBody) constructor.getBody()).getBlock();
addAssignmentsToBlock(block, thisInitializers);
}
}
// no constructors, generate default constructor
if (!hasConstructor) {
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);
}
});
}
private void insertEnclosingTypeForInstanceCreationArguments(CompilationUnit unit) {
unit.accept(new RecursiveAstVisitor<Void>() {
@Override
public Void visitInstanceCreationExpression(InstanceCreationExpression node) {
IMethodBinding binding = (IMethodBinding) getNodeBinding(node);
ConstructorDescription constructorDescription = getConstructorDescription(binding);
if (constructorDescription.insertEnclosingTypeRef) {
node.getArgumentList().getArguments().add(0, thisExpression());
}
return super.visitInstanceCreationExpression(node);
}
});
}
/**
* @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) {
// for (IProblem problem : javaUnit.getProblems()) {
// System.out.println(problem);
// }
try {
File astFile = pathToFile.get(sourceFilePath);
String javaSource = Files.toString(astFile, Charsets.UTF_8);
CompilationUnit dartUnit = SyntaxTranslator.translate(Context.this, javaUnit, javaSource);
units.put(astFile, dartUnit);
} catch (Throwable e) {
throw new Error(e);
}
}
},
null);
return units;
}
/**
* Improves names for anonymous {@link ClassDeclaration}s.
*/
private void renameAnonymousClassDeclarations() {
// prepare unused top-level names
Set<String> usedTopNames = Sets.newHashSet();
for (CompilationUnitMember unitMember : dartUniverse.getDeclarations()) {
if (unitMember instanceof ClassDeclaration) {
ClassDeclaration classDeclaration = (ClassDeclaration) unitMember;
String name = classDeclaration.getName().getName();
usedTopNames.add(name);
}
}
// rename anonymous types
for (Entry<InstanceCreationExpression, ClassDeclaration> entry : anonymousDeclarations.entrySet()) {
// prepare enclosing information
InstanceCreationExpression creation = entry.getKey();
ClassDeclaration enclosingClass = creation.getAncestor(ClassDeclaration.class);
//
SimpleIdentifier enclosingClassMemberName = null;
if (enclosingClassMemberName == null) {
MethodDeclaration enclosingMethod = creation.getAncestor(MethodDeclaration.class);
if (enclosingMethod != null) {
enclosingClassMemberName = enclosingMethod.getName();
}
}
if (enclosingClassMemberName == null) {
VariableDeclaration enclosingField = creation.getAncestor(VariableDeclaration.class);
if (enclosingField != null) {
enclosingClassMemberName = enclosingField.getName();
}
}
// prepare new name for anonymous class
ClassDeclaration classDeclaration = entry.getValue();
SimpleIdentifier nameNode = classDeclaration.getName();
String name = nameNode.getName();
name = StringUtils.substringBeforeLast(name, "_");
{
String enclosingClassName = enclosingClass.getName().getName();
if (!enclosingClassName.equals(name)) {
name = name + "_" + enclosingClassName;
}
}
if (enclosingClassMemberName != null) {
name += "_" + enclosingClassMemberName.getName();
}
// ensure unique name
if (!usedTopNames.add(name)) {
int index = 2;
while (true) {
String newName = name + "_" + index++;
if (usedTopNames.add(newName)) {
name = newName;
break;
}
}
}
// rename
renameIdentifier(nameNode, name);
}
}
private void renamePrivateClassMembers() {
for (ClassMember member : privateClassMembers) {
if (member instanceof FieldDeclaration) {
FieldDeclaration fieldDeclaration = (FieldDeclaration) member;
NodeList<VariableDeclaration> variables = fieldDeclaration.getFields().getVariables();
for (VariableDeclaration field : variables) {
SimpleIdentifier nameNode = field.getName();
String name = nameNode.getName();
renameIdentifier(nameNode, "_" + name);
}
}
if (member instanceof MethodDeclaration) {
MethodDeclaration methodDeclaration = (MethodDeclaration) member;
SimpleIdentifier nameNode = methodDeclaration.getName();
String name = nameNode.getName();
renameIdentifier(nameNode, "_" + name);
}
}
}
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);
}
}
}
}
}
}
}
});
}
}