/* * Copyright 2016 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.j2objc.translate; import com.google.common.collect.Iterables; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.SetMultimap; import com.google.devtools.j2objc.ast.AbstractTypeDeclaration; import com.google.devtools.j2objc.ast.Block; 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.ExpressionStatement; import com.google.devtools.j2objc.ast.FunctionInvocation; import com.google.devtools.j2objc.ast.MethodDeclaration; import com.google.devtools.j2objc.ast.MethodInvocation; import com.google.devtools.j2objc.ast.ReturnStatement; import com.google.devtools.j2objc.ast.SimpleName; import com.google.devtools.j2objc.ast.SingleVariableDeclaration; import com.google.devtools.j2objc.ast.ThisExpression; import com.google.devtools.j2objc.ast.TypeDeclaration; import com.google.devtools.j2objc.ast.UnitTreeVisitor; import com.google.devtools.j2objc.types.ExecutablePair; import com.google.devtools.j2objc.types.FunctionElement; import com.google.devtools.j2objc.types.GeneratedExecutableElement; import com.google.devtools.j2objc.types.GeneratedVariableElement; import com.google.devtools.j2objc.util.CodeReferenceMap; import com.google.devtools.j2objc.util.ElementUtil; import com.google.devtools.j2objc.util.TypeUtil; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.ExecutableType; import javax.lang.model.type.TypeMirror; /** * Generate shims for classes and enums that implement interfaces with default methods. Each shim * calls the functionalized default method implementation defined in the interface. Also generates * delegating shims for inherited methods that declare a different selector than the implementation. * * @author Lukhnos Liu, Keith Stanger */ public class DefaultMethodShimGenerator extends UnitTreeVisitor { private CodeReferenceMap deadCodeMap; public DefaultMethodShimGenerator(CompilationUnit unit, CodeReferenceMap deadCodeMap) { super(unit); this.deadCodeMap = deadCodeMap; } /** * Handles adding all the shims for a single type and manages state for collecting inherited * methods of the type. */ private class TypeFixer { private final AbstractTypeDeclaration typeNode; private final TypeElement typeElem; private final Set<TypeElement> visitedTypes = new HashSet<>(); private final SetMultimap<String, ExecutablePair> existingMethods = LinkedHashMultimap.create(); private final SetMultimap<String, ExecutablePair> newMethods = LinkedHashMultimap.create(); private SetMultimap<String, ExecutablePair> collector = existingMethods; private TypeFixer(AbstractTypeDeclaration node) { typeNode = node; typeElem = node.getTypeElement(); } private void visit() { // Collect existing methods. collectMethods((DeclaredType) typeElem.asType()); collectInheritedMethods(typeElem.getSuperclass()); // Collect new methods from newly implemented interfaces. collector = newMethods; for (TypeMirror t : typeElem.getInterfaces()) { collectInheritedMethods((DeclaredType) t); } for (String signature : newMethods.keySet()) { fixNewMethods(signature); } } private void collectInheritedMethods(TypeMirror type) { if (TypeUtil.isNone(type)) { return; } collectMethods((DeclaredType) type); for (TypeMirror supertype : typeUtil.directSupertypes(type)) { collectInheritedMethods(supertype); } } private void collectMethods(DeclaredType type) { TypeElement typeElem = TypeUtil.asTypeElement(type); if (visitedTypes.contains(typeElem)) { return; } visitedTypes.add(typeElem); for (ExecutableElement methodElem : Iterables.filter( ElementUtil.getMethods(typeElem), ElementUtil::isInstanceMethod)) { if (!isDeadMethod(methodElem)) { ExecutablePair method = new ExecutablePair( methodElem, typeUtil.asMemberOf(type, methodElem)); collector.put(getOverrideSignature(method), method); } } } private void fixNewMethods(String signature) { Set<ExecutablePair> existingMethods = this.existingMethods.get(signature); Set<ExecutablePair> newMethods = this.newMethods.get(signature); Iterable<ExecutablePair> allMethods = Iterables.concat(existingMethods, newMethods); ExecutablePair first = allMethods.iterator().next(); String mainSelector = nameTable.getMethodSelector(first.element()); ExecutablePair impl = resolveImplementation(allMethods); // Find the set of selectors for this method that don't yet have shims in a superclass. Set<String> existingSelectors = new HashSet<>(); Map<String, ExecutablePair> newSelectors = new LinkedHashMap<>(); for (ExecutablePair method : existingMethods) { existingSelectors.add(nameTable.getMethodSelector(method.element())); } existingSelectors.add(mainSelector); for (ExecutablePair method : newMethods) { String sel = nameTable.getMethodSelector(method.element()); if (!existingSelectors.contains(sel) && !newSelectors.containsKey(sel)) { newSelectors.put(sel, method); } } if (newMethods.contains(impl)) { if (ElementUtil.isDefault(impl.element())) { addDefaultMethodShim(mainSelector, impl); } else { // Must be an abstract class. unit.setHasIncompleteProtocol(); } } for (Map.Entry<String, ExecutablePair> entry : newSelectors.entrySet()) { addRenamingMethodShim(entry.getKey(), entry.getValue(), impl); } } private boolean isDeadMethod(ExecutableElement methodElem) { return deadCodeMap != null && deadCodeMap.containsMethod(methodElem, typeUtil); } private ExecutablePair resolveImplementation(Iterable<ExecutablePair> allMethods) { ExecutablePair impl = null; for (ExecutablePair method : allMethods) { if (takesPrecedence(method, impl)) { impl = method; } } return impl; } private boolean declaredByClass(ExecutableElement e) { return ElementUtil.getDeclaringClass(e).getKind().isClass(); } private boolean takesPrecedence(ExecutablePair a, ExecutablePair b) { return b == null || (!declaredByClass(b.element()) && declaredByClass(a.element())) || elementUtil.overrides( a.element(), b.element(), ElementUtil.getDeclaringClass(a.element())); } private void addShimWithInvocation( String selector, ExecutablePair method, Expression invocation, List<Expression> args) { GeneratedExecutableElement element = GeneratedExecutableElement.newMethodWithSelector( selector, method.type().getReturnType(), typeElem) .addModifiers(method.element().getModifiers()) .removeModifiers(Modifier.ABSTRACT, Modifier.DEFAULT); MethodDeclaration methodDecl = new MethodDeclaration(element); methodDecl.setHasDeclaration(false); int i = 0; for (TypeMirror paramType : method.type().getParameterTypes()) { GeneratedVariableElement newParam = GeneratedVariableElement.newParameter( "arg" + i++, paramType, element); element.addParameter(newParam); methodDecl.addParameter(new SingleVariableDeclaration(newParam)); args.add(new SimpleName(newParam)); } Block block = new Block(); block.addStatement(TypeUtil.isVoid(method.element().getReturnType()) ? new ExpressionStatement(invocation) : new ReturnStatement(invocation)); methodDecl.setBody(block); typeNode.addBodyDeclaration(methodDecl); } private void addDefaultMethodShim(String selector, ExecutablePair method) { // The shim's only purpose is to call the default method implementation and returns it value // if required. TypeElement declaringClass = ElementUtil.getDeclaringClass(method.element()); String name = nameTable.getFullFunctionName(method.element()); FunctionElement funcElement = new FunctionElement( name, method.element().getReturnType(), declaringClass) .addParameters(declaringClass.asType()) .addParameters(((ExecutableType) method.element().asType()).getParameterTypes()); FunctionInvocation invocation = new FunctionInvocation(funcElement, method.type().getReturnType()); // All default method implementations require self as the first function call argument. invocation.addArgument(new ThisExpression(typeElem.asType())); addShimWithInvocation(selector, method, invocation, invocation.getArguments()); } private void addRenamingMethodShim( String selector, ExecutablePair method, ExecutablePair delegate) { MethodInvocation invocation = new MethodInvocation(delegate, null); addShimWithInvocation(selector, method, invocation, invocation.getArguments()); } } // Generates a signature that will be the same for methods that can override each other and unique // otherwise. Used as a key to group inherited methods together. private String getOverrideSignature(ExecutablePair method) { StringBuilder sb = new StringBuilder(ElementUtil.getName(method.element())); sb.append('('); for (TypeMirror pType : method.type().getParameterTypes()) { sb.append(typeUtil.getSignatureName(pType)); } sb.append(')'); return sb.toString(); } @Override public void endVisit(EnumDeclaration node) { new TypeFixer(node).visit(); } @Override public void endVisit(TypeDeclaration node) { if (!node.isInterface()) { new TypeFixer(node).visit(); } } }