/* * 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.util; import com.google.devtools.j2objc.Options; import com.google.devtools.j2objc.ast.AbstractTypeDeclaration; import com.google.devtools.j2objc.ast.ArrayAccess; import com.google.devtools.j2objc.ast.ArrayCreation; import com.google.devtools.j2objc.ast.ArrayInitializer; import com.google.devtools.j2objc.ast.Assignment; import com.google.devtools.j2objc.ast.CastExpression; import com.google.devtools.j2objc.ast.ClassInstanceCreation; import com.google.devtools.j2objc.ast.ConditionalExpression; import com.google.devtools.j2objc.ast.EnumDeclaration; import com.google.devtools.j2objc.ast.Expression; import com.google.devtools.j2objc.ast.FieldAccess; import com.google.devtools.j2objc.ast.FunctionInvocation; import com.google.devtools.j2objc.ast.InfixExpression; import com.google.devtools.j2objc.ast.NullLiteral; import com.google.devtools.j2objc.ast.PackageDeclaration; import com.google.devtools.j2objc.ast.ParenthesizedExpression; import com.google.devtools.j2objc.ast.PostfixExpression; import com.google.devtools.j2objc.ast.PrefixExpression; import com.google.devtools.j2objc.ast.SimpleName; import com.google.devtools.j2objc.ast.TreeNode; import com.google.devtools.j2objc.ast.TreeUtil; import com.google.devtools.j2objc.ast.TypeDeclaration; import com.google.devtools.j2objc.ast.TypeLiteral; import com.google.devtools.j2objc.types.FunctionElement; import com.google.j2objc.annotations.ReflectionSupport; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; /** * General collection of utility methods. * * @author Keith Stanger, Tom Ball */ public final class TranslationUtil { private final TypeUtil typeUtil; private final NameTable nameTable; private final Options options; private final ElementUtil elementUtil; private final URLClassLoader jreEmulLoader; public TranslationUtil(TypeUtil typeUtil, NameTable nameTable, Options options, ElementUtil elementUtil) { this.typeUtil = typeUtil; this.nameTable = nameTable; this.options = options; this.elementUtil = elementUtil; this.jreEmulLoader = getJreEmulClassPath(options); } public static TypeElement getSuperType(AbstractTypeDeclaration node) { // Use the AST as the source of truth where possible. if (node instanceof TypeDeclaration) { TypeMirror superclassTypeMirror = ((TypeDeclaration) node).getSuperclassTypeMirror(); return superclassTypeMirror == null ? null : TypeUtil.asTypeElement(superclassTypeMirror); } else { return ElementUtil.getSuperclass(node.getTypeElement()); } } public static List<TypeElement> getInterfaceTypes(AbstractTypeDeclaration node) { // Use the AST as the source of truth where possible. List<? extends TypeMirror> astInterfaces = null; if (node instanceof TypeDeclaration) { astInterfaces = ((TypeDeclaration) node).getSuperInterfaceTypeMirrors(); } else if (node instanceof EnumDeclaration) { astInterfaces = ((EnumDeclaration) node).getSuperInterfaceTypeMirrors(); } else { // AnnotationTypeDeclaration return ElementUtil.getInterfaces(node.getTypeElement()); } List<TypeElement> result = new ArrayList<>(); for (TypeMirror typeMirror : astInterfaces) { result.add(TypeUtil.asTypeElement(typeMirror)); } return result; } public boolean needsReflection(AbstractTypeDeclaration node) { return needsReflection(node.getTypeElement()); } public boolean needsReflection(PackageDeclaration node) { ReflectionSupport.Level level = getReflectionSupportLevelOnPackage(node.getPackageElement()); return needsReflection(level); } public boolean needsReflection(TypeElement type) { if (ElementUtil.isLambda(type)) { return false; } PackageElement packageElement = ElementUtil.getPackage(type); ReflectionSupport.Level level = null; while (type != null) { level = getReflectionSupportLevel(ElementUtil.getAnnotation(type, ReflectionSupport.class)); if (level != null) { return level == ReflectionSupport.Level.FULL; } type = ElementUtil.getDeclaringClass(type); } // Check package level annotations level = getReflectionSupportLevelOnPackage(packageElement); return needsReflection(level); } private boolean needsReflection(ReflectionSupport.Level level) { if (level != null) { return level == ReflectionSupport.Level.FULL; } else { return !options.stripReflection(); } } private ReflectionSupport.Level getReflectionSupportLevelOnPackage(PackageElement node) { ReflectionSupport.Level level = getReflectionSupportLevel( ElementUtil.getAnnotation(node, ReflectionSupport.class)); if (level != null) { return level; } // Check if package-info.java contains ReflectionSupport annotation level = options.getPackageInfoLookup().getReflectionSupportLevel( node.getSimpleName().toString()); return level; } public static ReflectionSupport.Level getReflectionSupportLevel( AnnotationMirror reflectionSupport) { if (reflectionSupport == null) { return null; } VariableElement level = (VariableElement) ElementUtil.getAnnotationValue(reflectionSupport, "value"); return level != null ? ReflectionSupport.Level.valueOf(level.getSimpleName().toString()) : null; } /** * If possible give this expression an unbalanced extra retain. If a non-null * result is returned, then the returned expression has an unbalanced extra * retain and the passed in expression is removed from the tree and must be * discarded. If null is returned then the passed in expression is left * untouched. The caller must ensure the result is eventually consumed. */ public static Expression retainResult(Expression node) { switch (node.getKind()) { case ARRAY_CREATION: ((ArrayCreation) node).setHasRetainedResult(true); return TreeUtil.remove(node); case CLASS_INSTANCE_CREATION: ((ClassInstanceCreation) node).setHasRetainedResult(true); return TreeUtil.remove(node); case FUNCTION_INVOCATION: { FunctionInvocation invocation = (FunctionInvocation) node; if (invocation.getFunctionElement().getRetainedResultName() != null) { invocation.setHasRetainedResult(true); return TreeUtil.remove(node); } return null; } default: return null; } } public static boolean isAssigned(Expression node) { TreeNode parent = node.getParent(); while (parent instanceof ParenthesizedExpression) { parent = parent.getParent(); } if (parent instanceof PostfixExpression) { PostfixExpression.Operator op = ((PostfixExpression) parent).getOperator(); if (op == PostfixExpression.Operator.INCREMENT || op == PostfixExpression.Operator.DECREMENT) { return true; } } else if (parent instanceof PrefixExpression) { PrefixExpression.Operator op = ((PrefixExpression) parent).getOperator(); if (op == PrefixExpression.Operator.INCREMENT || op == PrefixExpression.Operator.DECREMENT || op == PrefixExpression.Operator.ADDRESS_OF) { return true; } } else if (parent instanceof Assignment) { return node == ((Assignment) parent).getLeftHandSide(); } return false; } /** * Reterns whether the expression might have any side effects. If true, it * would be unsafe to prune the given node from the tree. */ public static boolean hasSideEffect(Expression expr) { VariableElement var = TreeUtil.getVariableElement(expr); if (var != null && ElementUtil.isVolatile(var)) { return true; } switch (expr.getKind()) { case BOOLEAN_LITERAL: case CHARACTER_LITERAL: case NULL_LITERAL: case NUMBER_LITERAL: case QUALIFIED_NAME: case SIMPLE_NAME: case STRING_LITERAL: case SUPER_FIELD_ACCESS: case THIS_EXPRESSION: return false; case CAST_EXPRESSION: return hasSideEffect(((CastExpression) expr).getExpression()); case CONDITIONAL_EXPRESSION: { ConditionalExpression condExpr = (ConditionalExpression) expr; return hasSideEffect(condExpr.getExpression()) || hasSideEffect(condExpr.getThenExpression()) || hasSideEffect(condExpr.getElseExpression()); } case FIELD_ACCESS: return hasSideEffect(((FieldAccess) expr).getExpression()); case INFIX_EXPRESSION: for (Expression operand : ((InfixExpression) expr).getOperands()) { if (hasSideEffect(operand)) { return true; } } return false; case PARENTHESIZED_EXPRESSION: return hasSideEffect(((ParenthesizedExpression) expr).getExpression()); case PREFIX_EXPRESSION: { PrefixExpression preExpr = (PrefixExpression) expr; PrefixExpression.Operator op = preExpr.getOperator(); return op == PrefixExpression.Operator.INCREMENT || op == PrefixExpression.Operator.DECREMENT || hasSideEffect(preExpr.getOperand()); } default: return true; } } /** * Returns the modifier for an assignment expression being converted to a * function. The result will be "Array" if the lhs is an array access, * "Strong" if the lhs is a field with a strong reference, and an empty string * for local variables and weak fields. */ public String getOperatorFunctionModifier(Expression expr) { VariableElement var = TreeUtil.getVariableElement(expr); if (var == null) { assert TreeUtil.trimParentheses(expr) instanceof ArrayAccess : "Expression cannot be resolved to a variable or array access."; return "Array"; } String modifier = ""; if (ElementUtil.isVolatile(var)) { modifier += "Volatile"; } if (!ElementUtil.isWeakReference(var) && (var.getKind().isField() || options.useARC())) { modifier += "Strong"; } return modifier; } public Expression createObjectArray(List<Expression> expressions, ArrayType arrayType) { if (expressions.isEmpty()) { return new ArrayCreation(arrayType, typeUtil, 0); } ArrayCreation creation = new ArrayCreation(arrayType, typeUtil); ArrayInitializer initializer = new ArrayInitializer(arrayType); initializer.getExpressions().addAll(expressions); creation.setInitializer(initializer); return creation; } public Expression createAnnotation(AnnotationMirror annotationMirror) { DeclaredType type = annotationMirror.getAnnotationType(); TypeElement typeElem = (TypeElement) type.asElement(); FunctionElement element = new FunctionElement("create_" + nameTable.getFullName(typeElem), type, typeElem); FunctionInvocation invocation = new FunctionInvocation(element, type); Map<? extends ExecutableElement, ? extends AnnotationValue> values = typeUtil.elementUtil().getElementValuesWithDefaults(annotationMirror); for (ExecutableElement member : ElementUtil.getSortedAnnotationMembers(typeElem)) { TypeMirror valueType = member.getReturnType(); element.addParameters(valueType); invocation.addArgument(createAnnotationValue(valueType, values.get(member))); } return invocation; } public Expression createAnnotationValue(TypeMirror type, AnnotationValue aValue) { Object value = aValue.getValue(); if (value == null) { return new NullLiteral(typeUtil.getNull()); } else if (value instanceof VariableElement) { return new SimpleName((VariableElement) value); } else if (TypeUtil.isArray(type)) { assert value instanceof List; ArrayType arrayType = (ArrayType) type; @SuppressWarnings("unchecked") List<? extends AnnotationValue> list = (List<? extends AnnotationValue>) value; List<Expression> generatedValues = new ArrayList<>(); for (AnnotationValue elem : list) { generatedValues.add(createAnnotationValue(arrayType.getComponentType(), elem)); } return createObjectArray(generatedValues, arrayType); } else if (TypeUtil.isAnnotation(type)) { assert value instanceof AnnotationMirror; return createAnnotation((AnnotationMirror) value); } else if (value instanceof TypeMirror) { return new TypeLiteral((TypeMirror) value, typeUtil); } else { // Boolean, Character, Number, String return TreeUtil.newLiteral(value, typeUtil); } } /** * Returns true if an implementation for a type element should be generated. * Normally this is true unless the type is defined in the translator's * jre_emul.jar, to avoid duplicate types causing link errors. Types defined * on the system bootclasspath are ignored, since translating them won't * cause link errors later. * <p> * If the <code>-Xtranslate-bootclasspath</code> flag is specified * (normally only when building the jre_emul libraries), then types * are always generated. */ public boolean generateImplementation(TypeElement typeElement) { if (options.translateBootclasspathFiles()) { return true; } String className = elementUtil.getBinaryName(typeElement).replace('.', '/'); String resourcePath = className.replace('.', '/') + ".class"; return jreEmulLoader.findResource(resourcePath) == null; } private URLClassLoader getJreEmulClassPath(Options options) { List<URL> bootURLs = new ArrayList<>(); for (String path : options.getBootClasspath()) { if (path.matches("^.*jre_emul.*jar$")) { try { bootURLs.add(new File(path).toURI().toURL()); } catch (MalformedURLException e) { // Ignore bad path. } } } return new URLClassLoader(bootURLs.toArray(new URL[0])); } }