Created
March 16, 2018 11:41
-
-
Save nickman/647e1fbd52112ab3b15147cd99bbcd88 to your computer and use it in GitHub Desktop.
Example of adding annotations at runtime
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.heliosapm.aop.retransformer; | |
import java.lang.annotation.Annotation; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
import java.lang.instrument.ClassFileTransformer; | |
import java.lang.instrument.IllegalClassFormatException; | |
import java.lang.instrument.Instrumentation; | |
import java.lang.reflect.Method; | |
import java.security.ProtectionDomain; | |
import java.util.Arrays; | |
import java.util.Collections; | |
import java.util.EnumSet; | |
import java.util.HashMap; | |
import java.util.HashSet; | |
import java.util.Map; | |
import java.util.Set; | |
import java.util.Stack; | |
import javassist.ClassPool; | |
import javassist.CtClass; | |
import javassist.LoaderClassPath; | |
import javassist.bytecode.AnnotationsAttribute; | |
import javassist.bytecode.ClassFile; | |
import javassist.bytecode.ConstPool; | |
import javassist.bytecode.annotation.AnnotationMemberValue; | |
import javassist.bytecode.annotation.ArrayMemberValue; | |
import javassist.bytecode.annotation.BooleanMemberValue; | |
import javassist.bytecode.annotation.ByteMemberValue; | |
import javassist.bytecode.annotation.CharMemberValue; | |
import javassist.bytecode.annotation.ClassMemberValue; | |
import javassist.bytecode.annotation.DoubleMemberValue; | |
import javassist.bytecode.annotation.EnumMemberValue; | |
import javassist.bytecode.annotation.FloatMemberValue; | |
import javassist.bytecode.annotation.IntegerMemberValue; | |
import javassist.bytecode.annotation.LongMemberValue; | |
import javassist.bytecode.annotation.MemberValue; | |
import javassist.bytecode.annotation.ShortMemberValue; | |
import javassist.bytecode.annotation.StringMemberValue; | |
import net.bytebuddy.agent.ByteBuddyAgent; | |
import javax.xml.ws.soap.Addressing; | |
import javax.xml.ws.soap.AddressingFeature.Responses; | |
/** | |
* <p>Title: AnnotationBuilder</p> | |
* <p>Description: Fluent style annotation builder</p> | |
* <p>Company: Helios Development Group LLC</p> | |
* @author Whitehead (nwhitehead AT heliosdev DOT org) | |
* <p><code>com.heliosapm.aop.retransformer.AnnotationBuilder</code></p> | |
*/ | |
public class AnnotationBuilder { | |
/** A map of named member values keyed by the element type */ | |
protected final Map<String, MemberValueFactory> members = new HashMap<String, MemberValueFactory>(); | |
/** A set of the member names on the annotation type */ | |
protected final Set<String> memberNames; | |
/** The member definitions keyed by the member name */ | |
final Map<String, MemberDef<?>> defs; | |
/** The element types supported by the annotation */ | |
final Set<ElementType> elementTypes; | |
/** The annotation class we're applying */ | |
final Class<? extends Annotation> annotationClass; | |
/** Indicates if this annotation has runtime visibility */ | |
final boolean visible; | |
/** The parent annotation builder stack in a nested annotation builder used when we're adding an annotation which itself needs an annotation builder */ | |
protected final Stack<NamedAnnotationBuilder> annotationBuilderStack = new Stack<NamedAnnotationBuilder>(); | |
private class NamedAnnotationBuilder { | |
/** The intended annotation's annotation builder */ | |
final AnnotationBuilder annotationBuilder; | |
/** The intended annotation's name */ | |
final String name; | |
/** | |
* Creates a new NamedAnnotationBuilder | |
* @param name The intended annotation's name | |
* @param annotationBuilder The intended annotation's annotation builder | |
*/ | |
public NamedAnnotationBuilder(final String name, final AnnotationBuilder annotationBuilder) { | |
this.annotationBuilder = annotationBuilder; | |
this.name = name; | |
} | |
} | |
public interface MemberValueFactory { | |
public MemberValue forValue(final ConstPool pool); | |
} | |
public static final Set<Class> ALLOWED_TYPES = Collections.unmodifiableSet(new HashSet<Class>(Arrays.asList( | |
new Class[]{ | |
byte.class, byte[].class, boolean.class, boolean[].class, short.class, short[].class, | |
char.class, char[].class, int.class, int[].class, float.class, float[].class, long.class, | |
long[].class, double.class, double[].class, | |
String.class, Enum.class, Annotation.class, Class.class, | |
String[].class, Enum[].class, Annotation[].class, Class[].class | |
} | |
))); | |
public static final Set<Class> ALLOWED_NON_FINAL_TYPES = Collections.unmodifiableSet(new HashSet<Class>(Arrays.asList( | |
new Class[]{ | |
Enum.class, Annotation.class | |
} | |
))); | |
public static final Map<Class, Class> P2O; | |
public static final Map<Class, Class> O2P; | |
/** Primitive type to Jvaassist CtClass type decode */ | |
public static final Map<Class, CtClass> P2J; | |
static { | |
final Map<Class, Class> o2p = new HashMap<Class, Class>(); | |
final Map<Class, Class> p2o = new HashMap<Class, Class>(); | |
final Map<Class, CtClass> p2j = new HashMap<Class, CtClass>(); | |
// ========= full object --> primitive | |
o2p.put(java.lang.Byte.class, byte.class); | |
o2p.put(java.lang.Boolean.class, boolean.class); | |
o2p.put(java.lang.Short.class, short.class); | |
o2p.put(java.lang.Integer.class, int.class); | |
o2p.put(java.lang.Character.class, char.class); | |
o2p.put(java.lang.Float.class, float.class); | |
o2p.put(java.lang.Long.class, long.class); | |
o2p.put(java.lang.Double.class, double.class); | |
o2p.put(java.lang.Byte[].class, byte[].class); | |
o2p.put(java.lang.Boolean[].class, boolean[].class); | |
o2p.put(java.lang.Short[].class, short[].class); | |
o2p.put(java.lang.Character[].class, char[].class); | |
o2p.put(java.lang.Integer[].class, int[].class); | |
o2p.put(java.lang.Float[].class, float[].class); | |
o2p.put(java.lang.Long[].class, long[].class); | |
o2p.put(java.lang.Double[].class, double[].class); | |
// ========= primitive --> full object | |
p2o.put(byte.class, java.lang.Byte.class); | |
p2o.put(boolean.class, java.lang.Boolean.class); | |
p2o.put(short.class, java.lang.Short.class); | |
p2o.put(char.class, java.lang.Character.class); | |
p2o.put(int.class, java.lang.Integer.class); | |
p2o.put(float.class, java.lang.Float.class); | |
p2o.put(long.class, java.lang.Long.class); | |
p2o.put(double.class, java.lang.Double.class); | |
p2o.put(byte[].class, java.lang.Byte[].class); | |
p2o.put(boolean[].class, java.lang.Boolean[].class); | |
p2o.put(short[].class, java.lang.Short[].class); | |
p2o.put(char[].class, java.lang.Character[].class); | |
p2o.put(int[].class, java.lang.Integer[].class); | |
p2o.put(float[].class, java.lang.Float[].class); | |
p2o.put(long[].class, java.lang.Long[].class); | |
p2o.put(double[].class, java.lang.Double[].class); | |
// ========= primitive --> CtClass primtive | |
p2j.put(byte.class, CtClass.byteType); | |
p2j.put(boolean.class, CtClass.booleanType); | |
p2j.put(short.class, CtClass.shortType); | |
p2j.put(char.class, CtClass.charType); | |
p2j.put(int.class, CtClass.intType); | |
p2j.put(float.class, CtClass.floatType); | |
p2j.put(long.class, CtClass.longType); | |
p2j.put(double.class, CtClass.doubleType); | |
p2j.put(void.class, CtClass.voidType); | |
O2P = Collections.unmodifiableMap(new HashMap<Class, Class>(o2p)); | |
P2O = Collections.unmodifiableMap(new HashMap<Class, Class>(p2o)); | |
P2J = Collections.unmodifiableMap(new HashMap<Class, CtClass>(p2j)); | |
} | |
/** | |
* Creates a new AnnotationBuilder | |
* @param annotationClassName The class name of the annotation to build | |
* @param classLoader The optional class loader | |
* @return an annotation builder | |
*/ | |
public static AnnotationBuilder newBuilder(final String annotationClassName, final ClassLoader classLoader) { | |
if(annotationClassName==null || annotationClassName.trim().isEmpty()) throw new IllegalArgumentException("The passed class name was null or empty"); | |
try { | |
@SuppressWarnings("unchecked") | |
Class<Annotation> annotationClass = (Class<Annotation>) (classLoader==null ? | |
Class.forName(annotationClassName, true, Thread.currentThread().getContextClassLoader()) : | |
Class.forName(annotationClassName, true, classLoader)); | |
return new AnnotationBuilder(annotationClass); | |
} catch (Exception ex) { | |
throw new RuntimeException("Failed to load class [" + annotationClassName + "]", ex); | |
} | |
} | |
/** | |
* Creates a new AnnotationBuilder loading the annotation class using the calling thread's context classloader | |
* @param annotationClassName The class name of the annotation to build | |
* @return an annotation builder | |
*/ | |
public static AnnotationBuilder newBuilder(final String annotationClassName) { | |
return newBuilder(annotationClassName, null); | |
} | |
/** | |
* Creates a new AnnotationBuilder for the passed annotation type | |
* @param annotationClass The annotation type | |
* @return an annotation builder | |
*/ | |
public static AnnotationBuilder newBuilder(final Class<? extends Annotation> annotationClass) { | |
return new AnnotationBuilder(annotationClass); | |
} | |
/** | |
* Creates a new AnnotationBuilder | |
* @param annotationClass The annotation class to build | |
*/ | |
private AnnotationBuilder(final Class<? extends Annotation> annotationClass) { | |
this.annotationClass = annotationClass; | |
final Retention retention = this.annotationClass.getAnnotation(Retention.class); | |
visible = (retention!=null && retention.value().equals(RetentionPolicy.RUNTIME)); | |
final Method[] methods = annotationClass.getDeclaredMethods(); | |
final Set<String> mn = new HashSet<String>(methods.length); | |
for(Method m: methods) { | |
mn.add(m.getName()); | |
} | |
memberNames = Collections.unmodifiableSet(mn); | |
log("Member Names: " + memberNames); | |
defs = MemberDef.getMemberDefs(annotationClass); | |
final Target target = annotationClass.getAnnotation(Target.class); | |
Set<ElementType> ets = EnumSet.noneOf(ElementType.class); | |
if(target!=null) { | |
for(ElementType et: target.value()) { | |
ets.add(et); | |
} | |
} | |
this.elementTypes = Collections.unmodifiableSet(ets); | |
} | |
/** | |
* Applies the built annotation to the passed CtClass instances | |
* @param clazzes The CtClasses to apply the annotation to | |
*/ | |
public void applyTo(final CtClass...clazzes) { | |
if(!annotationBuilderStack.isEmpty()) throw new IllegalStateException("The annotation builder stack is not empty"); | |
for(CtClass clazz: clazzes) { | |
final ClassFile classFile = clazz.getClassFile(); | |
final ConstPool constPool = classFile.getConstPool(); | |
final javassist.bytecode.annotation.Annotation annot = new javassist.bytecode.annotation.Annotation(this.annotationClass.getName(), constPool); | |
final AnnotationsAttribute attr = new AnnotationsAttribute(constPool, visible ? AnnotationsAttribute.visibleTag : AnnotationsAttribute.invisibleTag); | |
for(Map.Entry<String, MemberValueFactory> entry: members.entrySet()) { | |
annot.addMemberValue(entry.getKey(), entry.getValue().forValue(constPool)); | |
} | |
attr.addAnnotation(annot); | |
classFile.addAttribute(attr); | |
} | |
} | |
/** | |
* Adds a new member name and value to the builder | |
* @param name The member name | |
* @param value The member value | |
* @return this annotation builder | |
*/ | |
public AnnotationBuilder add(final String name, final Class<?> value) { | |
validate(name, value); | |
members.put(name, new MemberValueFactory() { | |
@Override | |
public MemberValue forValue(final ConstPool pool) { | |
return new ClassMemberValue(value.getName(), pool); | |
} | |
}); | |
return this; | |
} | |
/** | |
* Adds a new member name and value to the builder | |
* @param name The member name | |
* @param value The member value | |
* @return this annotation builder | |
*/ | |
public AnnotationBuilder add(final String name, final byte value) { | |
validate(name, value); | |
members.put(name, new MemberValueFactory() { | |
@Override | |
public MemberValue forValue(final ConstPool pool) { | |
return new ByteMemberValue(value, pool); | |
} | |
}); | |
return this; | |
} | |
/** | |
* Adds a new member name and value to the builder | |
* @param name The member name | |
* @param value The member value | |
* @return this annotation builder | |
*/ | |
public AnnotationBuilder add(final String name, final byte[] value) { | |
validate(name, value); | |
members.put(name, new MemberValueFactory() { | |
@Override | |
public MemberValue forValue(final ConstPool pool) { | |
final ArrayMemberValue amv = new ArrayMemberValue(pool); | |
final MemberValue[] mvs = new MemberValue[value.length]; | |
for(int i = 0; i < value.length; i++) { | |
mvs[i] = new ByteMemberValue(value[i], pool); | |
} | |
amv.setValue(mvs); | |
return amv; | |
} | |
}); | |
return this; | |
} | |
/** | |
* Adds a new member name and value to the builder | |
* @param name The member name | |
* @param value The member value | |
* @return this annotation builder | |
*/ | |
public AnnotationBuilder add(final String name, final boolean value) { | |
validate(name, value); | |
members.put(name, new MemberValueFactory() { | |
@Override | |
public MemberValue forValue(final ConstPool pool) { | |
return new BooleanMemberValue(value, pool); | |
} | |
}); | |
return this; | |
} | |
/** | |
* Adds a new member name and value to the builder | |
* @param name The member name | |
* @param value The member value | |
* @return this annotation builder | |
*/ | |
public <E extends Enum<E>> AnnotationBuilder add(final String name, final E value) { | |
validate(name, value); | |
members.put(name, new MemberValueFactory() { | |
@Override | |
public MemberValue forValue(final ConstPool pool) { | |
final EnumMemberValue emv = new EnumMemberValue(pool); | |
emv.setType(value.getDeclaringClass().getName()); | |
emv.setValue(value.name()); | |
return emv; | |
} | |
}); | |
return this; | |
} | |
/** | |
* Adds a new member name and value to the builder | |
* @param name The member name | |
* @param value The member value | |
* @return this annotation builder | |
*/ | |
public <E extends Enum<E>> AnnotationBuilder add(final String name, final E[] value) { | |
validate(name, value); | |
members.put(name, new MemberValueFactory() { | |
@Override | |
public MemberValue forValue(final ConstPool pool) { | |
final ArrayMemberValue amv = new ArrayMemberValue(pool); | |
final EnumMemberValue[] emvs = new EnumMemberValue[value.length]; | |
final Class<E> enumType = value[0].getDeclaringClass(); | |
for(int i = 0; i < value.length; i++) { | |
final EnumMemberValue emv = new EnumMemberValue(pool); | |
emv.setType(enumType.getName()); | |
emv.setValue(value[i].name()); | |
} | |
amv.setValue(emvs); | |
return amv; | |
} | |
}); | |
return this; | |
} | |
/** | |
* Adds a new member name and value to the builder | |
* @param name The member name | |
* @param value The member value | |
* @return this annotation builder | |
*/ | |
public AnnotationBuilder add(final String name, final boolean[] value) { | |
validate(name, value); | |
members.put(name, new MemberValueFactory() { | |
@Override | |
public MemberValue forValue(final ConstPool pool) { | |
final ArrayMemberValue amv = new ArrayMemberValue(pool); | |
final MemberValue[] mvs = new MemberValue[value.length]; | |
for(int i = 0; i < value.length; i++) { | |
mvs[i] = new BooleanMemberValue(value[i], pool); | |
} | |
amv.setValue(mvs); | |
return amv; | |
} | |
}); | |
return this; | |
} | |
/** | |
* Adds a new member name and value to the builder | |
* @param name The member name | |
* @param value The member value | |
* @return this annotation builder | |
*/ | |
public AnnotationBuilder add(final String name, final short value) { | |
validate(name, value); | |
members.put(name, new MemberValueFactory() { | |
@Override | |
public MemberValue forValue(final ConstPool pool) { | |
return new ShortMemberValue(value, pool); | |
} | |
}); | |
return this; | |
} | |
/** | |
* Adds a new member name and value to the builder | |
* @param name The member name | |
* @param value The member value | |
* @return this annotation builder | |
*/ | |
public AnnotationBuilder add(final String name, final short[] value) { | |
validate(name, value); | |
members.put(name, new MemberValueFactory() { | |
@Override | |
public MemberValue forValue(final ConstPool pool) { | |
final ArrayMemberValue amv = new ArrayMemberValue(pool); | |
final MemberValue[] mvs = new MemberValue[value.length]; | |
for(int i = 0; i < value.length; i++) { | |
mvs[i] = new ShortMemberValue(value[i], pool); | |
} | |
amv.setValue(mvs); | |
return amv; | |
} | |
}); | |
return this; | |
} | |
/** | |
* Adds a new member name and value to the builder | |
* @param name The member name | |
* @param value The member value | |
* @return this annotation builder | |
*/ | |
public AnnotationBuilder add(final String name, final int value) { | |
validate(name, value); | |
members.put(name, new MemberValueFactory() { | |
@Override | |
public MemberValue forValue(final ConstPool pool) { | |
return new IntegerMemberValue(value, pool); | |
} | |
}); | |
return this; | |
} | |
/** | |
* Adds a new member name and value to the builder | |
* @param name The member name | |
* @param value The member value | |
* @return this annotation builder | |
*/ | |
public AnnotationBuilder add(final String name, final int[] value) { | |
validate(name, value); | |
members.put(name, new MemberValueFactory() { | |
@Override | |
public MemberValue forValue(final ConstPool pool) { | |
final ArrayMemberValue amv = new ArrayMemberValue(pool); | |
final MemberValue[] mvs = new MemberValue[value.length]; | |
for(int i = 0; i < value.length; i++) { | |
mvs[i] = new IntegerMemberValue(value[i], pool); | |
} | |
amv.setValue(mvs); | |
return amv; | |
} | |
}); | |
return this; | |
} | |
/** | |
* Adds a new member name and value to the builder | |
* @param name The member name | |
* @param value The member value | |
* @return this annotation builder | |
*/ | |
public AnnotationBuilder add(final String name, final float value) { | |
validate(name, value); | |
members.put(name, new MemberValueFactory() { | |
@Override | |
public MemberValue forValue(final ConstPool pool) { | |
return new FloatMemberValue(value, pool); | |
} | |
}); | |
return this; | |
} | |
/** | |
* Adds a new member name and value to the builder | |
* @param name The member name | |
* @param value The member value | |
* @return this annotation builder | |
*/ | |
public AnnotationBuilder add(final String name, final float[] value) { | |
validate(name, value); | |
members.put(name, new MemberValueFactory() { | |
@Override | |
public MemberValue forValue(final ConstPool pool) { | |
final ArrayMemberValue amv = new ArrayMemberValue(pool); | |
final MemberValue[] mvs = new MemberValue[value.length]; | |
for(int i = 0; i < value.length; i++) { | |
mvs[i] = new FloatMemberValue(value[i], pool); | |
} | |
amv.setValue(mvs); | |
return amv; | |
} | |
}); | |
return this; | |
} | |
/** | |
* Adds a new member name and value to the builder | |
* @param name The member name | |
* @param value The member value | |
* @return this annotation builder | |
*/ | |
public AnnotationBuilder add(final String name, final long value) { | |
validate(name, value); | |
members.put(name, new MemberValueFactory() { | |
@Override | |
public MemberValue forValue(final ConstPool pool) { | |
return new LongMemberValue(value, pool); | |
} | |
}); | |
return this; | |
} | |
/** | |
* Adds a new member name and value to the builder | |
* @param name The member name | |
* @param value The member value | |
* @return this annotation builder | |
*/ | |
public AnnotationBuilder add(final String name, final long[] value) { | |
validate(name, value); | |
members.put(name, new MemberValueFactory() { | |
@Override | |
public MemberValue forValue(final ConstPool pool) { | |
final ArrayMemberValue amv = new ArrayMemberValue(pool); | |
final MemberValue[] mvs = new MemberValue[value.length]; | |
for(int i = 0; i < value.length; i++) { | |
mvs[i] = new LongMemberValue(value[i], pool); | |
} | |
amv.setValue(mvs); | |
return amv; | |
} | |
}); | |
return this; | |
} | |
/** | |
* Starts a new annotation to build an annotation to add to an annotatable target | |
* @param name The name of the annotation | |
* @param value The annotation class | |
* @return the new annotation builder | |
*/ | |
public AnnotationBuilder addAnnotation(final String name, final Class<? extends Annotation> value) { | |
validate(name, value); | |
final AnnotationBuilder inner = new AnnotationBuilder(value); | |
inner.annotationBuilderStack.push(new NamedAnnotationBuilder("parent", this)); | |
this.annotationBuilderStack.push(new NamedAnnotationBuilder(name, inner)); | |
return inner; | |
} | |
/** | |
* Adds the current annotation builder to the parent and pops the parent back into the current context | |
* @return the parent annotation builder | |
*/ | |
public AnnotationBuilder endAnnotation() { | |
if(this.annotationBuilderStack.isEmpty()) throw new IllegalStateException("No annotation builder in state. Did you call addAnnotation first ?"); | |
final NamedAnnotationBuilder parent = this.annotationBuilderStack.pop(); | |
if(!"parent".equals(parent.name)) throw new IllegalStateException("The annotation builder in state was named [" + parent.name + "] but expected [parent]"); | |
if(parent.annotationBuilder.annotationBuilderStack.isEmpty()) throw new IllegalStateException("No annotation builder in parent state. Did you call addAnnotation first ?"); | |
final NamedAnnotationBuilder self = parent.annotationBuilder.annotationBuilderStack.pop(); | |
if(self.annotationBuilder != this) throw new IllegalStateException("The popped NamedAnnotationBuilder did not contain the expected annotation builder"); | |
members.put(self.name, new MemberValueFactory(){ | |
@Override | |
public MemberValue forValue(final ConstPool pool) { | |
final javassist.bytecode.annotation.Annotation annot = new javassist.bytecode.annotation.Annotation(self.annotationBuilder.annotationClass.getName(), pool); | |
for(Map.Entry<String, MemberValueFactory> entry: self.annotationBuilder.members.entrySet()) { | |
annot.addMemberValue(entry.getKey(), entry.getValue().forValue(pool)); | |
} | |
return new AnnotationMemberValue(annot, pool); | |
} | |
}); | |
return parent.annotationBuilder; | |
} | |
/** | |
* Adds a new member name and value to the builder | |
* @param name The member name | |
* @param value The member value | |
* @return this annotation builder | |
*/ | |
public AnnotationBuilder add(final String name, final double value) { | |
validate(name, value); | |
members.put(name, new MemberValueFactory() { | |
@Override | |
public MemberValue forValue(final ConstPool pool) { | |
return new DoubleMemberValue(value, pool); | |
} | |
}); | |
return this; | |
} | |
/** | |
* Adds a new member name and value to the builder | |
* @param name The member name | |
* @param value The member value | |
* @return this annotation builder | |
*/ | |
public AnnotationBuilder add(final String name, final double[] value) { | |
validate(name, value); | |
members.put(name, new MemberValueFactory() { | |
@Override | |
public MemberValue forValue(final ConstPool pool) { | |
final ArrayMemberValue amv = new ArrayMemberValue(pool); | |
final MemberValue[] mvs = new MemberValue[value.length]; | |
for(int i = 0; i < value.length; i++) { | |
mvs[i] = new DoubleMemberValue(value[i], pool); | |
} | |
amv.setValue(mvs); | |
return amv; | |
} | |
}); | |
return this; | |
} | |
/** | |
* Adds a new member name and value to the builder | |
* @param name The member name | |
* @param value The member value | |
* @return this annotation builder | |
*/ | |
public AnnotationBuilder add(final String name, final char value) { | |
validate(name, value); | |
members.put(name, new MemberValueFactory() { | |
@Override | |
public MemberValue forValue(final ConstPool pool) { | |
return new CharMemberValue(value, pool); | |
} | |
}); | |
return this; | |
} | |
/** | |
* Adds a new member name and value to the builder | |
* @param name The member name | |
* @param value The member value | |
* @return this annotation builder | |
*/ | |
public AnnotationBuilder add(final String name, final char[] value) { | |
validate(name, value); | |
members.put(name, new MemberValueFactory() { | |
@Override | |
public MemberValue forValue(final ConstPool pool) { | |
final ArrayMemberValue amv = new ArrayMemberValue(pool); | |
final MemberValue[] mvs = new MemberValue[value.length]; | |
for(int i = 0; i < value.length; i++) { | |
mvs[i] = new CharMemberValue(value[i], pool); | |
} | |
amv.setValue(mvs); | |
return amv; | |
} | |
}); | |
return this; | |
} | |
/** | |
* Adds a new member name and value to the builder | |
* @param name The member name | |
* @param value The member value | |
* @return this annotation builder | |
*/ | |
public AnnotationBuilder add(final String name, final String value) { | |
validate(name, value); | |
members.put(name, new MemberValueFactory() { | |
@Override | |
public MemberValue forValue(final ConstPool pool) { | |
return new StringMemberValue(value, pool); | |
} | |
}); | |
return this; | |
} | |
/** | |
* Adds a new member name and value to the builder | |
* @param name The member name | |
* @param value The member value | |
* @return this annotation builder | |
*/ | |
public AnnotationBuilder add(final String name, final String[] value) { | |
validate(name, value); | |
members.put(name, new MemberValueFactory() { | |
@Override | |
public MemberValue forValue(final ConstPool pool) { | |
final ArrayMemberValue amv = new ArrayMemberValue(pool); | |
final MemberValue[] mvs = new MemberValue[value.length]; | |
for(int i = 0; i < value.length; i++) { | |
mvs[i] = new StringMemberValue(value[i], pool); | |
} | |
amv.setValue(mvs); | |
return amv; | |
} | |
}); | |
return this; | |
} | |
public void validate(final String name, final Object value) { | |
if(name==null || name.trim().isEmpty()) throw new IllegalArgumentException("Member name was null or empty"); | |
if(!memberNames.contains(name)) throw new IllegalArgumentException("Invalid member name [" + name + "]"); | |
if(value==null) throw new IllegalArgumentException("The value passed was null for Member name [" + name + "]"); | |
final Class<?> type = value.getClass(); | |
for(MemberDef md: defs.values()) { | |
if(md.match(name, type)) return; | |
} | |
throw new IllegalArgumentException("Invalid type [" + value.getClass() + "] for member name [" + name + "]"); | |
} | |
class TestClass { | |
} | |
public static void main(String[] agrs) { | |
log("Quickie Test"); | |
AnnotationBuilder ab = newBuilder("javax.xml.ws.soap.Addressing") | |
.add("enabled", false) | |
.add("required", true) | |
.add("responses", Responses.NON_ANONYMOUS); | |
try { | |
final ClassPool cp = new ClassPool(); | |
cp.appendSystemPath(); | |
cp.appendClassPath(new LoaderClassPath(TestClass.class.getClassLoader())); | |
final CtClass ctclazz = cp.get(TestClass.class.getName()); | |
ab.applyTo(ctclazz); | |
final byte[] byteCode = ctclazz.toBytecode(); | |
final String target = TestClass.class.getName().replace('.', '/'); | |
log("Target:" + target); | |
final ClassFileTransformer cft = new ClassFileTransformer() { | |
@Override | |
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, | |
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { | |
log("ClassName:" + className); | |
if(target.equals(className)) { | |
log("Match !"); | |
return byteCode; | |
} | |
return null; | |
} | |
}; | |
// ============================================================================== | |
// There's several ways to acquire an Instrumentation instance | |
// This one is the easiest | |
// ============================================================================== | |
Instrumentation ins = ByteBuddyAgent.install(); | |
try { | |
ins.addTransformer(cft, true); | |
ins.retransformClasses(TestClass.class); | |
} finally { | |
ins.removeTransformer(cft); | |
} | |
log("Done"); | |
Addressing addr = TestClass.class.getAnnotation(Addressing.class); | |
log("Annotation Present:" + addr!=null); | |
if(addr!=null) { | |
log("Enabled:" + addr.enabled()); | |
log("Required:" + addr.required()); | |
log("Responses:" + addr.responses().name()); | |
} | |
} catch (Exception ex) { | |
ex.printStackTrace(System.err); | |
} | |
} | |
public static void log(Object msg) { | |
System.out.println(msg); | |
} | |
protected static class MemberDef<T> { | |
/** The member name */ | |
final String name; | |
/** The annotation member type */ | |
final Class<T> type; | |
/** The annotation member type upped alt */ | |
final Class<T> otype; | |
/** The annotation member default value */ | |
final T value; | |
/** | |
* Returns an array of member definitions for the passed annotation type | |
* @param type The annotation type | |
* @return the array of member definitions | |
*/ | |
@SuppressWarnings("unchecked") | |
public static <T extends Annotation> Map<String, MemberDef<?>> getMemberDefs(final Class<T> type) { | |
final Method[] methods = type.getDeclaredMethods(); | |
final Map<String, MemberDef<?>> map = new HashMap<String, MemberDef<?>>(methods.length); | |
for(Method method: methods) { | |
map.put(method.getName(), new MemberDef(method.getName(), method.getReturnType(), method.getDefaultValue())); | |
} | |
return Collections.unmodifiableMap(map); | |
} | |
/** | |
* Creates a new MemberDef | |
* @param name The member name | |
* @param type The annotation member type | |
* @param value The annotation member default value | |
*/ | |
public MemberDef(final String name, final Class<T> type, final T value) { | |
this.name = name; | |
this.type = type; | |
this.value = value; | |
this.otype = P2O.get(type); | |
} | |
/** | |
* Tests the passed member name and value to see if it is a match for this member def | |
* @param name The member name | |
* @param valueType The member value type | |
* @return true for a match, false otherwise | |
*/ | |
public boolean match(final String name, final Class<?> valueType) { | |
if(name==null || name.trim().isEmpty()) throw new IllegalArgumentException("The member name was null or empty"); | |
if(value==null) throw new IllegalArgumentException("The member type was null"); | |
if(!this.name.equals(name)) return false; | |
return (valueType.equals(this.type) || valueType.equals(this.otype)); | |
} | |
/** | |
* Returns the member name | |
* @return the name | |
*/ | |
public String getName() { | |
return name; | |
} | |
/** | |
* Returns the annotation member type | |
* @return the annotation member type | |
*/ | |
public Class<T> getType() { | |
return type; | |
} | |
/** | |
* Returns the annotation member value | |
* @return the annotation member value or null if no default is defined | |
*/ | |
public T getValue() { | |
return value; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment