package mil.nga.giat.geowave.core.cli.prefix; import java.lang.annotation.ElementType; import java.lang.annotation.Target; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javassist.ClassPool; import javassist.CtClass; import javassist.CtField; import javassist.CtMethod; import javassist.NotFoundException; import javassist.bytecode.AnnotationsAttribute; import javassist.bytecode.ConstPool; import javassist.bytecode.Descriptor; import javassist.bytecode.annotation.Annotation; import javassist.bytecode.annotation.MemberValue; /** * These functions make it less of a pain to deal with Javassist. There's one to * find methods, and one to clone annotations, which is used in several places * within JCommanderPrefixTranslator. */ public class JavassistUtils { private static Logger LOGGER = LoggerFactory.getLogger(JavassistUtils.class); public static final String PREFIX_PACKAGE = "mil.nga.giat.geowave.core.cli.parsed"; private static final String uniqueId; private static int objectCounter = 0; static { uniqueId = UUID.randomUUID().toString().replace( '-', '_'); } private JavassistUtils() {} /** * This function will take the given annotations attribute and create a new * attribute, cloning all the annotations and specified values within the * attribute. The annotations attribute can then be set on a method, class, * or field. * * @param attr * @return */ public static AnnotationsAttribute cloneAnnotationsAttribute( ConstPool constPool, AnnotationsAttribute attr, ElementType validElementType ) { // We can use system class loader here because the annotations for // Target // are part of the Java System. ClassLoader cl = ClassLoader.getSystemClassLoader(); AnnotationsAttribute attrNew = new AnnotationsAttribute( constPool, AnnotationsAttribute.visibleTag); if (attr != null) { for (Annotation annotation : attr.getAnnotations()) { Annotation newAnnotation = new Annotation( annotation.getTypeName(), constPool); // If this must target a certain type of field, then ensure we // only // copy over annotations that can target that type of field. // For instances, a METHOD annotation can't be applied to a // FIELD or TYPE. Class<?> annoClass; try { annoClass = cl.loadClass(annotation.getTypeName()); Target target = annoClass.getAnnotation(Target.class); if (target != null && !Arrays.asList( target.value()).contains( validElementType)) { continue; } } catch (ClassNotFoundException e) { // Cannot apply this annotation because its type cannot be // found. LOGGER.error( "Cannot apply this annotation because it's type cannot be found", e); continue; } // Copy over the options for this annotation. For example: // @Parameter(names = "-blah") // For this, a member value would be "names" which would be a // StringMemberValue if (annotation.getMemberNames() != null) { for (Object memberName : annotation.getMemberNames()) { MemberValue memberValue = annotation.getMemberValue((String) memberName); if (memberValue != null) { newAnnotation.addMemberValue( (String) memberName, memberValue); } } } attrNew.addAnnotation(newAnnotation); } } return attrNew; } /** * This class will find the method in the CtClass, and return it as a * CtMethod. * * @param clz * @param m * @return * @throws NotFoundException */ public static CtMethod findMethod( CtClass clz, Method m ) throws NotFoundException { ClassPool pool = ClassPool.getDefault(); Class<?>[] paramTypes = m.getParameterTypes(); List<CtClass> paramTypesCtClass = new ArrayList<CtClass>(); for (Class<?> claz : paramTypes) { paramTypesCtClass.add(pool.get(claz.getName())); } String desc = Descriptor.ofMethod( pool.get(m.getReturnType().getName()), paramTypesCtClass.toArray(new CtClass[] {})); CtMethod method = clz.getMethod( m.getName(), desc); return method; } /** * Simple helper method to essentially clone the annotations from one class * onto another * * @param oldClass * @param newClass */ public static void copyClassAnnotations( CtClass oldClass, CtClass newClass ) { // Load the existing annotations attributes AnnotationsAttribute classAnnotations = (AnnotationsAttribute) oldClass.getClassFile().getAttribute( AnnotationsAttribute.visibleTag); // Clone them AnnotationsAttribute copyClassAttribute = JavassistUtils.cloneAnnotationsAttribute( newClass.getClassFile2().getConstPool(), classAnnotations, ElementType.TYPE); // Set the annotations on the new class newClass.getClassFile().addAttribute( copyClassAttribute); } /** * Simple helper method to take any FIELD targetable annotations from the * method and copy them to the new field. All JCommander annotations can * target fields as well as methods, so this should capture them all. * * @param method * @param field */ public static void copyMethodAnnotationsToField( CtMethod method, CtField field ) { // Load the existing annotations attributes AnnotationsAttribute methodAnnotations = (AnnotationsAttribute) method.getMethodInfo().getAttribute( AnnotationsAttribute.visibleTag); // Clone them AnnotationsAttribute copyMethodAttribute = JavassistUtils.cloneAnnotationsAttribute( field.getFieldInfo2().getConstPool(), methodAnnotations, ElementType.FIELD); // Set the annotations on the new class field.getFieldInfo().addAttribute( copyMethodAttribute); } /** * Allows us to generate unique class names for generated classes * * @return */ public static String getNextUniqueClassName() { return String.format( "%s.cli_%s_%d", PREFIX_PACKAGE, uniqueId, objectCounter++); } /** * Allows us to generate unique field names for generated classes * * @return */ public static String getNextUniqueFieldName() { return String.format( "field_%d", objectCounter++); } /** * This will generate a class which is empty. Useful for applying * annotations to it * * @return */ public static CtClass generateEmptyClass() { // Create the class, so we can start adding the new facade fields to it. ClassPool pool = ClassPool.getDefault(); return pool.makeClass(getNextUniqueClassName()); } }