/* * Copyright 2017 the original author or authors. * * 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 org.gradle.api.internal.model; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.util.concurrent.UncheckedExecutionException; import groovy.lang.GroovyObject; import org.gradle.api.GradleException; import org.gradle.api.Named; import org.gradle.api.model.ObjectFactory; import org.gradle.internal.UncheckedException; import org.gradle.model.internal.asm.AsmClassGenerator; import org.gradle.model.internal.inspect.FormattingValidationProblemCollector; import org.gradle.model.internal.inspect.ValidationProblemCollector; import org.gradle.model.internal.type.ModelType; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import static org.objectweb.asm.Opcodes.*; public class DefaultObjectFactory implements ObjectFactory { public static final DefaultObjectFactory INSTANCE = new DefaultObjectFactory(); private static final Type OBJECT = Type.getType(Object.class); private static final Type STRING = Type.getType(String.class); private static final String RETURN_STRING = Type.getMethodDescriptor(STRING); private static final String RETURN_VOID_FROM_STRING = Type.getMethodDescriptor(Type.VOID_TYPE, STRING); private static final String NAME_FIELD = "__name__"; private static final String[] EMPTY_STRINGS = new String[0]; private static final String CONSTRUCTOR_NAME = "<init>"; private static final String RETURN_VOID = Type.getMethodDescriptor(Type.VOID_TYPE); private DefaultObjectFactory() { } // Currently retains strong references private final LoadingCache<Class<?>, LoadingCache<String, Object>> values = CacheBuilder.newBuilder().build(new CacheLoader<Class<?>, LoadingCache<String, Object>>() { @Override public LoadingCache<String, Object> load(Class<?> type) { return CacheBuilder.newBuilder().build(loaderFor(type)); } }); @Override public <T extends Named> T named(final Class<T> type, final String name) { try { return type.cast(values.getUnchecked(type).getUnchecked(name)); } catch (UncheckedExecutionException e) { throw UncheckedException.throwAsUncheckedException(e.getCause()); } } private ClassGeneratingLoader loaderFor(Class<?> publicClass) { // // Generate implementation class // FormattingValidationProblemCollector problemCollector = new FormattingValidationProblemCollector("Named implementation class", ModelType.of(publicClass)); visitFields(publicClass, problemCollector); if (problemCollector.hasProblems()) { throw new GradleException(problemCollector.format()); } AsmClassGenerator generator = new AsmClassGenerator(publicClass, "$Impl"); Type implementationType = generator.getGeneratedType(); ClassWriter visitor = generator.getVisitor(); Type publicType = Type.getType(publicClass); Type superClass; String[] interfaces; if (publicClass.isInterface()) { superClass = OBJECT; interfaces = new String[]{publicType.getInternalName()}; } else { superClass = publicType; interfaces = EMPTY_STRINGS; } visitor.visit(V1_5, ACC_PUBLIC | ACC_SYNTHETIC, implementationType.getInternalName(), null, superClass.getInternalName(), interfaces); // // Add name field // visitor.visitField(ACC_PRIVATE, NAME_FIELD, STRING.getDescriptor(), null, null); // // Add constructor // MethodVisitor methodVisitor = visitor.visitMethod(ACC_PUBLIC, CONSTRUCTOR_NAME, RETURN_VOID_FROM_STRING, null, EMPTY_STRINGS); methodVisitor.visitCode(); // Call this.super() methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superClass.getInternalName(), CONSTRUCTOR_NAME, RETURN_VOID, false); // Set this.name = param1 methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, implementationType.getInternalName(), NAME_FIELD, STRING.getDescriptor()); // Done methodVisitor.visitInsn(Opcodes.RETURN); methodVisitor.visitMaxs(0, 0); methodVisitor.visitEnd(); // // Add `getName()` // methodVisitor = visitor.visitMethod(ACC_PUBLIC, "getName", RETURN_STRING, null, EMPTY_STRINGS); methodVisitor.visitCode(); // return this.name methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); methodVisitor.visitFieldInsn(Opcodes.GETFIELD, implementationType.getInternalName(), NAME_FIELD, STRING.getDescriptor()); methodVisitor.visitInsn(Opcodes.ARETURN); methodVisitor.visitMaxs(0, 0); methodVisitor.visitEnd(); // // Add `toString()` // methodVisitor = visitor.visitMethod(ACC_PUBLIC, "toString", RETURN_STRING, null, EMPTY_STRINGS); methodVisitor.visitCode(); // return this.name methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); methodVisitor.visitFieldInsn(Opcodes.GETFIELD, implementationType.getInternalName(), NAME_FIELD, STRING.getDescriptor()); methodVisitor.visitInsn(Opcodes.ARETURN); methodVisitor.visitMaxs(0, 0); methodVisitor.visitEnd(); visitor.visitEnd(); generator.define(); // // Generate factory class // generator = new AsmClassGenerator(publicClass, "$Factory"); visitor = generator.getVisitor(); visitor.visit(V1_5, ACC_PUBLIC | ACC_SYNTHETIC, generator.getGeneratedType().getInternalName(), null, Type.getType(ClassGeneratingLoader.class).getInternalName(), EMPTY_STRINGS); // // Add constructor // methodVisitor = visitor.visitMethod(ACC_PUBLIC, CONSTRUCTOR_NAME, RETURN_VOID, null, EMPTY_STRINGS); methodVisitor.visitCode(); // Call this.super() methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getType(ClassGeneratingLoader.class).getInternalName(), CONSTRUCTOR_NAME, RETURN_VOID, false); // Done methodVisitor.visitInsn(Opcodes.RETURN); methodVisitor.visitMaxs(0, 0); methodVisitor.visitEnd(); // // Add factory method // methodVisitor = visitor.visitMethod(ACC_PUBLIC, "load", Type.getMethodDescriptor(OBJECT, STRING), null, EMPTY_STRINGS); methodVisitor.visitCode(); // Call return new <implClass>(param1) methodVisitor.visitTypeInsn(Opcodes.NEW, implementationType.getInternalName()); methodVisitor.visitInsn(Opcodes.DUP); methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, implementationType.getInternalName(), CONSTRUCTOR_NAME, RETURN_VOID_FROM_STRING, false); methodVisitor.visitInsn(Opcodes.ARETURN); methodVisitor.visitMaxs(0, 0); methodVisitor.visitEnd(); visitor.visitEnd(); Class<Object> factoryClass = generator.define(); try { return (ClassGeneratingLoader) (factoryClass.newInstance()); } catch (Exception e) { throw UncheckedException.throwAsUncheckedException(e); } } private void visitFields(Class<?> type, ValidationProblemCollector collector) { if (type.equals(Object.class)) { return; } if (type.getSuperclass() != null) { visitFields(type.getSuperclass(), collector); } // Disallow instance fields. This doesn't guarantee that the object is immutable, just makes it less likely // We might tighten this constraint to also disallow any _code_ on immutable types that reaches out to static state for (Field field : type.getDeclaredFields()) { if (Modifier.isStatic(field.getModifiers()) || GroovyObject.class.isAssignableFrom(type) && field.getName().equals("metaClass")) { continue; } collector.add(field, "A Named implementation class must not define any instance fields."); } } protected abstract static class ClassGeneratingLoader extends CacheLoader<String, Object> { @Override public abstract Object load(String name); } }