/*
* Copyright 2016, Stuart Douglas, and individual contributors as indicated
* by the @authors tag.
*
* 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.fakereplace.data;
import javassist.bytecode.AccessFlag;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.AttributeInfo;
import javassist.bytecode.Bytecode;
import javassist.bytecode.ClassFile;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.Opcode;
import javassist.bytecode.ParameterAnnotationsAttribute;
import org.fakereplace.api.ChangeType;
import org.fakereplace.replacement.notification.ChangedAnnotationImpl;
import org.fakereplace.classloading.ProxyDefinitionStore;
import org.fakereplace.replacement.notification.ChangedClassImpl;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Stores information about the annotations on reloaded classes
* <p>
* TODO: This leaks Class's. everthing should be stored in weak hashaps keyed on
* the class object
*
* @author stuart
*/
public class AnnotationDataStore {
private static Map<Class<?>, Annotation[]> classAnnotations = new ConcurrentHashMap<Class<?>, Annotation[]>();
private static Map<Class<?>, Map<Class<? extends Annotation>, Annotation>> classAnnotationsByType = new ConcurrentHashMap<Class<?>, Map<Class<? extends Annotation>, Annotation>>();
private static Map<Field, Annotation[]> fieldAnnotations = new ConcurrentHashMap<Field, Annotation[]>();
private static Map<Field, Map<Class<? extends Annotation>, Annotation>> fieldAnnotationsByType = new ConcurrentHashMap<Field, Map<Class<? extends Annotation>, Annotation>>();
private static Map<Method, Annotation[]> methodAnnotations = new ConcurrentHashMap<Method, Annotation[]>();
private static Map<Method, Map<Class<? extends Annotation>, Annotation>> methodAnnotationsByType = new ConcurrentHashMap<Method, Map<Class<? extends Annotation>, Annotation>>();
private static Map<Method, Annotation[][]> parameterAnnotations = new ConcurrentHashMap<Method, Annotation[][]>();
private static Map<Constructor<?>, Annotation[]> constructorAnnotations = new ConcurrentHashMap<Constructor<?>, Annotation[]>();
private static Map<Constructor<?>, Map<Class<? extends Annotation>, Annotation>> constructorAnnotationsByType = new ConcurrentHashMap<Constructor<?>, Map<Class<? extends Annotation>, Annotation>>();
private static Map<Constructor<?>, Annotation[][]> constructorParameterAnnotations = new ConcurrentHashMap<Constructor<?>, Annotation[][]>();
static final String PROXY_METHOD_NAME = "annotationsMethod";
public static boolean isClassDataRecorded(Class<?> clazz) {
return classAnnotations.containsKey(clazz);
}
public static Annotation[] getClassAnnotations(Class<?> clazz) {
return classAnnotations.get(clazz);
}
public static Annotation getClassAnnotation(Class<?> clazz, Class<? extends Annotation> annotation) {
return classAnnotationsByType.get(clazz).get(annotation);
}
public static boolean isClassAnnotationPresent(Class<?> clazz, Class<? extends Annotation> annotation) {
return classAnnotationsByType.get(clazz).containsKey(annotation);
}
public static boolean isFieldDataRecorded(Field clazz) {
return fieldAnnotations.containsKey(clazz);
}
public static Annotation[] getFieldAnnotations(Field clazz) {
return fieldAnnotations.get(clazz);
}
public static Annotation getFieldAnnotation(Field clazz, Class<? extends Annotation> annotation) {
return fieldAnnotationsByType.get(clazz).get(annotation);
}
public static boolean isFieldAnnotationPresent(Field clazz, Class<? extends Annotation> annotation) {
return fieldAnnotationsByType.get(clazz).containsKey(annotation);
}
public static boolean isMethodDataRecorded(Method clazz) {
return methodAnnotations.containsKey(clazz);
}
public static Annotation[] getMethodAnnotations(Method clazz) {
return methodAnnotations.get(clazz);
}
public static Annotation getMethodAnnotation(Method clazz, Class<? extends Annotation> annotation) {
return methodAnnotationsByType.get(clazz).get(annotation);
}
public static boolean isMethodAnnotationPresent(Method clazz, Class<? extends Annotation> annotation) {
return methodAnnotationsByType.get(clazz).containsKey(annotation);
}
public static Annotation[][] getMethodParameterAnnotations(Method clazz) {
return parameterAnnotations.get(clazz);
}
// constructor
public static boolean isConstructorDataRecorded(Constructor<?> clazz) {
return constructorAnnotations.containsKey(clazz);
}
public static Annotation[] getConstructorAnnotations(Constructor<?> clazz) {
return constructorAnnotations.get(clazz);
}
public static Annotation getConstructorAnnotation(Constructor<?> clazz, Class<? extends Annotation> annotation) {
return constructorAnnotationsByType.get(clazz).get(annotation);
}
public static boolean isConstructorAnnotationPresent(Constructor<?> clazz, Class<? extends Annotation> annotation) {
return constructorAnnotationsByType.get(clazz).containsKey(annotation);
}
public static Annotation[][] getMethodParameterAnnotations(Constructor<?> clazz) {
return constructorParameterAnnotations.get(clazz);
}
static Class<?> createAnnotationsProxy(ClassLoader loader, AnnotationsAttribute annotations) {
String proxyName = ProxyDefinitionStore.getProxyName();
ClassFile proxy = new ClassFile(false, proxyName, "java.lang.Object");
proxy.setAccessFlags(AccessFlag.PUBLIC);
AttributeInfo a = annotations.copy(proxy.getConstPool(), Collections.EMPTY_MAP);
proxy.addAttribute(a);
try {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bytes);
try {
proxy.write(dos);
} catch (IOException e) {
throw new RuntimeException(e);
}
ProxyDefinitionStore.saveProxyDefinition(loader, proxyName, bytes.toByteArray());
return loader.loadClass(proxyName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
static Class<?> createParameterAnnotationsProxy(ClassLoader loader, ParameterAnnotationsAttribute annotations, int paramCount) {
String proxyName = ProxyDefinitionStore.getProxyName();
ClassFile proxy = new ClassFile(false, proxyName, "java.lang.Object");
proxy.setAccessFlags(AccessFlag.PUBLIC);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < paramCount; ++i) {
sb.append("I");
}
MethodInfo method = new MethodInfo(proxy.getConstPool(), PROXY_METHOD_NAME, "(" + sb.toString() + ")V");
Bytecode b = new Bytecode(proxy.getConstPool());
b.add(Opcode.RETURN);
method.setAccessFlags(AccessFlag.PUBLIC);
method.setCodeAttribute(b.toCodeAttribute());
method.getCodeAttribute().setMaxLocals(paramCount + 1);
AttributeInfo an = annotations.copy(proxy.getConstPool(), Collections.EMPTY_MAP);
method.addAttribute(an);
try {
proxy.addMethod(method);
method.getCodeAttribute().computeMaxStack();
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bytes);
try {
proxy.write(dos);
} catch (IOException e) {
throw new RuntimeException(e);
}
ProxyDefinitionStore.saveProxyDefinition(loader, proxyName, bytes.toByteArray());
return loader.loadClass(proxyName);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void recordClassAnnotations(Class<?> clazz, AnnotationsAttribute annotations, ChangedClassImpl changedClass) {
// no annotations
if (annotations == null) {
Annotation[] ans = new Annotation[0];
classAnnotations.put(clazz, ans);
classAnnotationsByType.put(clazz, Collections.EMPTY_MAP);
for(Annotation annotation : clazz.getDeclaredAnnotations()) {
changedClass.changeClassAnnotation(new ChangedAnnotationImpl(null, annotation, ChangeType.REMOVE, changedClass, annotation.annotationType()));
}
} else {
final Class<?> pclass = createAnnotationsProxy(clazz.getClassLoader(), annotations);
classAnnotations.put(clazz, pclass.getAnnotations());
Map<Class<? extends Annotation>, Annotation> anVals = new HashMap<Class<? extends Annotation>, Annotation>();
classAnnotationsByType.put(clazz, anVals);
int count = 0;
for (Annotation a : pclass.getAnnotations()) {
anVals.put(a.annotationType(), a);
count++;
}
final Set<Class<? extends Annotation>> newAnnotations = new HashSet<Class<? extends Annotation>>(anVals.keySet());
for(Annotation annotation : clazz.getDeclaredAnnotations()) {
final Annotation newAnnotation = anVals.get(annotation.annotationType());
if(newAnnotation == null) {
//the annotation was removed
changedClass.changeClassAnnotation(new ChangedAnnotationImpl(null, annotation, ChangeType.REMOVE, changedClass, annotation.annotationType()));
} else if(!newAnnotation.equals(annotation)) {
//same annotation, but it has been modified
changedClass.changeClassAnnotation(new ChangedAnnotationImpl(newAnnotation, annotation, ChangeType.MODIFY, changedClass, annotation.annotationType()));
}
newAnnotations.remove(annotation.annotationType());
}
for(final Class<? extends Annotation> newAnnotationType : newAnnotations) {
final Annotation newAnnotation = anVals.get(newAnnotationType);
changedClass.changeClassAnnotation(new ChangedAnnotationImpl(newAnnotation, null, ChangeType.ADD, changedClass, newAnnotationType));
}
}
}
public static void recordFieldAnnotations(Field field, AnnotationsAttribute annotations) {
// no annotations
if (annotations == null) {
Annotation[] ans = new Annotation[0];
fieldAnnotations.put(field, ans);
fieldAnnotationsByType.put(field, Collections.EMPTY_MAP);
return;
}
Class<?> pclass = createAnnotationsProxy(field.getDeclaringClass().getClassLoader(), annotations);
fieldAnnotations.put(field, pclass.getAnnotations());
Map<Class<? extends Annotation>, Annotation> anVals = new HashMap<Class<? extends Annotation>, Annotation>();
fieldAnnotationsByType.put(field, anVals);
int count = 0;
for (Annotation a : pclass.getAnnotations()) {
anVals.put(a.annotationType(), a);
count++;
}
}
public static void recordMethodAnnotations(Method method, AnnotationsAttribute annotations) {
// no annotations
if (annotations == null) {
Annotation[] ans = new Annotation[0];
methodAnnotations.put(method, ans);
methodAnnotationsByType.put(method, Collections.EMPTY_MAP);
return;
}
Class<?> pclass = createAnnotationsProxy(method.getDeclaringClass().getClassLoader(), annotations);
methodAnnotations.put(method, pclass.getAnnotations());
Map<Class<? extends Annotation>, Annotation> anVals = new HashMap<Class<? extends Annotation>, Annotation>();
methodAnnotationsByType.put(method, anVals);
int count = 0;
for (Annotation a : pclass.getAnnotations()) {
anVals.put(a.annotationType(), a);
count++;
}
}
public static void recordMethodParameterAnnotations(Method method, ParameterAnnotationsAttribute annotations) {
// no annotations
if (annotations == null) {
Annotation[][] ans = new Annotation[method.getParameterAnnotations().length][0];
parameterAnnotations.put(method, ans);
return;
}
Class<?> pclass = createParameterAnnotationsProxy(method.getDeclaringClass().getClassLoader(), annotations, method.getParameterTypes().length);
Class<?>[] types = new Class[method.getParameterTypes().length];
for (int i = 0; i < types.length; ++i) {
types[i] = int.class;
}
try {
Method anMethod = pclass.getMethod(PROXY_METHOD_NAME, types);
parameterAnnotations.put(method, anMethod.getParameterAnnotations());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void recordConstructorAnnotations(Constructor<?> constructor, AnnotationsAttribute annotations) {
// no annotations
if (annotations == null) {
Annotation[] ans = new Annotation[0];
constructorAnnotations.put(constructor, ans);
constructorAnnotationsByType.put(constructor, Collections.EMPTY_MAP);
return;
}
Class<?> pclass = createAnnotationsProxy(constructor.getDeclaringClass().getClassLoader(), annotations);
constructorAnnotations.put(constructor, pclass.getAnnotations());
Map<Class<? extends Annotation>, Annotation> anVals = new HashMap<Class<? extends Annotation>, Annotation>();
constructorAnnotationsByType.put(constructor, anVals);
int count = 0;
for (Annotation a : pclass.getAnnotations()) {
anVals.put(a.annotationType(), a);
count++;
}
}
public static void recordConstructorParameterAnnotations(Constructor<?> method, ParameterAnnotationsAttribute annotations) {
// no annotations
if (annotations == null) {
Annotation[][] ans = new Annotation[method.getParameterAnnotations().length][0];
constructorParameterAnnotations.put(method, ans);
return;
}
Class<?> pclass = createParameterAnnotationsProxy(method.getDeclaringClass().getClassLoader(), annotations, method.getParameterTypes().length);
Class<?>[] types = new Class[method.getParameterTypes().length];
for (int i = 0; i < types.length; ++i) {
types[i] = int.class;
}
try {
Method anMethod = pclass.getMethod(PROXY_METHOD_NAME, types);
constructorParameterAnnotations.put(method, anMethod.getParameterAnnotations());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}