/* * 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.AbstractTypeDeclaration; import com.google.devtools.j2objc.ast.AnnotationTypeDeclaration; import com.google.devtools.j2objc.ast.CompilationUnit; import com.google.devtools.j2objc.ast.EnumDeclaration; import com.google.devtools.j2objc.ast.Expression; import com.google.devtools.j2objc.ast.FunctionInvocation; 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.SuperMethodInvocation; import com.google.devtools.j2objc.ast.ThisExpression; import com.google.devtools.j2objc.ast.TreeUtil; import com.google.devtools.j2objc.ast.TypeDeclaration; import com.google.devtools.j2objc.ast.UnitTreeVisitor; import com.google.devtools.j2objc.types.FunctionElement; 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 java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; /** * Some super method invocations cannot be translated directly as an ObjC super * invocation. This occurs when the invocation is qualified by an outer type or * when the containing method has been functionized. To resolve these * invocations we declare a static function pointer and look up the * implementation during static initialization. * * @author Keith Stanger */ public class SuperMethodInvocationRewriter extends UnitTreeVisitor { private Set<SuperMethodElementPair> superMethods = new LinkedHashSet<>(); private Map<TypeElement, AbstractTypeDeclaration> typeMap = new HashMap<>(); public SuperMethodInvocationRewriter(CompilationUnit unit) { super(unit); } @Override public void endVisit(CompilationUnit unit) { for (SuperMethodElementPair superMethod : superMethods) { String funcName = getSuperFunctionName(superMethod); String signature = getSuperFunctionSignature(superMethod.method); // Add declarations for the function pointers to call. unit.addNativeBlock(NativeDeclaration.newOuterDeclaration( null, "static " + UnicodeUtils.format(signature, funcName) + ";")); // Look up the implementations in the static initialization. AbstractTypeDeclaration typeNode = typeMap.get(superMethod.type); assert typeNode != null : "Type is expected to be in this compilation unit"; String superclassName = nameTable.getFullName(ElementUtil.getSuperclass(superMethod.type)); typeNode.addClassInitStatement(0, new NativeStatement(UnicodeUtils.format( "%s = (%s)[%s instanceMethodForSelector:@selector(%s)];", funcName, UnicodeUtils.format(signature, ""), superclassName, nameTable.getMethodSelector(superMethod.method)))); } } private static String getSuperFunctionSignature(ExecutableElement method) { StringBuilder signature = new StringBuilder( NameTable.getPrimitiveObjCType(method.getReturnType())); signature.append(" (*%s)(id, SEL"); for (VariableElement param : method.getParameters()) { signature.append(", ").append(NameTable.getPrimitiveObjCType(param.asType())); } signature.append(")"); return signature.toString(); } private String getSuperFunctionName(SuperMethodElementPair superMethod) { return UnicodeUtils.format( "%s_super$_%s", nameTable.getFullName(superMethod.type), nameTable.getFunctionName(superMethod.method)); } @Override public void endVisit(SuperMethodInvocation node) { Expression receiver = node.getReceiver(); ExecutableElement method = node.getExecutableElement(); TypeMirror exprType = node.getTypeMirror(); // Handle default method invocation: SomeInterface.super.method(...) if (ElementUtil.isDefault(method)) { FunctionElement element = new FunctionElement( nameTable.getFullFunctionName(method), exprType, ElementUtil.getDeclaringClass(method)) .addParameters(TypeUtil.ID_TYPE) .addParameters(ElementUtil.asTypes(method.getParameters())); FunctionInvocation invocation = new FunctionInvocation(element, exprType); List<Expression> args = invocation.getArguments(); if (receiver == null) { args.add(new ThisExpression(TreeUtil.getEnclosingTypeElement(node).asType())); } else { // OuterReferenceResolver has provided an outer path. args.add(TreeUtil.remove(receiver)); } TreeUtil.copyList(node.getArguments(), args); node.replaceWith(invocation); return; } if (receiver == null) { return; } VariableElement var = TreeUtil.getVariableElement(receiver); assert var != null : "Expected receiver to be a variable"; TypeMirror receiverType = var.asType(); TypeElement receiverElem = TypeUtil.asTypeElement(receiverType); SuperMethodElementPair superMethod = new SuperMethodElementPair(receiverElem, method); superMethods.add(superMethod); FunctionElement element = new FunctionElement(getSuperFunctionName(superMethod), exprType, receiverElem) .addParameters(receiverType, TypeUtil.ID_TYPE) .addParameters(ElementUtil.asTypes(method.getParameters())); FunctionInvocation invocation = new FunctionInvocation(element, exprType); List<Expression> args = invocation.getArguments(); args.add(TreeUtil.remove(receiver)); String selectorExpr = UnicodeUtils.format("@selector(%s)", nameTable.getMethodSelector(method)); args.add(new NativeExpression(selectorExpr, TypeUtil.ID_TYPE)); TreeUtil.copyList(node.getArguments(), args); node.replaceWith(invocation); } @Override public void endVisit(AnnotationTypeDeclaration node) { typeMap.put(node.getTypeElement(), node); } @Override public void endVisit(EnumDeclaration node) { typeMap.put(node.getTypeElement(), node); } @Override public void endVisit(TypeDeclaration node) { typeMap.put(node.getTypeElement(), node); } private static class SuperMethodElementPair { private final TypeElement type; private final ExecutableElement method; private SuperMethodElementPair(TypeElement type, ExecutableElement method) { this.type = type; this.method = method; } @Override public boolean equals(Object obj) { if (!(obj instanceof SuperMethodElementPair)) { return false; } SuperMethodElementPair other = (SuperMethodElementPair) obj; return other.type == type && other.method == method; } @Override public int hashCode() { return 31 * System.identityHashCode(type) + System.identityHashCode(method); } } }