/* * Copyright 2012 Jason Miller * * 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 jj.util; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import javassist.ClassPath; import javassist.ClassPool; import javassist.CtBehavior; import javassist.CtClass; import javassist.LoaderClassPath; import javassist.NotFoundException; import javassist.bytecode.AnnotationsAttribute; import javassist.bytecode.ClassFile; import javassist.bytecode.ConstPool; import javassist.bytecode.annotation.Annotation; /** * <p> * Some useful stuff for code gen and dealing with types generically * * <p> * All codegen systems should get their {@link ClassPool} from this location. This is * particularly important if you want to generate classes based on other * generated classes (as in event subscription) because there needs to be * some sort of app-level storage for the generated bytes, since javassist * doesn't keep them, despite misleading documentation to the contrary. * * @author jason * */ public enum CodeGenHelper { ; /** * Adds a runtime-visible annotation to the given method, which can be a constructor. * The {@link Annotation} is returned if values need to be added */ public static Annotation addAnnotationToMethod(final CtBehavior method, final Class<? extends java.lang.annotation.Annotation> annotation) { CtClass ctClass = method.getDeclaringClass(); ClassFile ccFile = ctClass.getClassFile(); ConstPool constpool = ccFile.getConstPool(); AnnotationsAttribute attr = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag); Annotation a = new Annotation(annotation.getName(), constpool); attr.addAnnotation(a); method.getMethodInfo().addAttribute(attr); return a; } /** * Adds a runtime-visible annotation to the given class. * The {@link Annotation} is returned if values need to be added */ public static Annotation addAnnotationToClass(final CtClass ctClass, final Class<? extends java.lang.annotation.Annotation> annotation) { ClassFile ccFile = ctClass.getClassFile(); ConstPool constpool = ccFile.getConstPool(); AnnotationsAttribute attr = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag); Annotation a = new Annotation(annotation.getName(), constpool); attr.addAnnotation(a); ccFile.addAttribute(attr); return a; } /** * Holds on to generated class bytes. Shared across all {@link ClassPool}s in the system * @author jason * */ private static final class MemoryClassPath implements ClassPath { final ConcurrentMap<String, byte[]> classBytes = new ConcurrentHashMap<>(); @Override public InputStream openClassfile(String classname) throws NotFoundException { return classBytes.containsKey(classname) ? new ByteArrayInputStream(classBytes.get(classname)) : null; } @Override public URL find(String classname) { try { return classBytes.containsKey(classname) ? new URL("file:/" + classname) : null; } catch (MalformedURLException e) { throw new AssertionError(e); } } @Override public void close() { classBytes.clear(); } } private static final MemoryClassPath memoryClassPath = new MemoryClassPath(); /** * Stores a (presumably) generated {@link CtClass} for common access via all * {@link ClassPool}s */ public static void storeGeneratedClass(CtClass clazz) throws Exception { clazz.stopPruning(true); memoryClassPath.classBytes.putIfAbsent(clazz.getName(), clazz.toBytecode()); clazz.stopPruning(false); } private static final ThreadLocal<ClassPool> classPool = new ThreadLocal<ClassPool>() { @Override protected ClassPool initialValue() { ClassPool classPool = new ClassPool(); classPool.appendClassPath(new LoaderClassPath(CodeGenHelper.class.getClassLoader())); classPool.appendClassPath(memoryClassPath); return classPool; } }; /** * Produces a {@link ClassPool} for code generation. instances are per-thread to reduce contention */ public static ClassPool classPool() { return classPool.get(); } public static final Map<Class<?>, Class<?>> wrappersToPrimitives; public static final Map<Class<?>, Class<?>> primitivesToWrappers; public static final Map<String, Class<?>> primitiveNamesToWrappers; public static final Map<String, String> primitiveDefaults; static { Map<Class<?>, Class<?>> builder = new HashMap<>(); builder.put(Boolean.class, Boolean.TYPE); builder.put(Character.class, Character.TYPE); builder.put(Byte.class, Byte.TYPE); builder.put(Short.class, Short.TYPE); builder.put(Integer.class, Integer.TYPE); builder.put(Long.class, Long.TYPE); builder.put(Float.class, Float.TYPE); builder.put(Double.class, Double.TYPE); wrappersToPrimitives = Collections.unmodifiableMap(builder); builder = new HashMap<>(); for (Class<?> wrapper : wrappersToPrimitives.keySet()) { builder.put(wrappersToPrimitives.get(wrapper), wrapper); } primitivesToWrappers = Collections.unmodifiableMap(builder); Map<String, Class<?>> builder2 = new HashMap<>(); for (Class<?> primitive : primitivesToWrappers.keySet()) { builder2.put(primitive.getName(), primitivesToWrappers.get(primitive)); } primitiveNamesToWrappers = Collections.unmodifiableMap(builder2); Map<String, String> builder3 = new HashMap<>(); builder3.put(Boolean.TYPE.getName(), "false"); builder3.put(Character.TYPE.getName(), "(char)0"); builder3.put(Byte.TYPE.getName(), "(byte)0"); builder3.put(Short.TYPE.getName(), "(short)0"); builder3.put(Integer.TYPE.getName(), "0"); builder3.put(Long.TYPE.getName(), "0L"); builder3.put(Float.TYPE.getName(), "0F"); builder3.put(Double.TYPE.getName(), "0.0"); Map<String, String> builder4 = new HashMap<>(); for (String type : builder3.keySet()) { builder4.put(primitiveNamesToWrappers.get(type).getName(), builder3.get(type)); } builder3.putAll(builder4); primitiveDefaults = Collections.unmodifiableMap(builder3); } }