/* * 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.ArrayCreation; import com.google.devtools.j2objc.ast.Block; import com.google.devtools.j2objc.ast.CastExpression; import com.google.devtools.j2objc.ast.ClassInstanceCreation; import com.google.devtools.j2objc.ast.CompilationUnit; import com.google.devtools.j2objc.ast.CreationReference; import com.google.devtools.j2objc.ast.Expression; import com.google.devtools.j2objc.ast.ExpressionMethodReference; import com.google.devtools.j2objc.ast.ExpressionStatement; import com.google.devtools.j2objc.ast.FieldDeclaration; import com.google.devtools.j2objc.ast.FunctionalExpression; import com.google.devtools.j2objc.ast.LambdaExpression; 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.SuperConstructorInvocation; import com.google.devtools.j2objc.ast.SuperMethodInvocation; import com.google.devtools.j2objc.ast.SuperMethodReference; 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.TypeMethodReference; import com.google.devtools.j2objc.ast.UnitTreeVisitor; import com.google.devtools.j2objc.ast.VariableDeclaration; import com.google.devtools.j2objc.types.ExecutablePair; import com.google.devtools.j2objc.types.GeneratedExecutableElement; import com.google.devtools.j2objc.types.GeneratedVariableElement; import com.google.devtools.j2objc.util.CaptureInfo; import com.google.devtools.j2objc.util.ElementUtil; import com.google.devtools.j2objc.util.TypeUtil; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.ExecutableType; import javax.lang.model.type.TypeMirror; /** * Rewrites LambdaExpression nodes into TypeDeclarations. * * @author Nathan Braswell, Keith Stanger */ public class LambdaRewriter extends UnitTreeVisitor { private final CaptureInfo captureInfo; public LambdaRewriter(CompilationUnit unit) { super(unit); this.captureInfo = unit.getEnv().captureInfo(); } private class RewriteContext { private final FunctionalExpression node; private final TypeElement lambdaType; private final TypeMirror typeMirror; private ExecutableElement functionalInterface; private ExecutableType functionalInterfaceType; private TypeDeclaration typeDecl; private GeneratedExecutableElement implElement; private MethodDeclaration implDecl; private ClassInstanceCreation creation; private RewriteContext(FunctionalExpression node) { this.node = node; lambdaType = node.getTypeElement(); typeMirror = node.getTypeMirror(); resolveFunctionalInterface(); createTypeDeclaration(); createImplementation(); createCreation(); removeCastExpression(); replaceNode(); } public void resolveFunctionalInterface() { typeUtil.visitTypeHierarchy(typeMirror, baseType -> { TypeElement element = (TypeElement) baseType.asElement(); if (element.getKind().isClass()) { return true; } for (ExecutableElement method : ElementUtil.getMethods(element)) { if (!ElementUtil.isDefault(method) && !ElementUtil.isStatic(method)) { functionalInterface = method; functionalInterfaceType = typeUtil.asMemberOf(baseType, method); return false; } } return true; }); assert functionalInterface != null : "Could not find functional interface for " + typeMirror; } private void createTypeDeclaration() { typeDecl = new TypeDeclaration(lambdaType); typeDecl.setSourceRange(node.getStartPosition(), node.getLength()); TreeUtil.getEnclosingTypeBodyDeclarations(node).add(typeDecl); } private void createImplementation() { String selector = nameTable.getMethodSelector(functionalInterface); implElement = GeneratedExecutableElement.newMethodWithSelector( selector, functionalInterface.getReturnType(), lambdaType); implDecl = new MethodDeclaration(implElement); typeDecl.addBodyDeclaration(implDecl); } private void createCreation() { ExecutableElement constructorElement = GeneratedExecutableElement.newConstructor(lambdaType, typeUtil); // Add the implicit constructor to call. MethodDeclaration constructorDecl = new MethodDeclaration(constructorElement); constructorDecl.setBody(new Block()); constructorDecl.getBody().addStatement(new SuperConstructorInvocation( new ExecutablePair(getObjectConstructor()))); typeDecl.addBodyDeclaration(constructorDecl); creation = new ClassInstanceCreation( new ExecutablePair(constructorElement), lambdaType.asType()); creation.setExpression(TreeUtil.remove(node.getLambdaOuterArg())); TreeUtil.moveList(node.getLambdaCaptureArgs(), creation.getCaptureArgs()); } private void removeCastExpression() { TreeNode parent = node.getParent(); if (parent instanceof CastExpression) { parent.replaceWith(TreeUtil.remove(node)); } } private void replaceNode() { if (captureInfo.isCapturing(lambdaType)) { node.replaceWith(creation); } else { // For non-capturing lambdas, create a static final instance. VariableElement instanceVar = GeneratedVariableElement.newField( "instance", lambdaType.asType(), lambdaType) .addModifiers(Modifier.STATIC, Modifier.FINAL); typeDecl.addBodyDeclaration(new FieldDeclaration(instanceVar, creation)); node.replaceWith(new SimpleName(instanceVar)); } } private void setImplementationBody(TreeNode body) { implDecl.setBody( body instanceof Block ? (Block) body : asImplementationBlock((Expression) body)); } private Block asImplementationBlock(Expression expr) { Block block = new Block(); if (TypeUtil.isVoid(functionalInterface.getReturnType())) { block.addStatement(new ExpressionStatement(expr)); } else { block.addStatement(new ReturnStatement(expr)); } return block; } private void rewriteLambdaExpression(LambdaExpression node) { for (VariableDeclaration decl : node.getParameters()) { VariableElement var = decl.getVariableElement(); implElement.addParameter(var); implDecl.addParameter(new SingleVariableDeclaration(var)); } setImplementationBody(TreeUtil.remove(node.getBody())); } private Iterator<VariableElement> createParameters() { List<? extends TypeMirror> paramTypes = functionalInterfaceType.getParameterTypes(); List<VariableElement> params = new ArrayList<>(paramTypes.size()); int i = 0; for (TypeMirror type : paramTypes) { GeneratedVariableElement param = GeneratedVariableElement.newParameter( getParamName(i++), type, implElement); params.add(param); implElement.addParameter(param); implDecl.addParameter(new SingleVariableDeclaration(param)); } return params.iterator(); } private void forwardRemainingArgs(Iterator<VariableElement> params, List<Expression> args) { while (params.hasNext()) { args.add(new SimpleName(params.next())); } } private void rewriteCreationReference(CreationReference node) { TypeMirror creationType = node.getType().getTypeMirror(); if (TypeUtil.isArray(creationType)) { ArrayCreation creation = new ArrayCreation((ArrayType) creationType, typeUtil); forwardRemainingArgs(createParameters(), creation.getDimensions()); setImplementationBody(creation); } else { ClassInstanceCreation creation = new ClassInstanceCreation(new ExecutablePair(node.getExecutableElement()), creationType) .setVarargsType(node.getVarargsType()); forwardRemainingArgs(createParameters(), creation.getArguments()); creation.setExpression(TreeUtil.remove(node.getCreationOuterArg())); TreeUtil.moveList(node.getCreationCaptureArgs(), creation.getCaptureArgs()); setImplementationBody(creation); } } private void rewriteExpressionMethodReference(ExpressionMethodReference node) { ExecutableElement method = node.getExecutableElement(); Iterator<VariableElement> params = createParameters(); Expression invocationTarget = null; if (!ElementUtil.isStatic(method)) { VariableElement receiverField = captureInfo.getReceiverField(lambdaType); if (receiverField != null) { invocationTarget = new SimpleName(receiverField); creation.setExpression(TreeUtil.remove(node.getExpression())); } else { // The expression is actually a type name and doesn't evaluate to an invocable object. invocationTarget = new SimpleName(params.next()); } } MethodInvocation invocation = new MethodInvocation(new ExecutablePair(method), invocationTarget) .setVarargsType(node.getVarargsType()); forwardRemainingArgs(params, invocation.getArguments()); setImplementationBody(invocation); } private void rewriteSuperMethodReference(SuperMethodReference node) { SuperMethodInvocation invocation = new SuperMethodInvocation(new ExecutablePair(node.getExecutableElement())) .setVarargsType(node.getVarargsType()); invocation.setReceiver(TreeUtil.remove(node.getReceiver())); forwardRemainingArgs(createParameters(), invocation.getArguments()); setImplementationBody(invocation); } private void rewriteTypeMethodReference(TypeMethodReference node) { ExecutableElement method = node.getExecutableElement(); Iterator<VariableElement> params = createParameters(); MethodInvocation invocation = new MethodInvocation( new ExecutablePair(method), ElementUtil.isStatic(method) ? null : new SimpleName(params.next())) .setVarargsType(node.getVarargsType()); forwardRemainingArgs(params, invocation.getArguments()); setImplementationBody(invocation); } } @Override public void endVisit(LambdaExpression node) { new RewriteContext(node).rewriteLambdaExpression(node); } @Override public void endVisit(CreationReference node) { new RewriteContext(node).rewriteCreationReference(node); } @Override public void endVisit(ExpressionMethodReference node) { new RewriteContext(node).rewriteExpressionMethodReference(node); } @Override public void endVisit(SuperMethodReference node) { new RewriteContext(node).rewriteSuperMethodReference(node); } @Override public void endVisit(TypeMethodReference node) { new RewriteContext(node).rewriteTypeMethodReference(node); } private static String getParamName(int i) { StringBuilder sb = new StringBuilder(); while (true) { sb.append((char) ('a' + (i % 26))); i = i / 26; if (i == 0) { break; } i--; } return sb.reverse().toString(); } private ExecutableElement getObjectConstructor() { for (ExecutableElement constructor : ElementUtil.getConstructors(typeUtil.getJavaObject())) { if (constructor.getParameters().isEmpty()) { return constructor; } } throw new AssertionError("Can't find constructor for java.lang.Object."); } }