All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.auto.value.processor.ForwardingClassGenerator Maven / Gradle / Ivy

There is a newer version: 2.0.31
Show newest version
/*
 * Copyright 2022 Google LLC
 *
 * 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.auto.value.processor;

import static com.google.auto.common.MoreTypes.asArray;
import static com.google.auto.common.MoreTypes.asTypeElement;
import static java.util.stream.Collectors.joining;
import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
import static org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
import static org.objectweb.asm.Opcodes.ACC_SUPER;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ARETURN;
import static org.objectweb.asm.Opcodes.DLOAD;
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.FLOAD;
import static org.objectweb.asm.Opcodes.ILOAD;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.LLOAD;
import static org.objectweb.asm.Opcodes.NEW;
import static org.objectweb.asm.Opcodes.V1_8;

import com.google.auto.common.MoreElements;
import com.google.common.collect.ImmutableList;
import javax.lang.model.element.NestingKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;

/**
 * Generates a class that invokes the constructor of another class.
 *
 * 

The point here is that the constructor might be synthetic, in which case it can't be called * directly from Java source code. Say we want to call the constructor {@code ConstructMe(int, * String, long)} with parameters {@code 1, "2", 3L}. If the constructor is synthetic, then Java * source code can't just do {@code new ConstructMe(1, "2", 3L)}. So this class allows you to * generate a class file, say {@code Forwarder}, that is basically what you would get if you could * compile this: * *

 * final class Forwarder {
 *   private Forwarder() {}
 *
 *   static ConstructMe of(int a, String b, long c) {
 *     return new ConstructMe(a, b, c);
 *   }
 * }
 * 
* *

Because the class file is assembled directly, rather than being produced by the Java compiler, * it can call the synthetic constructor. Then regular Java source code can do {@code * Forwarder.of(1, "2", 3L)} to call the constructor. */ final class ForwardingClassGenerator { /** * Assembles a class with a static method {@code of} that calls the constructor of another class * with the same parameters. * *

It would be simpler if we could just pass in an {@code ExecutableElement} representing the * constructor, but if it is synthetic then it won't be visible to the {@code javax.lang.model} * APIs. So we have to pass the constructed type and the constructor parameter types separately. * * @param forwardingClassName the fully-qualified name of the class to generate * @param classToConstruct the type whose constructor will be invoked ({@code ConstructMe} in the * example above) * @param constructorParameters the erased types of the constructor parameters, which will also be * the types of the generated {@code of} method. We require the types to be erased so as not * to require an instance of the {@code Types} interface to erase them here. Having to deal * with generics would complicate things unnecessarily. * @return a byte array making up the new class file */ static byte[] makeConstructorForwarder( String forwardingClassName, TypeMirror classToConstruct, ImmutableList constructorParameters) { ClassWriter classWriter = new ClassWriter(COMPUTE_MAXS); classWriter.visit( V1_8, ACC_FINAL | ACC_SUPER, internalName(forwardingClassName), null, "java/lang/Object", null); classWriter.visitSource(forwardingClassName, null); // Generate the `of` method. // TODO(emcmanus): cleaner generics. If we're constructing Foo then we should // generate a generic signature for the `of` method, as if the Java declaration were this: // static Foo of(...) // Currently we just generate: // static Foo of(...) // which returns the raw Foo type. String parameterSignature = constructorParameters.stream() .map(ForwardingClassGenerator::signatureEncoding) .collect(joining("")); String internalClassToConstruct = internalName(asTypeElement(classToConstruct)); String ofMethodSignature = "(" + parameterSignature + ")L" + internalClassToConstruct + ";"; MethodVisitor ofMethodVisitor = classWriter.visitMethod(ACC_STATIC, "of", ofMethodSignature, null, null); ofMethodVisitor.visitCode(); // The remaining instructions are basically what ASMifier generates for a class like the // `Forwarder` class in the example above. ofMethodVisitor.visitTypeInsn(NEW, internalClassToConstruct); ofMethodVisitor.visitInsn(DUP); int local = 0; for (TypeMirror type : constructorParameters) { ofMethodVisitor.visitVarInsn(loadInstruction(type), local); local += localSize(type); } String constructorToCallSignature = "(" + parameterSignature + ")V"; ofMethodVisitor.visitMethodInsn( INVOKESPECIAL, internalClassToConstruct, "", constructorToCallSignature, /* isInterface= */ false); ofMethodVisitor.visitInsn(ARETURN); ofMethodVisitor.visitMaxs(0, 0); ofMethodVisitor.visitEnd(); classWriter.visitEnd(); return classWriter.toByteArray(); } /** The bytecode instruction that copies a parameter of the given type onto the JVM stack. */ private static int loadInstruction(TypeMirror type) { switch (type.getKind()) { case DECLARED: case ARRAY: return ALOAD; case LONG: return LLOAD; case FLOAT: return FLOAD; case DOUBLE: return DLOAD; case BYTE: case SHORT: case CHAR: case INT: case BOOLEAN: // These are all represented as int local variables. return ILOAD; default: // We expect the caller to have erased the parameters so we shouldn't be seeing type // variables or whatever. throw new IllegalArgumentException("Unexpected type " + type); } } /** * The size in the local variable array of a value of the given type. A quirk of the JVM means * that long and double variables each take up two consecutive slots in the local variable array. * (The first n local variables are the parameters, so we need to know their sizes when iterating * over them.) */ private static int localSize(TypeMirror type) { switch (type.getKind()) { case LONG: case DOUBLE: return 2; default: return 1; } } private static String internalName(String className) { return className.replace('.', '/'); } /** * Given a class like {@code foo.bar.Outer.Inner}, produces a string like {@code * "foo/bar/Outer$Inner"}, which is the way the class is referenced in the JVM. */ private static String internalName(TypeElement typeElement) { if (typeElement.getNestingKind().equals(NestingKind.MEMBER)) { TypeElement enclosing = MoreElements.asType(typeElement.getEnclosingElement()); return internalName(enclosing) + "$" + typeElement.getSimpleName(); } return internalName(typeElement.getQualifiedName().toString()); } private static String signatureEncoding(TypeMirror type) { switch (type.getKind()) { case ARRAY: return "[" + signatureEncoding(asArray(type).getComponentType()); case BYTE: return "B"; case SHORT: return "S"; case INT: return "I"; case LONG: return "J"; case FLOAT: return "F"; case DOUBLE: return "D"; case CHAR: return "C"; case BOOLEAN: return "Z"; case DECLARED: return "L" + internalName(asTypeElement(type)) + ";"; default: throw new AssertionError("Bad signature type " + type); } } private ForwardingClassGenerator() {} }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy