/**
* Copyright (C) 2006-2017 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
* abiding by the rules of distribution of free software. You can use, modify
* and/or redistribute the software under the terms of the CeCILL-C license as
* circulated by CEA, CNRS and INRIA at http://www.cecill.info.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
package spoon.support.reflect.declaration;
import spoon.Launcher;
import spoon.SpoonException;
import spoon.reflect.code.CtCodeElement;
import spoon.reflect.code.CtExpression;
import spoon.reflect.code.CtFieldAccess;
import spoon.reflect.code.CtFieldRead;
import spoon.reflect.code.CtLiteral;
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.code.CtNewArray;
import spoon.reflect.code.CtTypeAccess;
import spoon.reflect.declaration.CtAnnotatedElementType;
import spoon.reflect.declaration.CtAnnotation;
import spoon.reflect.declaration.CtAnnotationMethod;
import spoon.reflect.declaration.CtAnnotationType;
import spoon.reflect.declaration.CtConstructor;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtField;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtPackage;
import spoon.reflect.declaration.CtParameter;
import spoon.reflect.declaration.CtShadowable;
import spoon.reflect.declaration.CtType;
import spoon.reflect.eval.PartialEvaluator;
import spoon.reflect.reference.CtFieldReference;
import spoon.reflect.reference.CtTypeParameterReference;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.CtVisitor;
import spoon.support.comparator.CtLineElementComparator;
import spoon.support.reflect.code.CtExpressionImpl;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
/**
* The implementation for {@link spoon.reflect.declaration.CtAnnotation}.
*
* @author Renaud Pawlak
*/
public class CtAnnotationImpl<A extends Annotation> extends CtExpressionImpl<A> implements CtAnnotation<A> {
private static final long serialVersionUID = 1L;
CtTypeReference<A> annotationType;
private Map<String, CtExpression> elementValues = new TreeMap() {
@Override
public Set<Entry<String, CtExpression>> entrySet() {
Set<Entry<String, CtExpression>> result = new TreeSet<Entry<String, CtExpression>>(new Comparator<Entry<String, CtExpression>>() {
final CtLineElementComparator comp = new CtLineElementComparator();
@Override
public int compare(Entry<String, CtExpression> o1, Entry<String, CtExpression> o2) {
return comp.compare(o1.getValue(), o2.getValue());
}
}
);
result.addAll(super.entrySet());
return result;
}
};
public CtAnnotationImpl() {
super();
}
public void accept(CtVisitor visitor) {
visitor.visitCtAnnotation(this);
}
@Override
public <T extends CtAnnotation<A>> T addValue(String elementName, Object value) {
if (value instanceof CtExpression) {
return addValueExpression(elementName, (CtExpression<?>) value);
}
return this.addValueExpression(elementName, convertValueToExpression(value));
}
private CtExpression convertValueToExpression(Object value) {
CtExpression res;
if (value.getClass().isArray()) {
// Value should be converted to a CtNewArray.
res = getFactory().Core().createNewArray();
Object[] values = (Object[]) value;
res.setType(getFactory().Type().createArrayReference(getFactory().Type().createReference(value.getClass().getComponentType())));
for (Object o : values) {
((CtNewArray) res).addElement(convertValueToExpression(o));
}
} else if (value instanceof Collection) {
// Value should be converted to a CtNewArray.
res = getFactory().Core().createNewArray();
Collection values = (Collection) value;
res.setType(getFactory().Type().createArrayReference(getFactory().Type().createReference(values.toArray()[0].getClass())));
for (Object o : values) {
((CtNewArray) res).addElement(convertValueToExpression(o));
}
} else if (value instanceof Class) {
// Value should be a field access to a .class.
res = getFactory().Code().createClassAccess(getFactory().Type().createReference((Class) value));
} else if (value instanceof Field) {
// Value should be a field access to a field.
CtFieldReference<Object> variable = getFactory().Field().createReference((Field) value);
variable.setStatic(true);
CtTypeAccess target = getFactory().Code().createTypeAccess(getFactory().Type().createReference(((Field) value).getDeclaringClass()));
CtFieldRead fieldRead = getFactory().Core().createFieldRead();
fieldRead.setVariable(variable);
fieldRead.setTarget(target);
fieldRead.setType(target.getAccessedType());
res = fieldRead;
} else if (isPrimitive(value.getClass()) || value instanceof String) {
// Value should be a literal.
res = getFactory().Code().createLiteral(value);
} else if (value.getClass().isEnum()) {
final CtTypeReference declaringClass = getFactory().Type().createReference(((Enum) value).getDeclaringClass());
final CtFieldReference variableRef = getFactory().Field().createReference(declaringClass, declaringClass, ((Enum) value).name());
CtTypeAccess target = getFactory().Code().createTypeAccess(declaringClass);
CtFieldRead fieldRead = getFactory().Core().createFieldRead();
fieldRead.setVariable(variableRef);
fieldRead.setTarget(target);
fieldRead.setType(declaringClass);
res = fieldRead;
} else {
throw new SpoonException("Please, submit a valid value.");
}
return res;
}
private boolean isPrimitive(Class c) {
return c.isPrimitive() || c == Byte.class || c == Short.class || c == Integer.class || c == Long.class || c == Float.class || c == Double.class || c == Boolean.class || c == Character.class;
}
private <T extends CtAnnotation<A>> T addValueExpression(String elementName, CtExpression<?> expression) {
if (elementValues.containsKey(elementName)) {
// Update value of the existing one.
final CtExpression ctExpression = (CtExpression) elementValues.get(elementName);
if (ctExpression instanceof CtNewArray) {
// Already an array, add the value inside it.
if (expression instanceof CtNewArray) {
List<CtExpression<?>> elements = ((CtNewArray) expression).getElements();
for (CtExpression expInArray : elements) {
((CtNewArray) ctExpression).addElement(expInArray);
}
} else {
((CtNewArray) ctExpression).addElement(expression);
}
} else {
// Switch the value to a CtNewArray.
CtNewArray<Object> newArray = getFactory().Core().createNewArray();
newArray.setType(ctExpression.getType());
newArray.setParent(this);
newArray.addElement(ctExpression);
newArray.addElement(expression);
elementValues.put(elementName, newArray);
}
} else {
// Add the new value.
elementValues.put(elementName, expression);
expression.setParent(this);
}
return (T) this;
}
@Override
public <T extends CtAnnotation<A>> T addValue(String elementName, CtLiteral<?> value) {
return addValueExpression(elementName, value);
}
@Override
public <T extends CtAnnotation<A>> T addValue(String elementName, CtNewArray<? extends CtExpression> value) {
return addValueExpression(elementName, value);
}
@Override
public <T extends CtAnnotation<A>> T addValue(String elementName, CtFieldAccess<?> value) {
return addValueExpression(elementName, value);
}
@Override
public <T extends CtAnnotation<A>> T addValue(String elementName, CtAnnotation<?> value) {
return addValueExpression(elementName, value);
}
@SuppressWarnings("unchecked")
private Object convertValue(Object value) {
if (value instanceof CtFieldReference) {
Class<?> c = null;
try {
c = ((CtFieldReference<?>) value).getDeclaringType().getActualClass();
} catch (Exception e) {
return ((CtLiteral<?>) ((CtFieldReference<?>) value).getDeclaration().getDefaultExpression()
.partiallyEvaluate()).getValue();
}
if (((CtFieldReference<?>) value).getSimpleName().equals("class")) {
return c;
}
CtField<?> field = ((CtFieldReference<?>) value).getDeclaration();
if (Enum.class.isAssignableFrom(c)) {
// Value references a Enum field
return Enum.valueOf((Class<? extends Enum>) c, ((CtFieldReference<?>) value).getSimpleName());
}
// Value is a static final
if (field != null) {
return convertValue(field.getDefaultExpression());
} else {
try {
return ((Field) ((CtFieldReference<?>) value).getActualField()).get(null);
} catch (Exception e) {
Launcher.LOGGER.error(e.getMessage(), e);
}
return null;
}
} else if (value instanceof CtFieldAccess) {
// Get variable
return convertValue(((CtFieldAccess<?>) value).getVariable());
} else if (value instanceof CtNewArray) {
CtNewArray<?> arrayExpression = (CtNewArray<?>) value;
Class<?> componentType = arrayExpression.getType().getActualClass().getComponentType();
List<CtExpression<?>> elements = arrayExpression.getElements();
Object array = Array.newInstance(componentType, elements.size());
for (int i = 0; i < elements.size(); i++) {
Array.set(array, i, this.convertValue(elements.get(i)));
}
return array;
} else if (value instanceof CtAnnotation) {
// Get proxy
return ((CtAnnotation<?>) value).getActualAnnotation();
} else if (value instanceof CtLiteral) {
// Replace literal by his value
return ((CtLiteral<?>) value).getValue();
} else if (value instanceof CtCodeElement) {
// Evaluate code elements
PartialEvaluator eval = getFactory().Eval().createPartialEvaluator();
Object ret = eval.evaluate((CtCodeElement) value);
return this.convertValue(ret);
} else if (value instanceof CtTypeReference) {
// Get RT class for References
return ((CtTypeReference<?>) value).getActualClass();
}
return value;
}
private Class<?> getElementType(String name) {
// Try by CT reflection
CtType<?> t = getAnnotationType().getDeclaration();
if (t != null) {
CtMethod<?> method = t.getMethod(name);
return method.getType().getActualClass();
}
// Try with RT reflection
Class<?> c = getAnnotationType().getActualClass();
for (Method m : c.getMethods()) {
if (m.getName().equals(name)) {
return m.getReturnType();
}
}
return null;
}
public CtTypeReference<A> getAnnotationType() {
return annotationType;
}
private Object getDefaultValue(String fieldName) {
Object ret = null;
CtAnnotationType<?> at = (CtAnnotationType<?>) getAnnotationType().getDeclaration();
if (at != null) {
CtAnnotationMethod<?> f = (CtAnnotationMethod) at.getMethod(fieldName);
ret = f.getDefaultExpression();
}
return ret;
}
@SuppressWarnings("unchecked")
public <T> T getElementValue(String key) {
Object ret = this.elementValues.get(key);
if (ret == null) {
ret = getDefaultValue(key);
}
if (ret == null) {
ret = getReflectValue(key);
}
Class<?> type = getElementType(key);
ret = this.convertValue(ret);
if (type.isPrimitive()) {
if ((type == boolean.class) && (ret.getClass() != boolean.class)) {
ret = Boolean.parseBoolean(ret.toString());
} else if ((type == byte.class) && (ret.getClass() != byte.class)) {
ret = Byte.parseByte(ret.toString());
} else if ((type == char.class) && (ret.getClass() != char.class)) {
ret = ret.toString().charAt(0);
} else if ((type == double.class) && (ret.getClass() != double.class)) {
ret = Double.parseDouble(ret.toString());
} else if ((type == float.class) && (ret.getClass() != float.class)) {
ret = Float.parseFloat(ret.toString());
} else if ((type == int.class) && (ret.getClass() != int.class)) {
ret = Integer.parseInt(ret.toString());
} else if ((type == long.class) && (ret.getClass() != long.class)) {
ret = Long.parseLong(ret.toString());
} else if (type == short.class && ret.getClass() != short.class) {
ret = Short.parseShort(ret.toString());
}
}
if (type.isArray() && ret != null && ret.getClass() != type) {
final Object array = Array.newInstance(ret.getClass(), 1);
((Object[]) array)[0] = ret;
ret = array;
}
return (T) ret;
}
@Override
public <T extends CtExpression> T getValue(String key) {
return (T) this.elementValues.get(key);
}
public Map<String, Object> getElementValues() {
TreeMap<String, Object> res = new TreeMap<>();
for (Entry<String, CtExpression> elementValue : elementValues.entrySet()) {
res.put(elementValue.getKey(), elementValue.getValue());
}
return res;
}
@Override
public Map<String, CtExpression> getValues() {
return Collections.unmodifiableMap(elementValues);
}
private Object getReflectValue(String fieldname) {
try {
Class<?> c = getAnnotationType().getActualClass();
Method m = c.getMethod(fieldname);
return m.getDefaultValue();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
@SuppressWarnings("unchecked")
public <T extends CtAnnotation<A>> T setAnnotationType(CtTypeReference<? extends Annotation> annotationType) {
if (annotationType != null) {
annotationType.setParent(this);
}
this.annotationType = (CtTypeReference<A>) annotationType;
return (T) this;
}
@Override
public <T extends CtAnnotation<A>> T setElementValues(Map<String, Object> values) {
this.elementValues.clear();
for (Entry<String, Object> e : values.entrySet()) {
addValue(e.getKey(), e.getValue());
}
return (T) this;
}
@Override
public <T extends CtAnnotation<A>> T setValues(Map<String, CtExpression> values) {
this.elementValues.clear();
for (Entry<String, CtExpression> e : values.entrySet()) {
addValue(e.getKey(), e.getValue());
}
return (T) this;
}
@Override
public CtElement getAnnotatedElement() {
return this.getParent();
}
@Override
public CtAnnotatedElementType getAnnotatedElementType() {
CtElement annotatedElement = this.getAnnotatedElement();
if (annotatedElement == null) {
return null;
}
if (annotatedElement instanceof CtMethod) {
return CtAnnotatedElementType.METHOD;
}
if (annotatedElement instanceof CtAnnotation || annotatedElement instanceof CtAnnotationType) {
return CtAnnotatedElementType.ANNOTATION_TYPE;
}
if (annotatedElement instanceof CtType) {
return CtAnnotatedElementType.TYPE;
}
if (annotatedElement instanceof CtField) {
return CtAnnotatedElementType.FIELD;
}
if (annotatedElement instanceof CtConstructor) {
return CtAnnotatedElementType.CONSTRUCTOR;
}
if (annotatedElement instanceof CtParameter) {
return CtAnnotatedElementType.PARAMETER;
}
if (annotatedElement instanceof CtLocalVariable) {
return CtAnnotatedElementType.LOCAL_VARIABLE;
}
if (annotatedElement instanceof CtPackage) {
return CtAnnotatedElementType.PACKAGE;
}
if (annotatedElement instanceof CtTypeParameterReference) {
return CtAnnotatedElementType.TYPE_PARAMETER;
}
if (annotatedElement instanceof CtTypeReference) {
return CtAnnotatedElementType.TYPE_USE;
}
return null;
}
@SuppressWarnings("unchecked")
public A getActualAnnotation() {
class AnnotationInvocationHandler implements InvocationHandler {
CtAnnotation<? extends Annotation> annotation;
AnnotationInvocationHandler(CtAnnotation<? extends Annotation> annotation) {
super();
this.annotation = annotation;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String fieldname = method.getName();
if ("toString".equals(fieldname)) {
return CtAnnotationImpl.this.toString();
} else if ("annotationType".equals(fieldname)) {
return annotation.getAnnotationType().getActualClass();
}
Object ret = getElementValue(fieldname);
// This is done here because return types should not be CT types;
// CtLiteral<String> vs String.
if (ret instanceof CtLiteral<?>) {
CtLiteral<?> l = (CtLiteral<?>) ret;
return l.getValue();
}
return ret;
}
}
return (A) Proxy.newProxyInstance(annotationType.getActualClass().getClassLoader(), new Class[] { annotationType.getActualClass() }, new AnnotationInvocationHandler(this));
}
boolean isShadow;
@Override
public boolean isShadow() {
return isShadow;
}
@Override
public <E extends CtShadowable> E setShadow(boolean isShadow) {
this.isShadow = isShadow;
return (E) this;
}
@Override
public CtAnnotation<A> clone() {
return (CtAnnotation<A>) super.clone();
}
}