/* * 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.j2objc.translate; import com.google.devtools.j2objc.ast.Assignment; import com.google.devtools.j2objc.ast.Block; import com.google.devtools.j2objc.ast.ClassInstanceCreation; import com.google.devtools.j2objc.ast.CommaExpression; import com.google.devtools.j2objc.ast.CompilationUnit; import com.google.devtools.j2objc.ast.EnumConstantDeclaration; import com.google.devtools.j2objc.ast.EnumDeclaration; import com.google.devtools.j2objc.ast.Expression; import com.google.devtools.j2objc.ast.ExpressionStatement; import com.google.devtools.j2objc.ast.ForStatement; import com.google.devtools.j2objc.ast.FunctionInvocation; import com.google.devtools.j2objc.ast.InfixExpression; import com.google.devtools.j2objc.ast.MethodDeclaration; import com.google.devtools.j2objc.ast.NativeDeclaration; import com.google.devtools.j2objc.ast.NativeExpression; import com.google.devtools.j2objc.ast.NativeStatement; import com.google.devtools.j2objc.ast.NumberLiteral; import com.google.devtools.j2objc.ast.PostfixExpression; import com.google.devtools.j2objc.ast.SimpleName; import com.google.devtools.j2objc.ast.SingleVariableDeclaration; import com.google.devtools.j2objc.ast.Statement; import com.google.devtools.j2objc.ast.StringLiteral; import com.google.devtools.j2objc.ast.TreeUtil; import com.google.devtools.j2objc.ast.Type; import com.google.devtools.j2objc.ast.UnitTreeVisitor; import com.google.devtools.j2objc.ast.VariableDeclarationExpression; import com.google.devtools.j2objc.ast.VariableDeclarationFragment; import com.google.devtools.j2objc.ast.VariableDeclarationStatement; import com.google.devtools.j2objc.types.FunctionElement; import com.google.devtools.j2objc.types.GeneratedTypeElement; import com.google.devtools.j2objc.types.GeneratedVariableElement; import com.google.devtools.j2objc.util.ElementUtil; import com.google.devtools.j2objc.util.NameTable; import com.google.devtools.j2objc.util.TypeUtil; import com.google.devtools.j2objc.util.UnicodeUtils; import com.google.j2objc.annotations.ObjectiveCName; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; /** * Modifies enum types for Objective C. * * @author Keith Stanger */ public class EnumRewriter extends UnitTreeVisitor { public EnumRewriter(CompilationUnit unit) { super(unit); } @Override public void endVisit(EnumDeclaration node) { addEnumInitialization(node); addValuesMethod(node); addValueOfMethod(node); addExtraNativeDecls(node); } private void addEnumInitialization(EnumDeclaration node) { if (node.getEnumConstants().isEmpty()) { return; } if (options.useARC()) { addArcInitialization(node); } else { if (isSimpleEnum(node)) { addSimpleNonArcInitialization(node); } else { addNonArcInitialization(node); } } } /** * Returns true if an enum doesn't have custom or renamed constructors, * vararg constructors or constants with anonymous class extensions. */ private boolean isSimpleEnum(EnumDeclaration node) { TypeElement type = node.getTypeElement(); for (EnumConstantDeclaration constant : node.getEnumConstants()) { ExecutableElement method = constant.getExecutableElement(); if (method.getParameters().size() > 0 || method.isVarArgs()) { return false; } if (ElementUtil.hasAnnotation(method, ObjectiveCName.class)) { return false; } TypeElement valueType = ElementUtil.getDeclaringClass(method); if (valueType != type) { return false; } } return true; } private void addSimpleNonArcInitialization(EnumDeclaration node) { List<EnumConstantDeclaration> constants = node.getEnumConstants(); List<Statement> stmts = node.getClassInitStatements().subList(0, 0); stmts.add(new NativeStatement("size_t objSize = class_getInstanceSize(self);")); stmts.add(new NativeStatement(UnicodeUtils.format( "size_t allocSize = %s * objSize;", constants.size()))); stmts.add(new NativeStatement("uintptr_t ptr = (uintptr_t)calloc(allocSize, 1);")); VariableElement localEnum = GeneratedVariableElement.newLocalVar("e", TypeUtil.ID_TYPE, null); stmts.add(new VariableDeclarationStatement(localEnum, null)); StringBuffer sb = new StringBuffer("id names[] = {\n "); for (EnumConstantDeclaration constant : node.getEnumConstants()) { sb.append("@\"" + ElementUtil.getName(constant.getVariableElement()) + "\", "); } sb.append("\n};"); stmts.add(new NativeStatement(sb.toString())); TypeMirror intType = typeUtil.getInt(); GeneratedVariableElement loopCounterElement = GeneratedVariableElement.newLocalVar("i", intType, TreeUtil.getEnclosingElement(node)); VariableDeclarationExpression loopCounter = new VariableDeclarationExpression().setType(Type.newType(loopCounterElement.asType())) .addFragment(new VariableDeclarationFragment( loopCounterElement, TreeUtil.newLiteral(0, typeUtil))); Expression loopTest = new InfixExpression() .setOperator(InfixExpression.Operator.LESS) .setTypeMirror(intType) .addOperand(new SimpleName(loopCounterElement)) .addOperand(TreeUtil.newLiteral(constants.size(), typeUtil)); Expression loopUpdater = new PostfixExpression(loopCounterElement, PostfixExpression.Operator.INCREMENT); Block loopBody = new Block(); stmts.add(new ForStatement() .addInitializer(loopCounter) .setExpression(loopTest) .addUpdater(loopUpdater) .setBody(loopBody)); String enumClassName = nameTable.getFullName(node.getTypeElement()); loopBody.addStatement(new NativeStatement("(" + enumClassName + "_values_[i] = e = objc_constructInstance(self, (void *)ptr), ptr += objSize);")); loopBody.addStatement(new NativeStatement(enumClassName + "_initWithNSString_withInt_(e, names[i], i);")); } private void addNonArcInitialization(EnumDeclaration node) { TypeElement type = node.getTypeElement(); int baseTypeCount = 0; List<Statement> sizeStatements = new ArrayList<>(); List<Statement> initStatements = new ArrayList<>(); TypeMirror voidType = typeUtil.getVoid(); VariableElement localEnum = GeneratedVariableElement.newLocalVar("e", TypeUtil.ID_TYPE, null); int i = 0; for (EnumConstantDeclaration constant : node.getEnumConstants()) { VariableElement varElement = constant.getVariableElement(); String varName = ElementUtil.getName(varElement); ExecutableElement methodElement = constant.getExecutableElement(); TypeElement valueType = ElementUtil.getDeclaringClass(methodElement); boolean isAnonymous = valueType != type; String classExpr = isAnonymous ? "[" + nameTable.getFullName(valueType) + " class]" : "self"; String sizeName = "objSize" + (isAnonymous ? "_" + varName : ""); if (isAnonymous) { sizeStatements.add(new NativeStatement(UnicodeUtils.format( "size_t %s = class_getInstanceSize(%s);", sizeName, classExpr))); sizeStatements.add(new NativeStatement(UnicodeUtils.format("allocSize += %s;", sizeName))); } else { baseTypeCount++; } initStatements.add(new ExpressionStatement(new CommaExpression( new Assignment(new SimpleName(varElement), new Assignment( new SimpleName(localEnum), new NativeExpression(UnicodeUtils.format( "objc_constructInstance(%s, (void *)ptr)", classExpr), type.asType()))), new NativeExpression("ptr += " + sizeName, voidType)))); String initName = nameTable.getFullFunctionName(methodElement); FunctionElement initElement = new FunctionElement(initName, voidType, valueType) .addParameters(valueType.asType()) .addParameters(ElementUtil.asTypes(methodElement.getParameters())); FunctionInvocation initFunc = new FunctionInvocation(initElement, voidType); initFunc.addArgument(new SimpleName(localEnum)); TreeUtil.copyList(constant.getArguments(), initFunc.getArguments()); initFunc.addArgument(new StringLiteral(varName, typeUtil)); initFunc.addArgument(new NumberLiteral(i++, typeUtil)); initStatements.add(new ExpressionStatement(initFunc)); } List<Statement> stmts = node.getClassInitStatements().subList(0, 0); if (baseTypeCount == 0) { stmts.add(new NativeStatement("size_t allocSize = 0;")); } else { stmts.add(new NativeStatement("size_t objSize = class_getInstanceSize(self);")); stmts.add(new NativeStatement(UnicodeUtils.format( "size_t allocSize = %s * objSize;", baseTypeCount))); } stmts.addAll(sizeStatements); stmts.add(new NativeStatement("uintptr_t ptr = (uintptr_t)calloc(allocSize, 1);")); stmts.add(new VariableDeclarationStatement(localEnum, null)); stmts.addAll(initStatements); } // ARC does not allow using "objc_constructInstance" so ARC code doesn't get // the shared allocation optimization. private void addArcInitialization(EnumDeclaration node) { List<Statement> stmts = node.getClassInitStatements().subList(0, 0); int i = 0; for (EnumConstantDeclaration constant : node.getEnumConstants()) { VariableElement varElement = constant.getVariableElement(); ClassInstanceCreation creation = new ClassInstanceCreation(constant.getExecutablePair()); TreeUtil.copyList(constant.getArguments(), creation.getArguments()); creation.addArgument(new StringLiteral(ElementUtil.getName(varElement), typeUtil)); creation.addArgument(new NumberLiteral(i++, typeUtil)); creation.setHasRetainedResult(true); stmts.add(new ExpressionStatement(new Assignment(new SimpleName(varElement), creation))); } } @Override public boolean visit(MethodDeclaration node) { ExecutableElement element = node.getExecutableElement(); TypeElement declaringClass = ElementUtil.getDeclaringClass(element); if (!ElementUtil.isConstructor(element) || !ElementUtil.isEnum(declaringClass)) { return false; } node.removeModifiers(Modifier.PUBLIC | Modifier.PROTECTED); node.addModifiers(Modifier.PRIVATE); return true; } private void addValuesMethod(EnumDeclaration node) { TypeElement type = node.getTypeElement(); ExecutableElement method = ElementUtil.findMethod(type, "values"); assert method != null : "Can't find values method on enum type."; String typeName = nameTable.getFullName(type); MethodDeclaration methodDecl = new MethodDeclaration(method); Block body = new Block(); methodDecl.setBody(body); body.addStatement(new NativeStatement(UnicodeUtils.format( " return [IOSObjectArray arrayWithObjects:%s_values_ count:%s type:%s_class_()];", typeName, node.getEnumConstants().size(), typeName))); node.addBodyDeclaration(methodDecl); } private void addValueOfMethod(EnumDeclaration node) { TypeElement type = node.getTypeElement(); ExecutableElement method = ElementUtil.findMethod(type, "valueOf", "java.lang.String"); assert method != null : "Can't find valueOf method on enum type."; String typeName = nameTable.getFullName(type); int numConstants = node.getEnumConstants().size(); VariableElement nameParam = GeneratedVariableElement.newParameter( "name", method.getParameters().get(0).asType(), method); MethodDeclaration methodDecl = new MethodDeclaration(method); methodDecl.addParameter(new SingleVariableDeclaration(nameParam)); Block body = new Block(); methodDecl.setBody(body); StringBuilder impl = new StringBuilder(); if (numConstants > 0) { impl.append(UnicodeUtils.format( " for (int i = 0; i < %s; i++) {\n" + " %s *e = %s_values_[i];\n" + " if ([name isEqual:[e name]]) {\n" + " return e;\n" + " }\n" + " }\n", numConstants, typeName, typeName)); } impl.append( " @throw create_JavaLangIllegalArgumentException_initWithNSString_(name);\n" + " return nil;"); body.addStatement(new NativeStatement(impl.toString())); node.addBodyDeclaration(methodDecl); } private void addExtraNativeDecls(EnumDeclaration node) { String typeName = nameTable.getFullName(node.getTypeElement()); int numConstants = node.getEnumConstants().size(); boolean swiftFriendly = options.swiftFriendly(); StringBuilder header = new StringBuilder(); StringBuilder implementation = new StringBuilder(); header.append("- (id)copyWithZone:(NSZone *)zone;\n"); // Append enum type suffix. String nativeName = NameTable.getNativeEnumName(typeName); // The native type is not declared for an empty enum. if (swiftFriendly && numConstants > 0) { header.append(UnicodeUtils.format("- (%s)toNSEnum;\n", nativeName)); } if (swiftFriendly && numConstants > 0) { implementation.append(UnicodeUtils.format( "- (%s)toNSEnum {\n" + " return (%s)[self ordinal];\n" + "}\n\n", nativeName, nativeName)); } // Enum constants needs to implement NSCopying. Being singletons, they can // just return self. No need to increment the retain count because enum // values are never deallocated. implementation.append("- (id)copyWithZone:(NSZone *)zone {\n return self;\n}\n"); node.addBodyDeclaration(NativeDeclaration.newInnerDeclaration( header.toString(), implementation.toString())); StringBuilder outerHeader = new StringBuilder(); StringBuilder outerImpl = new StringBuilder(); outerHeader.append(UnicodeUtils.format( "FOUNDATION_EXPORT %s *%s_fromOrdinal(NSUInteger ordinal);\n", typeName, typeName)); outerImpl.append(UnicodeUtils.format( "%s *%s_fromOrdinal(NSUInteger ordinal) {\n", typeName, typeName)); // Avoid "comparison of unsigned expression >= 0 is always true" error. if (numConstants == 0) { outerImpl.append(" return nil;\n}\n"); } else { outerImpl.append(UnicodeUtils.format( " %s_initialize();\n" // Param is unsigned, so don't need to check lower bound. + " if (ordinal >= %s) {\n" + " return nil;\n" + " }\n" + " return %s_values_[ordinal];\n" + "}\n", typeName, numConstants, typeName)); } NativeDeclaration outerDecl = NativeDeclaration.newOuterDeclaration(outerHeader.toString(), outerImpl.toString()); outerDecl.addImplementationImportType( GeneratedTypeElement.newEmulatedClass( "java.lang.IllegalArgumentException", typeUtil.resolveJavaType("java.lang.RuntimeException").asType()).asType()); node.addBodyDeclaration(outerDecl); } }