/* * 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.engine; import com.google.common.base.Joiner; 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.Block; 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.Expression; import com.google.dart.engine.ast.ExpressionStatement; import com.google.dart.engine.ast.FormalParameter; 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.PrefixExpression; import com.google.dart.engine.ast.PropertyAccess; import com.google.dart.engine.ast.SimpleFormalParameter; import com.google.dart.engine.ast.SimpleIdentifier; import com.google.dart.engine.ast.SimpleStringLiteral; import com.google.dart.engine.ast.Statement; 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.VariableDeclarationStatement; 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.TokenType; import com.google.dart.java2dart.Context; import com.google.dart.java2dart.processor.SemanticProcessor; import com.google.dart.java2dart.util.JavaUtils; import static com.google.dart.java2dart.util.AstFactory.assignmentExpression; import static com.google.dart.java2dart.util.AstFactory.binaryExpression; import static com.google.dart.java2dart.util.AstFactory.blockFunctionBody; import static com.google.dart.java2dart.util.AstFactory.expressionFunctionBody; import static com.google.dart.java2dart.util.AstFactory.expressionStatement; import static com.google.dart.java2dart.util.AstFactory.fieldDeclaration; import static com.google.dart.java2dart.util.AstFactory.formalParameterList; import static com.google.dart.java2dart.util.AstFactory.functionDeclaration; import static com.google.dart.java2dart.util.AstFactory.functionExpression; import static com.google.dart.java2dart.util.AstFactory.identifier; import static com.google.dart.java2dart.util.AstFactory.integer; import static com.google.dart.java2dart.util.AstFactory.methodDeclaration; import static com.google.dart.java2dart.util.AstFactory.methodInvocation; import static com.google.dart.java2dart.util.AstFactory.parenthesizedExpression; import static com.google.dart.java2dart.util.AstFactory.prefixExpression; import static com.google.dart.java2dart.util.AstFactory.propertyAccess; import static com.google.dart.java2dart.util.AstFactory.simpleFormalParameter; import static com.google.dart.java2dart.util.AstFactory.typeName; import static com.google.dart.java2dart.util.AstFactory.variableDeclaration; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import java.io.PrintWriter; import java.util.List; import java.util.Map; import java.util.Set; /** * {@link SemanticProcessor} for Engine. */ public class EngineSemanticProcessor extends SemanticProcessor { /** * Adds "main" function with given {@link Statement}s. */ public static void addMain(CompilationUnit unit, List<Statement> statements) { unit.getDeclarations().add( functionDeclaration( null, null, "main", functionExpression(formalParameterList(), blockFunctionBody(statements)))); } /** * Gather all <code>TestSuite.addTestSuite</code> into "main" function. */ public static boolean gatherTestSuites(final List<Statement> mainStatements, CompilationUnitMember node) { if (node instanceof ClassDeclaration) { ClassDeclaration classDeclaration = (ClassDeclaration) node; if (classDeclaration.getName().getName().equals("TestAll")) { node.accept(new RecursiveAstVisitor<Void>() { @Override public Void visitMethodInvocation(MethodInvocation node) { if (node.getMethodName().getName().equals("addTestSuite")) { mainStatements.add(expressionStatement(methodInvocation( node.getArgumentList().getArguments().get(0), "dartSuite"))); } return super.visitMethodInvocation(node); } }); return true; } } return false; } /** * Generates "invokeParserMethodImpl" and supporting Dart code. */ public static void replaceReflection_generateParserTable(final Context context, final PrintWriter pw, AstNode node) { node.accept(new RecursiveAstVisitor<Void>() { @Override public Void visitClassDeclaration(ClassDeclaration node) { List<ClassMember> members = Lists.newArrayList(node.getMembers()); // visit s usually for (ClassMember classMember : members) { classMember.accept(this); } // use Parser, fill method lookup table if (node.getName().getName().equals("Parser")) { Set<String> usedMethodSignatures = Sets.newHashSet(); pw.println(); pw.print("Map<String, MethodTrampoline> methodTable_Parser = <String, MethodTrampoline> {"); for (ClassMember classMember : members) { Object binding = context.getNodeBinding(classMember); if (classMember instanceof MethodDeclaration && binding instanceof IMethodBinding) { MethodDeclaration method = (MethodDeclaration) classMember; IMethodBinding methodBinding = (IMethodBinding) binding; if (method.getPropertyKeyword() == null) { int parameterCount = methodBinding.getParameterTypes().length; String methodSignature = methodBinding.getName() + "_" + parameterCount; // don't add method more than once and hope that it is never called if (usedMethodSignatures.contains(methodSignature)) { continue; } else { usedMethodSignatures.add(methodSignature); } // generate map entry to method pw.println(); pw.print(" '"); pw.print(methodSignature); pw.print("': "); pw.print("new MethodTrampoline("); pw.print(parameterCount); pw.print(", (Parser target"); for (int i = 0; i < parameterCount; i++) { pw.print(", arg"); pw.print(i); } pw.print(") => target."); pw.print(method.getName().getName()); pw.print("("); for (int i = 0; i < parameterCount; i++) { if (i != 0) { pw.print(", "); } pw.print("arg"); pw.print(i); } pw.print(")),"); } } } pw.println("};"); pw.println(); } return null; } @Override public Void visitMethodDeclaration(MethodDeclaration node) { String name = node.getName().getName(); if (name.equals("_findParserMethod")) { removeMethod(node); } if (name.equals("invokeParserMethodImpl")) { removeMethod(node); String source = toSource( "Object invokeParserMethodImpl(Parser parser, String methodName, List<Object> objects, Token tokenStream) {", " parser.currentToken = tokenStream;", " MethodTrampoline method = methodTable_Parser['${methodName}_${objects.length}'];", " return method.invoke(parser, objects);", "}"); pw.print("\n"); pw.print(source); pw.print("\n"); } return super.visitMethodDeclaration(node); } private void removeMethod(MethodDeclaration node) { ((ClassDeclaration) node.getParent()).getMembers().remove(node); } }); } /** * Rewrites "invokeParserMethodImpl" invocation. */ public static void replaceReflection_invokeParserMethodImpl(AstNode node) { node.accept(new RecursiveAstVisitor<Void>() { @Override public Void visitMethodInvocation(MethodInvocation node) { String name = node.getMethodName().getName(); if (name.equals("invokeParserMethodImpl")) { node.setTarget(null); return null; } return super.visitMethodInvocation(node); } }); } /** * Find code like: * * <pre> * Field scopeField = visitor.getClass().getSuperclass().getDeclaredField("nameScope"); * scopeField.setAccessible(true); * Scope outerScope = (Scope) scopeField.get(visitor); * </pre> * * and replaces it with direct calling of generated public accessors. */ static void rewriteReflectionFieldsWithDirect(final Context context, CompilationUnit unit) { final Map<String, String> varToField = Maps.newHashMap(); final Map<String, String> varToMethod = Maps.newHashMap(); final Set<Pair<String, String>> refClassFields = Sets.newHashSet(); final String accessorSuffix = "_J2DAccessor"; unit.accept(new RecursiveAstVisitor<Void>() { @Override public Void visitBlock(Block node) { List<Statement> statements = ImmutableList.copyOf(node.getStatements()); for (Statement statement : statements) { statement.accept(this); } return null; } @Override public Void visitExpressionStatement(ExpressionStatement node) { if (node.getExpression() instanceof MethodInvocation) { MethodInvocation invocation = (MethodInvocation) node.getExpression(); if (JavaUtils.isMethod( context.getNodeBinding(invocation), "java.lang.reflect.AccessibleObject", "setAccessible")) { ((Block) node.getParent()).getStatements().remove(node); return null; } } return super.visitExpressionStatement(node); } @Override public Void visitMethodInvocation(MethodInvocation node) { List<Expression> arguments = node.getArgumentList().getArguments(); if (JavaUtils.isMethod(context.getNodeBinding(node), "java.lang.reflect.Field", "get")) { Expression target = arguments.get(0); String varName = ((SimpleIdentifier) node.getTarget()).getName(); String fieldName = varToField.get(varName); String accessorName = fieldName + accessorSuffix; SemanticProcessor.replaceNode(node, propertyAccess(target, identifier(accessorName))); return null; } if (JavaUtils.isMethod(context.getNodeBinding(node), "java.lang.reflect.Field", "set")) { Expression target = arguments.get(0); String varName = ((SimpleIdentifier) node.getTarget()).getName(); String fieldName = varToField.get(varName); String accessorName = fieldName + accessorSuffix; SemanticProcessor.replaceNode( node, assignmentExpression( propertyAccess(target, identifier(accessorName)), TokenType.EQ, arguments.get(1))); return null; } if (JavaUtils.isMethod(context.getNodeBinding(node), "java.lang.reflect.Method", "invoke")) { Expression target = arguments.get(0); if (node.getTarget() instanceof MethodInvocation) { System.out.println(node.getTarget()); } String varName = ((SimpleIdentifier) node.getTarget()).getName(); String methodName = varToMethod.get(varName); List<Expression> methodArgs; if (arguments.size() == 1) { methodArgs = Lists.newArrayList(); } else if (arguments.size() == 2 && arguments.get(1) instanceof ListLiteral) { methodArgs = ((ListLiteral) arguments.get(1)).getElements(); } else { methodArgs = Lists.newArrayList(arguments); methodArgs.remove(0); } if (methodName != null) { SemanticProcessor.replaceNode(node, methodInvocation(target, methodName, methodArgs)); } return null; } return super.visitMethodInvocation(node); } @Override public Void visitVariableDeclarationStatement(VariableDeclarationStatement node) { super.visitVariableDeclarationStatement(node); VariableDeclarationList variableList = node.getVariables(); ITypeBinding typeBinding = context.getNodeTypeBinding(variableList.getType()); List<VariableDeclaration> variables = variableList.getVariables(); if (JavaUtils.isTypeNamed(typeBinding, "java.lang.reflect.Field") && variables.size() == 1) { VariableDeclaration variable = variables.get(0); if (variable.getInitializer() instanceof MethodInvocation) { MethodInvocation initializer = (MethodInvocation) variable.getInitializer(); if (JavaUtils.isMethod( context.getNodeBinding(initializer), "java.lang.Class", "getDeclaredField")) { Expression getFieldArgument = initializer.getArgumentList().getArguments().get(0); String varName = variable.getName().getName(); String fieldName = ((SimpleStringLiteral) getFieldArgument).getValue(); varToField.put(varName, fieldName); ((Block) node.getParent()).getStatements().remove(node); // add (Class, Field) pair to generate accessor later addClassFieldPair(initializer.getTarget(), fieldName); } } } if (JavaUtils.isTypeNamed(typeBinding, "java.lang.reflect.Method") && variables.size() == 1) { VariableDeclaration variable = variables.get(0); if (variable.getInitializer() instanceof MethodInvocation) { MethodInvocation initializer = (MethodInvocation) variable.getInitializer(); if (JavaUtils.isMethod( context.getNodeBinding(initializer), "java.lang.Class", "getDeclaredMethod")) { Expression getMethodArgument = initializer.getArgumentList().getArguments().get(0); String varName = variable.getName().getName(); String methodName = ((SimpleStringLiteral) getMethodArgument).getValue(); varToMethod.put(varName, methodName); ((Block) node.getParent()).getStatements().remove(node); } } } return null; } private void addClassFieldPair(Expression target, String fieldName) { while (target instanceof MethodInvocation) { target = ((MethodInvocation) target).getTarget(); } // we expect: object.runtimeType if (target instanceof PropertyAccess) { Expression classTarget = ((PropertyAccess) target).getTarget(); ITypeBinding classTargetBinding = context.getNodeTypeBinding(classTarget); String className = classTargetBinding.getName(); refClassFields.add(Pair.of(className, fieldName)); } } }); // generate private field accessors unit.accept(new RecursiveAstVisitor<Void>() { @Override public Void visitClassDeclaration(ClassDeclaration node) { String className = node.getName().getName(); for (Pair<String, String> pair : refClassFields) { if (pair.getLeft().equals(className)) { String fieldName = pair.getRight(); String accessorName = fieldName + accessorSuffix; String privatePropertyName; if ("elementResolver".equals(fieldName) || "thisType".equals(fieldName) || "typeAnalyzer".equals(fieldName) || "labelScope".equals(fieldName) || "nameScope".equals(fieldName) || "enclosingClass".equals(fieldName)) { privatePropertyName = "_" + fieldName; } else { privatePropertyName = fieldName; } node.getMembers().add( methodDeclaration( null, null, Keyword.GET, null, identifier(accessorName), null, expressionFunctionBody(identifier(privatePropertyName)))); node.getMembers().add( methodDeclaration( null, null, Keyword.SET, null, identifier(accessorName), formalParameterList(simpleFormalParameter("__v")), expressionFunctionBody(assignmentExpression( identifier(privatePropertyName), TokenType.EQ, identifier("__v"))))); } } return super.visitClassDeclaration(node); } }); } static void useImportPrefix(final Context context, final AstNode rootNode, final String prefixName, final String[] packageNames, final boolean exactPackage) { rootNode.accept(new RecursiveAstVisitor<Void>() { @Override public Void visitMethodInvocation(MethodInvocation node) { super.visitMethodInvocation(node); Expression target = node.getTarget(); Object binding0 = context.getNodeBinding(target); if (binding0 instanceof ITypeBinding) { ITypeBinding binding = (ITypeBinding) binding0; String shortName = binding.getName(); shortName = StringUtils.substringBefore(shortName, "<"); if (shouldRewrite(binding)) { SemanticProcessor.replaceNode(target, identifier(prefixName, shortName)); return null; } } return null; } @Override public Void visitPropertyAccess(PropertyAccess node) { super.visitPropertyAccess(node); Expression target = node.getTarget(); Object binding0 = context.getNodeBinding(target); if (binding0 instanceof ITypeBinding) { ITypeBinding binding = (ITypeBinding) binding0; String shortName = binding.getName(); shortName = StringUtils.substringBefore(shortName, "<"); if (shouldRewrite(binding)) { SemanticProcessor.replaceNode(target, identifier(prefixName, shortName)); return null; } } return null; } @Override public Void visitTypeName(TypeName node) { ITypeBinding binding = context.getNodeTypeBinding(node); if (binding != null) { if (shouldRewrite(binding)) { String shortName = node.getName().getName(); if (shortName.indexOf('.') != -1) { shortName = StringUtils.substringAfterLast(shortName, "."); } shortName = StringUtils.substringBefore(shortName, "<"); node.setName(identifier(prefixName, shortName)); return null; } } return super.visitTypeName(node); } private boolean isInPackage(ITypeBinding binding) { String typeName = binding.getQualifiedName(); for (String packageName : packageNames) { if (typeName.equals(packageName + binding.getName())) { return true; } } return false; } private boolean isPrefixPackage(ITypeBinding binding) { String typeName = binding.getQualifiedName(); for (String packageName : packageNames) { if (typeName.startsWith(packageName)) { return true; } } return false; } private boolean shouldRewrite(ITypeBinding binding) { if (exactPackage) { return isInPackage(binding); } return isPrefixPackage(binding); } }); } private static String toSource(String... lines) { return Joiner.on("\n").join(lines); } public EngineSemanticProcessor(Context context) { super(context); } @Override public void process(final CompilationUnit unit) { unit.accept(new GeneralizingAstVisitor<Void>() { @Override public Void visitClassDeclaration(ClassDeclaration node) { // visit copy of members (we modify them) for (ClassMember member : Lists.newArrayList(node.getMembers())) { member.accept(this); } ITypeBinding typeBinding = context.getNodeTypeBinding(node); // hashCode is broken on Dart VM. So, we generate it using different way. // https://code.google.com/p/dart/issues/detail?id=5746 if (JavaUtils.isTypeNamed(typeBinding, "com.google.dart.engine.ast.ASTNode")) { node.getMembers().add( fieldDeclaration( true, typeName("int"), variableDeclaration("_hashCodeGenerator", integer(0)))); node.getMembers().add( fieldDeclaration( false, Keyword.FINAL, typeName("int"), variableDeclaration( "hashCode", prefixExpression(TokenType.PLUS_PLUS, identifier("_hashCodeGenerator"))))); } // done return null; } @Override public Void visitInstanceCreationExpression(InstanceCreationExpression node) { super.visitInstanceCreationExpression(node); Object binding = context.getNodeBinding(node); if (binding instanceof IMethodBinding) { IMethodBinding methodBinding = (IMethodBinding) binding; // new IntList(5) -> new List() if (isMethodInClass2( methodBinding, "<init>(int)", "com.google.dart.engine.utilities.collection.IntList")) { node.getArgumentList().getArguments().clear(); return null; } } // done return null; } @Override public Void visitMethodDeclaration(MethodDeclaration node) { super.visitMethodDeclaration(node); String name = node.getName().getName(); NodeList<FormalParameter> parameters = getParameters(node); if ("accept".equals(name) && parameters.size() == 1) { node.setReturnType(null); FormalParameter formalParameter = parameters.get(0); ((SimpleFormalParameter) formalParameter).getType().setTypeArguments(null); } return null; } @Override public Void visitMethodInvocation(MethodInvocation node) { AstNode parent = node.getParent(); List<Expression> args = node.getArgumentList().getArguments(); if (isMethodInClass(node, "toArray", "com.google.dart.engine.utilities.collection.IntList")) { replaceNode(node, node.getTarget()); return null; } if (isMethodInClass( node, "equals", "com.google.dart.engine.utilities.general.ObjectUtilities")) { Expression arg0 = args.get(0); Expression arg1 = args.get(1); if (parent instanceof PrefixExpression && ((PrefixExpression) parent).getOperator().getType() == TokenType.BANG) { replaceNode( parent, parenthesizedExpression(binaryExpression(arg0, TokenType.BANG_EQ, arg1))); } else { replaceNode( node, parenthesizedExpression(binaryExpression(arg0, TokenType.EQ_EQ, arg1))); } return null; } if (isMethodInClass( node, "getContents", "com.google.dart.engine.utilities.io.FileUtilities")) { replaceNode(node, methodInvocation(args.get(0), "readAsStringSync")); return null; } if (isMethodInClass( node, "getExtension", "com.google.dart.engine.utilities.io.FileUtilities")) { replaceNode(node.getTarget(), identifier("FileNameUtilities")); return null; } if (isMethodInClass(node, "encode", "com.google.dart.engine.utilities.io.UriUtilities")) { replaceNode(node, methodInvocation(identifier("Uri"), "encodeFull", args.get(0))); return null; } return super.visitMethodInvocation(node); } @Override public Void visitTypeName(TypeName node) { if (node.getName() instanceof SimpleIdentifier) { SimpleIdentifier nameNode = (SimpleIdentifier) node.getName(); String name = nameNode.getName(); if ("IntList".equals(name)) { replaceNode(node, typeName("List", typeName("int"))); return null; } } return super.visitTypeName(node); } private NodeList<FormalParameter> getParameters(MethodDeclaration node) { if (node.getParameters() == null) { return new NodeList<FormalParameter>(null); } return node.getParameters().getParameters(); } }); } }