Java tutorial
/* * Copyright 2011 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 * * 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.devtools.j2cpp.translate; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.devtools.j2cpp.sym.Symbols; import com.google.devtools.j2cpp.types.GeneratedMethodBinding; import com.google.devtools.j2cpp.types.GeneratedVariableBinding; import com.google.devtools.j2cpp.types.NodeCopier; import com.google.devtools.j2cpp.types.Types; import com.google.devtools.j2cpp.util.NameTable; import com.google.devtools.j2cpp.types.IOSArrayTypeBinding; import com.google.devtools.j2cpp.types.IOSMethodBinding; import com.google.devtools.j2cpp.types.IOSVariableBinding; import com.google.devtools.j2objc.util.ErrorReportingASTVisitor; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; import org.eclipse.jdt.core.dom.ArrayCreation; import org.eclipse.jdt.core.dom.ArrayInitializer; import org.eclipse.jdt.core.dom.Assignment; import org.eclipse.jdt.core.dom.Block; import org.eclipse.jdt.core.dom.BodyDeclaration; import org.eclipse.jdt.core.dom.BooleanLiteral; import org.eclipse.jdt.core.dom.BreakStatement; import org.eclipse.jdt.core.dom.CharacterLiteral; import org.eclipse.jdt.core.dom.ContinueStatement; import org.eclipse.jdt.core.dom.DoStatement; import org.eclipse.jdt.core.dom.EnhancedForStatement; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.ExpressionStatement; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.ForStatement; import org.eclipse.jdt.core.dom.IBinding; import org.eclipse.jdt.core.dom.IExtendedModifier; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.IPackageBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.IVariableBinding; import org.eclipse.jdt.core.dom.Initializer; import org.eclipse.jdt.core.dom.LabeledStatement; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.MethodInvocation; import org.eclipse.jdt.core.dom.Modifier; import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword; import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.NumberLiteral; import org.eclipse.jdt.core.dom.PrimitiveType; import org.eclipse.jdt.core.dom.ReturnStatement; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.Statement; import org.eclipse.jdt.core.dom.StringLiteral; import org.eclipse.jdt.core.dom.SuperMethodInvocation; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.TypeLiteral; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.core.dom.VariableDeclarationStatement; import org.eclipse.jdt.core.dom.WhileStatement; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.logging.Logger; /** * Rewrites the Java AST to replace difficult to translate code with methods * that are more Objective C/iOS specific. For example, Objective C doesn't have * the concept of class variables, so they need to be replaced with static * accessor methods referencing private static data. * * @author Tom Ball */ public class Rewriter extends ErrorReportingASTVisitor { /** * The list of Objective-C type qualifier keywords. */ private static final List<String> typeQualifierKeywords = Lists.newArrayList("in", "out", "inout", "oneway", "bycopy", "byref"); @SuppressWarnings("unchecked") @Override public boolean visit(TypeDeclaration node) { return visitType(node.getAST(), Types.getTypeBinding(node), node.bodyDeclarations(), node.getModifiers()); } @SuppressWarnings("unchecked") @Override public boolean visit(AnonymousClassDeclaration node) { return visitType(node.getAST(), Types.getTypeBinding(node), node.bodyDeclarations(), Modifier.NONE); } private boolean visitType(AST ast, ITypeBinding typeBinding, List<BodyDeclaration> members, int modifiers) { ITypeBinding[] interfaces = typeBinding.getInterfaces(); if (interfaces.length > 0) { if (Modifier.isAbstract(modifiers)) { // Add any interface methods that aren't defined by this abstract type. // Obj-C needs these to verify that the generated class implements the // interface/protocol. for (ITypeBinding intrface : interfaces) { // Collect needed methods from this interface and all super-interfaces. Queue<ITypeBinding> interfaceQueue = new LinkedList<ITypeBinding>(); Set<IMethodBinding> interfaceMethods = new LinkedHashSet<IMethodBinding>(); interfaceQueue.add(intrface); while ((intrface = interfaceQueue.poll()) != null) { interfaceMethods.addAll(Arrays.asList(intrface.getDeclaredMethods())); interfaceQueue.addAll(Arrays.asList(intrface.getInterfaces())); } addMissingMethods(ast, typeBinding, interfaceMethods, members); } } else if (!typeBinding.isInterface()) { // Check for methods that the type *explicitly implements* for cases // where a superclass provides the implementation. For example, many // Java interfaces define equals(Object) to provide documentation, which // a class doesn't need to implement in Java, but does in Obj-C. These // classes need a forwarding method to pass the Obj-C compiler. Set<IMethodBinding> interfaceMethods = new LinkedHashSet<IMethodBinding>(); for (ITypeBinding intrface : interfaces) { interfaceMethods.addAll(Arrays.asList(intrface.getDeclaredMethods())); } addForwardingMethods(ast, typeBinding, interfaceMethods, members); } } removeSerialization(members); renameDuplicateMembers(typeBinding); return true; } private void addMissingMethods(AST ast, ITypeBinding typeBinding, Set<IMethodBinding> interfaceMethods, List<BodyDeclaration> decls) { for (IMethodBinding interfaceMethod : interfaceMethods) { if (!isMethodImplemented(typeBinding, interfaceMethod, decls)) { addAbstractMethod(ast, typeBinding, interfaceMethod, decls); } } } private void addForwardingMethods(AST ast, ITypeBinding typeBinding, Set<IMethodBinding> interfaceMethods, List<BodyDeclaration> decls) { for (IMethodBinding interfaceMethod : interfaceMethods) { String methodName = interfaceMethod.getName(); // These are the only java.lang.Object methods that are both overridable // and translated to Obj-C. if (methodName.matches("equals|hashCode|toString")) { if (!isMethodImplemented(typeBinding, interfaceMethod, decls)) { addForwardingMethod(ast, typeBinding, interfaceMethod, decls); } } } } private boolean isMethodImplemented(ITypeBinding type, IMethodBinding interfaceMethod, List<BodyDeclaration> decls) { for (BodyDeclaration decl : decls) { if (!(decl instanceof MethodDeclaration)) { continue; } if (Types.getMethodBinding(decl).isSubsignature(interfaceMethod)) { return true; } } return isMethodImplemented(type.getSuperclass(), interfaceMethod); } private boolean isMethodImplemented(ITypeBinding type, IMethodBinding method) { if (type == null || type.getQualifiedName().equals("java.lang.Object")) { return false; } for (IMethodBinding m : type.getDeclaredMethods()) { if (method.isSubsignature(m) || (method.getName().equals(m.getName()) && method.getReturnType().getErasure().isEqualTo(m.getReturnType().getErasure()) && Arrays.equals(method.getParameterTypes(), m.getParameterTypes()))) { return true; } } return isMethodImplemented(type.getSuperclass(), method); } @Override public boolean visit(MethodDeclaration node) { // change the names of any methods that conflict with NSObject messages IMethodBinding binding = Types.getMethodBinding(node); String name = binding.getName(); renameReservedNames(name, binding); @SuppressWarnings("unchecked") List<SingleVariableDeclaration> params = node.parameters(); for (int i = 0; i < params.size(); i++) { // Change the names of any parameters that are type qualifier keywords. SingleVariableDeclaration param = params.get(i); name = param.getName().getIdentifier(); if (typeQualifierKeywords.contains(name)) { IVariableBinding varBinding = param.resolveBinding(); NameTable.rename(varBinding, name + "Arg"); } } return true; } @Override public boolean visit(MethodInvocation node) { boolean visitChildren = true; if (rewriteSystemOut(node)) { visitChildren = false; } if (rewriteStringFormat(node)) { visitChildren = false; } IMethodBinding binding = Types.getMethodBinding(node); String name = binding.getName(); renameReservedNames(name, binding); return visitChildren; } @Override public boolean visit(SuperMethodInvocation node) { renameReservedNames(node.getName().getIdentifier(), Types.getMethodBinding(node)); return true; } private void renameReservedNames(String name, IMethodBinding binding) { if (NameTable.isReservedName(name)) { NameTable.rename(binding, name + "__"); } } @Override public boolean visit(FieldDeclaration node) { int mods = node.getModifiers(); if (Modifier.isStatic(mods)) { ASTNode parent = node.getParent(); @SuppressWarnings("unchecked") List<BodyDeclaration> classMembers = parent instanceof AbstractTypeDeclaration ? ((AbstractTypeDeclaration) parent).bodyDeclarations() : ((AnonymousClassDeclaration) parent).bodyDeclarations(); // safe by specification int indexOfNewMember = classMembers.indexOf(node) + 1; @SuppressWarnings("unchecked") List<VariableDeclarationFragment> fragments = node.fragments(); // safe by specification for (VariableDeclarationFragment var : fragments) { IVariableBinding binding = Types.getVariableBinding(var); if (Types.isPrimitiveConstant(binding) && Modifier.isPrivate(binding.getModifiers())) { // Don't define accessors for private constants, since they can be // directly referenced. continue; } // rename varName to varName_, per Obj-C style guide SimpleName oldName = var.getName(); ITypeBinding type = ((AbstractTypeDeclaration) node.getParent()).resolveBinding(); String varName = NameTable.getStaticVarQualifiedName(type, oldName.getIdentifier()); NameTable.rename(binding, varName); ITypeBinding typeBinding = binding.getType(); var.setExtraDimensions(0); // if array, type was corrected above // add accessor(s) if (needsReader(var, classMembers)) { classMembers.add(indexOfNewMember++, makeStaticReader(var, mods)); } if (!Modifier.isFinal(node.getModifiers()) && needsWriter(var, classMembers)) { classMembers.add(indexOfNewMember++, makeStaticWriter(var, oldName.getIdentifier(), node.getType(), mods)); } // move non-constant initialization to init block Expression initializer = var.getInitializer(); if (initializer != null && initializer.resolveConstantExpressionValue() == null) { var.setInitializer(null); AST ast = var.getAST(); SimpleName newName = ast.newSimpleName(varName); Types.addBinding(newName, binding); Assignment assign = ast.newAssignment(); assign.setLeftHandSide(newName); Expression newInit = NodeCopier.copySubtree(ast, initializer); assign.setRightHandSide(newInit); Types.addBinding(assign, typeBinding); Block initBlock = ast.newBlock(); @SuppressWarnings("unchecked") List<Statement> stmts = initBlock.statements(); // safe by definition stmts.add(ast.newExpressionStatement(assign)); Initializer staticInitializer = ast.newInitializer(); staticInitializer.setBody(initBlock); @SuppressWarnings("unchecked") List<IExtendedModifier> initMods = staticInitializer.modifiers(); // safe by definition initMods.add(ast.newModifier(ModifierKeyword.STATIC_KEYWORD)); classMembers.add(indexOfNewMember++, staticInitializer); } } } return true; } @Override public boolean visit(Block node) { // split array declarations so that initializers are in separate statements. @SuppressWarnings("unchecked") List<Statement> stmts = node.statements(); // safe by definition int n = stmts.size(); for (int i = 0; i < n; i++) { Statement s = stmts.get(i); if (s instanceof VariableDeclarationStatement) { VariableDeclarationStatement var = (VariableDeclarationStatement) s; Map<VariableDeclarationFragment, Expression> initializers = Maps.newLinkedHashMap(); @SuppressWarnings("unchecked") List<VariableDeclarationFragment> fragments = var.fragments(); for (VariableDeclarationFragment fragment : fragments) { ITypeBinding varType = Types.getTypeBinding(fragment); if (varType.isArray()) { fragment.setExtraDimensions(0); Expression initializer = fragment.getInitializer(); if (initializer != null) { initializers.put(fragment, initializer); if (initializer instanceof ArrayCreation) { ArrayCreation creator = (ArrayCreation) initializer; if (creator.getInitializer() != null) { // replace this redundant array creation node with its // rewritten initializer initializer = creator.getInitializer(); } else { continue; } } if (initializer instanceof ArrayInitializer) { fragment.setInitializer(createIOSArrayInitializer(Types.getTypeBinding(fragment), (ArrayInitializer) initializer)); } } } } } else if (s instanceof ExpressionStatement && ((ExpressionStatement) s).getExpression() instanceof Assignment) { Assignment assign = (Assignment) ((ExpressionStatement) s).getExpression(); ITypeBinding assignType = Types.getTypeBinding(assign); if (assign.getRightHandSide() instanceof ArrayInitializer) { ArrayInitializer arrayInit = (ArrayInitializer) assign.getRightHandSide(); assert assignType.isArray() : "array initializer assigned to non-array"; assign.setRightHandSide(createIOSArrayInitializer(assignType, arrayInit)); } else if (assign.getRightHandSide() instanceof ArrayCreation) { ArrayCreation arrayCreate = (ArrayCreation) assign.getRightHandSide(); ArrayInitializer arrayInit = arrayCreate.getInitializer(); if (arrayInit != null) { // Replace ArrayCreation node with its initializer. AST ast = node.getAST(); Assignment newAssign = ast.newAssignment(); Types.addBinding(newAssign, assignType); newAssign.setLeftHandSide(NodeCopier.copySubtree(ast, assign.getLeftHandSide())); newAssign.setRightHandSide(createIOSArrayInitializer(assignType, arrayInit)); ((ExpressionStatement) s).setExpression(newAssign); } } } } return true; } @SuppressWarnings("unchecked") @Override public boolean visit(LabeledStatement node) { Statement s = node.getBody(); Statement statementBody = null; if (s instanceof DoStatement) { statementBody = ((DoStatement) s).getBody(); } else if (s instanceof EnhancedForStatement) { statementBody = ((EnhancedForStatement) s).getBody(); } else if (s instanceof ForStatement) { statementBody = ((ForStatement) s).getBody(); } else if (s instanceof WhileStatement) { statementBody = ((WhileStatement) s).getBody(); } if (statementBody != null) { AST ast = node.getAST(); final boolean[] hasContinue = new boolean[1]; final boolean[] hasBreak = new boolean[1]; node.accept(new ASTVisitor() { @Override public void endVisit(ContinueStatement node) { if (node.getLabel() != null) { hasContinue[0] = true; } } @Override public void endVisit(BreakStatement node) { if (node.getLabel() != null) { hasBreak[0] = true; } } }); List<Statement> stmts = null; if (hasContinue[0]) { if (statementBody instanceof Block) { // Add empty labeled statement as last block statement. stmts = ((Block) statementBody).statements(); LabeledStatement newLabel = ast.newLabeledStatement(); newLabel.setLabel(NodeCopier.copySubtree(ast, node.getLabel())); newLabel.setBody(ast.newEmptyStatement()); stmts.add(newLabel); } } if (hasBreak[0]) { ASTNode parent = node.getParent(); if (parent instanceof Block) { stmts = ((Block) parent).statements(); } else { // Surround parent with block. Block block = ast.newBlock(); stmts = block.statements(); stmts.add((Statement) parent); // Replace parent in its statement list with new block. List<Statement> superStmts = ((Block) parent.getParent()).statements(); for (int i = 0; i < superStmts.size(); i++) { if (superStmts.get(i) == parent) { superStmts.set(i, block); break; } } stmts = block.statements(); } // Find node in statement list, and add empty labeled statement after it. for (int i = 0; i < stmts.size(); i++) { if (stmts.get(i) == node) { LabeledStatement newLabel = ast.newLabeledStatement(); newLabel.setLabel(NodeCopier.copySubtree(ast, node.getLabel())); newLabel.setBody(ast.newEmptyStatement()); stmts.add(i + 1, newLabel); break; } } } if (hasContinue[0] || hasBreak[0]) { // Replace this node with its statement, thus deleting the label. ASTNode parent = node.getParent(); if (parent instanceof Block) { stmts = ((Block) parent).statements(); for (int i = 0; i < stmts.size(); i++) { if (stmts.get(i) == node) { stmts.set(i, NodeCopier.copySubtree(ast, node.getBody())); break; } } } } } return true; } /** * Returns true if a reader method is needed for a specified field. The * heuristic used is to find a method that has the same name, returns the * same type, and has no parameters. Obviously, lousy code can fail this * test, but it should work in practice with existing Java code standards. */ private boolean needsReader(VariableDeclarationFragment var, List<BodyDeclaration> classMembers) { String methodName = var.getName().getIdentifier(); ITypeBinding varType = Types.getTypeBinding(var); for (BodyDeclaration member : classMembers) { if (member instanceof MethodDeclaration) { IMethodBinding method = Types.getMethodBinding(member); if (method.getName().equals(methodName) && method.getReturnType().isEqualTo(varType) && method.getParameterTypes().length == 0) { return false; } } } return true; } /** * Returns true if a writer method is needed for a specified field. The * heuristic used is to find a method that has "set" plus the capitalized * field name, returns null, and takes a single parameter of the same type. * Obviously, lousy code can fail this test, but it should work in practice * with Google code standards. */ private boolean needsWriter(VariableDeclarationFragment var, List<BodyDeclaration> classMembers) { String methodName = "set" + NameTable.capitalize(var.getName().getIdentifier()); ITypeBinding varType = Types.getTypeBinding(var); ITypeBinding voidType = var.getAST().resolveWellKnownType("void"); for (BodyDeclaration member : classMembers) { if (member instanceof MethodDeclaration) { IMethodBinding method = Types.getMethodBinding(member); ITypeBinding[] params = method.getParameterTypes(); if (method.getName().equals(methodName) && method.getReturnType().isEqualTo(voidType) && params.length == 1 && params[0].isEqualTo(varType)) { return false; } } } return true; } /** * Convert an array initializer into a init method on the equivalent * IOSArray. This init method takes a C array and count, like * NSArray.arrayWithObjects:count:. For example, "int[] a = { 1, 2, 3 };" * translates to "[IOSIntArray initWithInts:(int[]){ 1, 2, 3 } count:3];". */ private MethodInvocation createIOSArrayInitializer(ITypeBinding arrayType, ArrayInitializer arrayInit) { AST ast = arrayInit.getAST(); int dimensions = arrayType.getDimensions(); ITypeBinding componentType; IOSArrayTypeBinding iosArrayBinding; if (dimensions > 2) { // This gets resolved into IOSObjectArray, for an array of arrays. componentType = iosArrayBinding = Types.resolveArrayType(arrayType); } else if (dimensions == 2) { // Creates a single-dimension array type. componentType = Types.resolveArrayType(arrayType.getElementType()); iosArrayBinding = Types.resolveArrayType(componentType); } else { componentType = Types.getTypeBinding(arrayInit).getComponentType(); iosArrayBinding = Types.resolveArrayType(componentType); } // Create IOS message. MethodInvocation message = ast.newMethodInvocation(); SimpleName receiver = ast.newSimpleName(iosArrayBinding.getName()); Types.addBinding(receiver, iosArrayBinding); message.setExpression(receiver); String methodName = iosArrayBinding.getInitMethod(); SimpleName messageName = ast.newSimpleName(methodName); GeneratedMethodBinding methodBinding = new GeneratedMethodBinding(methodName, Modifier.PUBLIC | Modifier.STATIC, iosArrayBinding, iosArrayBinding, false, false, true); Types.addBinding(messageName, methodBinding); message.setName(messageName); Types.addBinding(message, methodBinding); // Pass array initializer as C-style array to message. @SuppressWarnings("unchecked") List<Expression> args = message.arguments(); // safe by definition ArrayInitializer newArrayInit = NodeCopier.copySubtree(ast, arrayInit); @SuppressWarnings("unchecked") List<Expression> exprs = newArrayInit.expressions(); for (int i = 0; i < exprs.size(); i++) { // Convert any elements that are also array initializers. Expression expr = exprs.get(i); if (expr instanceof ArrayInitializer) { exprs.set(i, createIOSArrayInitializer(componentType, (ArrayInitializer) expr)); } } args.add(newArrayInit); GeneratedVariableBinding argBinding = new GeneratedVariableBinding(arrayType, false, true, null, methodBinding); methodBinding.addParameter(argBinding); NumberLiteral arraySize = ast.newNumberLiteral(Integer.toString(arrayInit.expressions().size())); Types.addBinding(arraySize, ast.resolveWellKnownType("int")); args.add(arraySize); argBinding = new GeneratedVariableBinding(ast.resolveWellKnownType("int"), false, true, null, methodBinding); methodBinding.addParameter(argBinding); // Specify type for object arrays. if (iosArrayBinding.getName().equals("IOSObjectArray")) { TypeLiteral typeLiteral = ast.newTypeLiteral(); typeLiteral.setType(Types.makeType(componentType)); Types.addBinding(typeLiteral, Types.getIOSClass()); args.add(typeLiteral); argBinding = new GeneratedVariableBinding("type", 0, Types.getIOSClass(), false, true, null, methodBinding); methodBinding.addParameter(argBinding); } return message; } /** * Add a static read accessor method for a specified variable. The generator * phase will rename the variable from "name" to "name_", following the Obj-C * style guide. */ private MethodDeclaration makeStaticReader(VariableDeclarationFragment var, int modifiers) { AST ast = var.getAST(); String varName = var.getName().getIdentifier(); IVariableBinding varBinding = var.resolveBinding(); String methodName; methodName = NameTable.getStaticAccessorName(varName); Type returnType = Types.makeType(varBinding.getType()); MethodDeclaration accessor = createBlankAccessor(var, methodName, modifiers, returnType); ReturnStatement returnStmt = ast.newReturnStatement(); SimpleName returnName = ast.newSimpleName(var.getName().getIdentifier() + "_"); Types.addBinding(returnName, varBinding); returnStmt.setExpression(returnName); @SuppressWarnings("unchecked") List<Statement> stmts = accessor.getBody().statements(); // safe by definition stmts.add(returnStmt); GeneratedMethodBinding binding = new GeneratedMethodBinding(accessor, varBinding.getDeclaringClass(), false); Types.addBinding(accessor, binding); Types.addBinding(accessor.getName(), binding); Symbols.scanAST(accessor); return accessor; } /** * Add a static write accessor method for a specified variable. */ private MethodDeclaration makeStaticWriter(VariableDeclarationFragment var, String paramName, Type type, int modifiers) { AST ast = var.getAST(); String varName = var.getName().getIdentifier(); IVariableBinding varBinding = Types.getVariableBinding(var); Type returnType = ast.newPrimitiveType(PrimitiveType.VOID); Types.addBinding(returnType, ast.resolveWellKnownType("void")); String methodName = "set" + NameTable.capitalize(varName); MethodDeclaration accessor = createBlankAccessor(var, methodName, modifiers, returnType); GeneratedMethodBinding binding = new GeneratedMethodBinding(accessor, varBinding.getDeclaringClass(), false); Types.addBinding(accessor, binding); Types.addBinding(accessor.getName(), binding); SingleVariableDeclaration param = ast.newSingleVariableDeclaration(); param.setName(ast.newSimpleName(paramName)); Type paramType = NodeCopier.copySubtree(ast, type); param.setType(paramType); Types.addBinding(paramType, type.resolveBinding()); @SuppressWarnings("unchecked") List<SingleVariableDeclaration> parameters = accessor.parameters(); // safe by definition GeneratedVariableBinding paramBinding = new GeneratedVariableBinding(paramName, 0, type.resolveBinding(), false, true, varBinding.getDeclaringClass(), binding); Types.addBinding(param, paramBinding); Types.addBinding(param.getName(), paramBinding); parameters.add(param); binding.addParameter(paramBinding); Assignment assign = ast.newAssignment(); SimpleName sn = ast.newSimpleName(NameTable.getName(varBinding)); assign.setLeftHandSide(sn); Types.addBinding(sn, varBinding); assign.setRightHandSide(NodeCopier.copySubtree(ast, param.getName())); Types.addBinding(assign, varBinding.getType()); ExpressionStatement assignStmt = ast.newExpressionStatement(assign); @SuppressWarnings("unchecked") List<Statement> stmts = accessor.getBody().statements(); // safe by definition stmts.add(assignStmt); Symbols.scanAST(accessor); return accessor; } /** * Create an unbound accessor method, minus its code. */ @SuppressWarnings("unchecked") // safe by specification private MethodDeclaration createBlankAccessor(VariableDeclarationFragment var, String name, int modifiers, Type returnType) { AST ast = var.getAST(); MethodDeclaration accessor = ast.newMethodDeclaration(); accessor.setName(ast.newSimpleName(name)); accessor.modifiers().addAll(ast.newModifiers(modifiers)); accessor.setBody(ast.newBlock()); accessor.setReturnType2(NodeCopier.copySubtree(ast, returnType)); return accessor; } /** * Rewrites System.out and System.err println calls as NSLog calls. * * @return true if the node was rewritten */ // TODO(user): remove when there is iOS console support. @SuppressWarnings("unchecked") private boolean rewriteSystemOut(MethodInvocation node) { Expression expression = node.getExpression(); if (expression instanceof Name) { Name expr = (Name) node.getExpression(); IBinding binding = expr.resolveBinding(); if (binding instanceof IVariableBinding) { IVariableBinding varBinding = (IVariableBinding) binding; ITypeBinding type = varBinding.getDeclaringClass(); if (type == null) { return false; } String clsName = type.getQualifiedName(); String varName = varBinding.getName(); if (clsName.equals("java.lang.System") && (varName.equals("out") || varName.equals("err"))) { // Change System.out.* or System.err.* to NSLog AST ast = node.getAST(); MethodInvocation newInvocation = ast.newMethodInvocation(); IMethodBinding methodBinding = new IOSMethodBinding("NSLog", Types.getMethodBinding(node), null); Types.addBinding(newInvocation, methodBinding); Types.addFunction(methodBinding); newInvocation.setName(ast.newSimpleName("NSLog")); Types.addBinding(newInvocation.getName(), methodBinding); newInvocation.setExpression(null); // Insert NSLog format argument List<Expression> args = node.arguments(); if (args.size() == 1) { Expression arg = args.get(0); arg.accept(this); String format = getFormatArgument(arg); StringLiteral literal = ast.newStringLiteral(); literal.setLiteralValue(format); Types.addBinding(literal, ast.resolveWellKnownType("java.lang.String")); newInvocation.arguments().add(literal); // JDT won't let nodes be re-parented, so copy and map. ASTNode newArg = NodeCopier.copySubtree(ast, arg); if (arg instanceof MethodInvocation) { IMethodBinding argBinding = ((MethodInvocation) arg).resolveMethodBinding(); if (!argBinding.getReturnType().isPrimitive()) { IOSMethodBinding newBinding = new IOSMethodBinding("format", argBinding, Types.getNSString()); Types.addMappedInvocation((MethodInvocation) newArg, newBinding); } } newInvocation.arguments().add(newArg); } else if (args.size() > 1 && node.getName().getIdentifier().equals("printf")) { newInvocation.arguments().addAll(NodeCopier.copySubtrees(ast, args)); } else if (args.size() == 0) { // NSLog requires a format string. StringLiteral literal = ast.newStringLiteral(); literal.setLiteralValue(""); Types.addBinding(literal, ast.resolveWellKnownType("java.lang.String")); newInvocation.arguments().add(literal); } // Replace old invocation with new. ASTNode parent = node.getParent(); if (parent instanceof ExpressionStatement) { ExpressionStatement stmt = (ExpressionStatement) parent; stmt.setExpression(newInvocation); } else { throw new AssertionError("unknown parent type: " + parent.getClass().getSimpleName()); } return true; } } } return false; } /** * Rewrites String.format()'s format string to be iOS-compatible. * * @return true if the node was rewritten */ private boolean rewriteStringFormat(MethodInvocation node) { IMethodBinding binding = node.resolveMethodBinding(); if (binding == null) { // No binding due to error already reported. return false; } ITypeBinding typeBinding = binding.getDeclaringClass(); AST ast = node.getAST(); if (typeBinding.equals(ast.resolveWellKnownType("java.lang.String")) && binding.getName().equals("format")) { @SuppressWarnings("unchecked") List<Expression> args = node.arguments(); if (args.isEmpty()) { return false; } Expression first = args.get(0); typeBinding = first.resolveTypeBinding(); if (typeBinding.getQualifiedName().equals("java.util.Locale")) { args.remove(0); // discard locale parameter first = args.get(0); typeBinding = first.resolveTypeBinding(); } if (first instanceof StringLiteral) { String format = ((StringLiteral) first).getLiteralValue(); String convertedFormat = convertStringFormatString(format); if (!format.equals(convertedFormat)) { StringLiteral newLiteral = ast.newStringLiteral(); newLiteral.setLiteralValue(convertedFormat); Types.addBinding(newLiteral, ast.resolveWellKnownType("java.lang.String")); args.set(0, newLiteral); } return true; } } return false; } /** * Given a AST node, return the appropriate printf() format specifier. */ private String getFormatArgument(ASTNode node) { ITypeBinding type = Types.getTypeBinding(node); AST ast = node.getAST(); if (node instanceof CharacterLiteral || type.isEqualTo(ast.resolveWellKnownType("char"))) { return "%C"; } if (node instanceof BooleanLiteral || type.isEqualTo(ast.resolveWellKnownType("boolean"))) { return "%d"; } if (type.isEqualTo(ast.resolveWellKnownType("byte")) || type.isEqualTo(ast.resolveWellKnownType("int")) || type.isEqualTo(ast.resolveWellKnownType("short"))) { return "%d"; } if (type.isEqualTo(ast.resolveWellKnownType("long"))) { return "%lld"; } if (type.isEqualTo(ast.resolveWellKnownType("float")) || type.isEqualTo(ast.resolveWellKnownType("double"))) { return "%f"; } if (node instanceof NumberLiteral) { String token = ((NumberLiteral) node).getToken(); try { Integer.parseInt(token); return "%d"; } catch (NumberFormatException e) { try { Long.parseLong(token); return "%lld"; } catch (NumberFormatException e2) { try { Double.parseDouble(token); return "%f"; } catch (NumberFormatException e3) { throw new AssertionError("unknown number literal format: \"" + token + "\""); } } } } return "%@"; // object, including string } /** * Convert a Java string format string into a NSString equivalent. */ @SuppressWarnings("fallthrough") private String convertStringFormatString(String s) { if (s.isEmpty()) { return s; } String[] parts = s.split("%"); StringBuffer result = new StringBuffer(); int i = 0; if (!s.startsWith("%")) { result.append(parts[0]); i++; } while (i < parts.length) { String part = parts[i]; if (part.length() > 0) { result.append('%'); switch (part.charAt(0)) { case 's': case 'S': result.append('@'); break; case 'c': case 'C': result.append('C'); break; case 'h': case 'H': result.append('x'); break; // These aren't mapped, so escape them so it's obvious when output case 'b': case 'B': case 't': case 'T': case 'n': result.append('%'); // and fall-through default: result.append(part.charAt(0)); } result.append(part.substring(1)); } i++; } return result.toString(); } /** * Add an abstract method to the given type that implements the given * interface method binding. */ private void addAbstractMethod(AST ast, ITypeBinding typeBinding, IMethodBinding interfaceMethod, List<BodyDeclaration> decls) { MethodDeclaration method = createInterfaceMethodBody(ast, typeBinding, interfaceMethod); @SuppressWarnings("unchecked") List<Modifier> modifiers = method.modifiers(); modifiers.add(ast.newModifier(ModifierKeyword.ABSTRACT_KEYWORD)); decls.add(method); } /** * Java interfaces that redeclare java.lang.Object's equals, hashCode, or * toString methods need a forwarding method if the implementing class * relies on java.lang.Object's implementation. This is because NSObject * is declared as adhering to the NSObject protocol, but doesn't explicitly * declare these method in its interface. This prevents gcc from finding * an implementation, so it issues a warning. */ private void addForwardingMethod(AST ast, ITypeBinding typeBinding, IMethodBinding interfaceMethod, List<BodyDeclaration> decls) { Logger.getAnonymousLogger() .fine(String.format("adding %s to %s", interfaceMethod.getName(), typeBinding.getQualifiedName())); MethodDeclaration method = createInterfaceMethodBody(ast, typeBinding, interfaceMethod); // Add method body with single "super.method(parameters);" statement. Block body = ast.newBlock(); method.setBody(body); SuperMethodInvocation superInvocation = ast.newSuperMethodInvocation(); superInvocation.setName(NodeCopier.copySubtree(ast, method.getName())); @SuppressWarnings("unchecked") List<SingleVariableDeclaration> parameters = method.parameters(); // safe by design @SuppressWarnings("unchecked") List<Expression> args = superInvocation.arguments(); // safe by definition for (SingleVariableDeclaration param : parameters) { Expression arg = NodeCopier.copySubtree(ast, param.getName()); args.add(arg); } Types.addBinding(superInvocation, Types.getMethodBinding(method)); @SuppressWarnings("unchecked") List<Statement> stmts = body.statements(); // safe by definition ReturnStatement returnStmt = ast.newReturnStatement(); returnStmt.setExpression(superInvocation); stmts.add(returnStmt); decls.add(method); } private MethodDeclaration createInterfaceMethodBody(AST ast, ITypeBinding typeBinding, IMethodBinding interfaceMethod) { IMethodBinding methodBinding = new IOSMethodBinding(interfaceMethod.getName(), interfaceMethod, typeBinding); MethodDeclaration method = ast.newMethodDeclaration(); Types.addBinding(method, methodBinding); method.setReturnType2(Types.makeType(interfaceMethod.getReturnType())); SimpleName methodName = ast.newSimpleName(interfaceMethod.getName()); Types.addBinding(methodName, methodBinding); method.setName(methodName); @SuppressWarnings("unchecked") List<Modifier> modifiers = method.modifiers(); modifiers.add(ast.newModifier(ModifierKeyword.PUBLIC_KEYWORD)); @SuppressWarnings("unchecked") List<SingleVariableDeclaration> parameters = method.parameters(); // safe by design ITypeBinding[] parameterTypes = interfaceMethod.getParameterTypes(); for (int i = 0; i < parameterTypes.length; i++) { ITypeBinding paramType = parameterTypes[i]; String paramName = "param" + i; SingleVariableDeclaration param = ast.newSingleVariableDeclaration(); IVariableBinding paramBinding = IOSVariableBinding.newParameter(paramName, i, paramType, methodBinding, paramType.getDeclaringClass(), Modifier.isFinal(paramType.getModifiers())); Types.addBinding(param, paramBinding); param.setName(ast.newSimpleName(paramName)); Types.addBinding(param.getName(), paramBinding); param.setType(Types.makeType(paramType)); parameters.add(param); } Symbols.scanAST(method); return method; } /** * Remove private serialization methods and fields; since Java serialization * isn't supported, they only take up space. The list of methods is taken * from the java.io.Serialization javadoc comments. */ private void removeSerialization(List<BodyDeclaration> members) { for (Iterator<BodyDeclaration> iterator = members.iterator(); iterator.hasNext();) { BodyDeclaration member = iterator.next(); int mods = member.getModifiers(); if (member instanceof MethodDeclaration) { IMethodBinding binding = Types.getMethodBinding(member); String name = binding.getName(); ITypeBinding[] parameterTypes = binding.getParameterTypes(); ITypeBinding returnType = binding.getReturnType(); if (name.equals("readObject") && Modifier.isPrivate(mods) && parameterTypes.length == 1 && parameterTypes[0].getQualifiedName().equals("java.io.ObjectInputStream") && returnType.getBinaryName().equals("V")) { iterator.remove(); continue; } if (name.equals("writeObject") && Modifier.isPrivate(mods) && parameterTypes.length == 1 && parameterTypes[0].getQualifiedName().equals("java.io.ObjectOutputStream") && returnType.getBinaryName().equals("V")) { iterator.remove(); continue; } if (name.equals("readObjectNoData") && Modifier.isPrivate(mods) && parameterTypes.length == 0 && returnType.getBinaryName().equals("V")) { iterator.remove(); continue; } if ((name.equals("readResolve") || name.equals("writeResolve")) && Modifier.isPrivate(mods) && parameterTypes.length == 0 && returnType.getQualifiedName().equals("java.lang.Object")) { iterator.remove(); continue; } } else if (member instanceof FieldDeclaration) { FieldDeclaration field = (FieldDeclaration) member; Type type = field.getType(); VariableDeclarationFragment var = (VariableDeclarationFragment) field.fragments().get(0); if (var.getName().getIdentifier().equals("serialVersionUID") && type.isPrimitiveType() && ((PrimitiveType) type).getPrimitiveTypeCode() == PrimitiveType.LONG && Modifier.isPrivate(mods) && Modifier.isStatic(mods)) { iterator.remove(); continue; } } } } /** * If a field and method have the same name, or if a field hides a visible * superclass field, rename the field. This is necessary to avoid a name * clash when the fields are declared as properties. */ private void renameDuplicateMembers(ITypeBinding typeBinding) { Map<String, IVariableBinding> fields = Maps.newHashMap(); // Check all superclass(es) fields with declared fields. ITypeBinding superclass = typeBinding.getSuperclass(); if (superclass != null) { addFields(superclass, true, true, fields); for (IVariableBinding var : typeBinding.getDeclaredFields()) { String name = var.getName(); IVariableBinding field = fields.get(name); if (field != null) { name += '_'; NameTable.rename(var, name); fields.put(name, var); } } } // Check all declared fields with method names. addFields(typeBinding, true, false, fields); for (IMethodBinding method : typeBinding.getDeclaredMethods()) { String name = method.getName(); IVariableBinding field = fields.get(name); if (field != null) { IVariableBinding newField; while ((newField = fields.get(name)) != null) { name += '_'; field = newField; } NameTable.rename(field, name, true); } } } private void addFields(ITypeBinding type, boolean includePrivate, boolean includeSuperclasses, Map<String, IVariableBinding> fields) { for (IVariableBinding field : type.getDeclaredFields()) { if (!fields.containsValue(field)) { // if not already renamed int mods = field.getModifiers(); if (!Modifier.isStatic(mods)) { if (includePrivate) { fields.put(field.getName(), field); } else if (Modifier.isPublic(mods) || Modifier.isProtected(mods)) { fields.put(field.getName(), field); } else { IPackageBinding typePackage = type.getPackage(); IPackageBinding fieldPackage = field.getDeclaringClass().getPackage(); if (typePackage.isEqualTo(fieldPackage)) { fields.put(field.getName(), field); } } } } } ITypeBinding superclass = type.getSuperclass(); if (includeSuperclasses && superclass != null) { addFields(superclass, false, true, fields); } } }