001/*
002 *  Copyright 2013 Christopher Pheby
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.jadira.reflection.access.invokedynamic;
017
018import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
019import static org.objectweb.asm.Opcodes.ACC_SUPER;
020import static org.objectweb.asm.Opcodes.ALOAD;
021import static org.objectweb.asm.Opcodes.ARETURN;
022import static org.objectweb.asm.Opcodes.DUP;
023import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
024import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
025import static org.objectweb.asm.Opcodes.NEW;
026import static org.objectweb.asm.Opcodes.POP;
027import static org.objectweb.asm.Opcodes.RETURN;
028import static org.objectweb.asm.Opcodes.V1_7;
029
030import java.lang.reflect.Constructor;
031import java.lang.reflect.Field;
032import java.lang.reflect.Method;
033import java.lang.reflect.Modifier;
034import java.util.concurrent.ConcurrentHashMap;
035
036import org.jadira.reflection.access.AbstractClassAccess;
037import org.jadira.reflection.access.api.ClassAccess;
038import org.jadira.reflection.access.api.FieldAccess;
039import org.jadira.reflection.access.api.MethodAccess;
040import org.jadira.reflection.access.classloader.AccessClassLoader;
041import org.objectweb.asm.ClassVisitor;
042import org.objectweb.asm.ClassWriter;
043import org.objectweb.asm.MethodVisitor;
044
045/**
046 * ClassAccess implementation which uses ASM and the invokeDynamic instruction. 
047 * InvokeDynamic requests are accessed using a caching callpoint (via Dynalang) which means performance is
048 * similar to standard ASM based access
049 * @param <C> The Class to be accessed
050 */
051public abstract class InvokeDynamicClassAccess<C> extends AbstractClassAccess<C> implements ClassAccess<C> {
052
053    private static final ConcurrentHashMap<Class<?>, InvokeDynamicClassAccess<?>> CLASS_ACCESSES = new ConcurrentHashMap<Class<?>, InvokeDynamicClassAccess<?>>();
054    
055    private static final String CLASS_ACCESS_NM = ClassAccess.class.getName().replace('.', '/');
056
057    private static final String INVOKEDYNAMIC_CLASS_ACCESS_NM = InvokeDynamicClassAccess.class.getName().replace('.', '/');
058
059    private boolean isNonStaticMemberClass;
060    
061        /**
062         * Indicates if the class being accessed is a non-static member class
063         * @return True if the class is a non-static member class
064         */
065    public boolean isNonStaticMemberClass() {
066        return isNonStaticMemberClass;
067    }
068
069        /**
070         * Constructor, intended for use by generated subclasses
071         * @param clazz The Class to be accessed
072         */
073    protected InvokeDynamicClassAccess(Class<C> clazz) {
074        super(clazz);
075    }
076    
077        /**
078         * Get a new instance that can access the given Class. If the ClassAccess for this class
079         * has not been obtained before, then the specific InvokeDynamicClassAccess is created by 
080         * generating a specialised subclass of this class and returning it. 
081         * @param clazz Class to be accessed
082         * @param <C> The type of class
083         * @return New InvokeDynamicClassAccess instance
084         */
085    public static <C> InvokeDynamicClassAccess<C> get(Class<C> clazz) {
086
087        @SuppressWarnings("unchecked")
088        InvokeDynamicClassAccess<C> access = (InvokeDynamicClassAccess<C>) CLASS_ACCESSES.get(clazz);
089        if (access != null) {
090            return access;
091        }
092        
093        Class<?> enclosingType = clazz.getEnclosingClass();
094
095        final boolean isNonStaticMemberClass = determineNonStaticMemberClass(clazz, enclosingType);
096
097        String clazzName = clazz.getName();
098
099        String accessClassName = constructAccessClassName(clazzName);
100
101        Class<?> accessClass = null;
102
103        AccessClassLoader loader = AccessClassLoader.get(clazz);
104        synchronized (loader) {
105            try {
106                accessClass = loader.loadClass(accessClassName);
107            } catch (ClassNotFoundException ignored) {
108
109                String accessClassNm = accessClassName.replace('.', '/');
110                String clazzNm = clazzName.replace('.', '/');
111                String enclosingClassNm = determineEnclosingClassNm(clazz, enclosingType, isNonStaticMemberClass);
112
113                String signatureString = "L" + INVOKEDYNAMIC_CLASS_ACCESS_NM + "<L" + clazzNm + ";>;L" + CLASS_ACCESS_NM + "<L" + clazzNm + ";>;";
114
115                ClassWriter cw = new ClassWriter(0);
116
117//              TraceClassVisitor tcv = new TraceClassVisitor(cv, new PrintWriter(System.err));
118//              CheckClassAdapter cw = new CheckClassAdapter(tcv);
119
120                cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, accessClassNm, signatureString, INVOKEDYNAMIC_CLASS_ACCESS_NM, null);
121
122                enhanceForConstructor(cw, accessClassNm, clazzNm);
123
124                if (isNonStaticMemberClass) {
125                    enhanceForNewInstanceInner(cw, clazzNm, enclosingClassNm);
126                } else {
127                    enhanceForNewInstance(cw, clazzNm);
128                }
129
130                cw.visitEnd();
131
132                loader.registerClass(accessClassName, cw.toByteArray());
133
134                try {
135                    accessClass = loader.findClass(accessClassName);
136                } catch (ClassNotFoundException e) {
137                    throw new IllegalStateException("AccessClass unexpectedly could not be found", e);
138                }
139            }
140        }
141        try {
142            @SuppressWarnings("unchecked")
143            Constructor<InvokeDynamicClassAccess<C>> c = (Constructor<InvokeDynamicClassAccess<C>>) accessClass.getConstructor(new Class[] { Class.class });
144            access = c.newInstance(clazz);
145            access.isNonStaticMemberClass = isNonStaticMemberClass;
146            
147            CLASS_ACCESSES.putIfAbsent(clazz, access);
148            return access;
149        } catch (Exception ex) {
150            throw new RuntimeException("Error constructing constructor access class: " + accessClassName + "{ " + ex.getMessage() + " }", ex);
151        }
152    }
153
154    private static boolean determineNonStaticMemberClass(Class<?> clazz, Class<?> enclosingType) {
155        final boolean isNonStaticMemberClass;
156        if (enclosingType != null && clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers())) {
157            isNonStaticMemberClass = true;
158        } else {
159            isNonStaticMemberClass = false;
160        }
161        ;
162        return isNonStaticMemberClass;
163    }
164    
165    private static <C> String determineEnclosingClassNm(Class<C> clazz, Class<?> enclosingType, final boolean isNonStaticMemberClass) {
166
167        final String enclosingClassNm;
168
169        if (!isNonStaticMemberClass) {
170
171            enclosingClassNm = null;
172        } else {
173
174            enclosingClassNm = enclosingType.getName().replace('.', '/');
175        }
176        return enclosingClassNm;
177    }
178    
179    private static String constructAccessClassName(String clazzName) {
180
181        String accessClassName = clazzName + InvokeDynamicClassAccess.class.getSimpleName();
182        if (accessClassName.startsWith("java.")) {
183            accessClassName = InvokeDynamicClassAccess.class.getSimpleName().toLowerCase() + accessClassName;
184        }
185
186        return accessClassName;
187    }
188    
189    private static void enhanceForConstructor(ClassVisitor cw, String accessClassNm, String clazzNm) {
190        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/Class;)V", "(L" + clazzNm + ";)V", null);
191        mv.visitCode();
192        mv.visitVarInsn(ALOAD, 0);
193        mv.visitVarInsn(ALOAD, 1);
194        mv.visitMethodInsn(INVOKESPECIAL, INVOKEDYNAMIC_CLASS_ACCESS_NM, "<init>", "(Ljava/lang/Class;)V");
195        mv.visitInsn(RETURN);
196        mv.visitMaxs(2, 2);
197        mv.visitEnd();
198    }
199
200    private static void enhanceForNewInstance(ClassVisitor cw, String classNm) {
201        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "newInstance", "()Ljava/lang/Object;", null, null);
202
203        mv.visitCode();
204        mv.visitTypeInsn(NEW, classNm);
205        mv.visitInsn(DUP);
206        mv.visitMethodInsn(INVOKESPECIAL, classNm, "<init>", "()V");
207        mv.visitInsn(ARETURN);
208        mv.visitMaxs(2, 1);
209        mv.visitEnd();
210    }
211
212    private static void enhanceForNewInstanceInner(ClassVisitor cw, String classNm, String enclosingClassNm) {
213        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "newInstance", "()LLjava/lang/Object;", null, null);
214        mv.visitCode();
215        mv.visitTypeInsn(NEW, classNm);
216        mv.visitInsn(DUP);
217        mv.visitTypeInsn(NEW, enclosingClassNm);
218        mv.visitInsn(DUP);
219        mv.visitMethodInsn(INVOKESPECIAL, enclosingClassNm, "<init>", "()V");
220        mv.visitInsn(DUP);
221        mv.visitMethodInsn(INVOKEVIRTUAL, classNm, "getClass", "()Ljava/lang/Class;");
222        mv.visitInsn(POP);
223        mv.visitMethodInsn(INVOKESPECIAL, classNm, "<init>", "(L" + enclosingClassNm + ";)V");
224        mv.visitInsn(ARETURN);
225        mv.visitMaxs(4, 1);
226        mv.visitEnd();
227    }
228    
229    @Override
230    public abstract C newInstance();
231    
232        @Override
233        protected MethodAccess<C> constructMethodAccess(Method method) {
234                return InvokeDynamicMethodAccess.get(method);
235        }
236        
237        @Override
238        protected FieldAccess<C> constructFieldAccess(Field field) {
239                return InvokeDynamicFieldAccess.get(this, field);
240        }
241        
242        @Override
243        protected <X> ClassAccess<X> constructClassAccess(Class<X> clazz) {
244                return InvokeDynamicClassAccess.get(clazz);
245        }
246}