/*
* This file is part of Mixin, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.spongepowered.tools.obfuscation.mirror;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import com.google.common.collect.ImmutableList;
/**
* A wrapper for {@link AnnotationMirror} which provides a more convenient way
* to access annotation values.
*/
public final class AnnotationHandle {
/**
* Annotation being wrapped
*/
private final AnnotationMirror annotation;
/**
* ctor
*
* @param annotation annotation to wrap
*/
private AnnotationHandle(AnnotationMirror annotation) {
this.annotation = annotation;
}
/**
* Return the wrapped mirror, only used to pass to Messager methods
*
* @return annotation mirror (can be null)
*/
public AnnotationMirror asMirror() {
return this.annotation;
}
/**
* Get whether the annotation mirror actually exists, if the mirror is null
* returns false
*
* @return true if the annotation exists
*/
public boolean exists() {
return this.annotation != null;
}
@Override
public String toString() {
if (this.annotation == null) {
return "@{UnknownAnnotation}";
}
return "@" + this.annotation.getAnnotationType().asElement().getSimpleName();
}
/**
* Get a value with the specified key from this annotation, return the
* specified default value if the key is not set or is not present
*
* @param key key
* @param defaultValue value to return if the key is not set or not present
* @return value or default if not set
* @param <T> duck type
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public <T> T getValue(String key, T defaultValue) {
if (this.annotation == null) {
return defaultValue;
}
AnnotationValue value = this.getAnnotationValue(key);
if (defaultValue instanceof Enum && value != null) {
VariableElement varValue = (VariableElement)value.getValue();
if (varValue == null) {
return defaultValue;
}
return (T)Enum.valueOf((Class<? extends Enum>)defaultValue.getClass(), varValue.getSimpleName().toString());
}
return value != null ? (T)value.getValue() : defaultValue;
}
/**
* Get the annotation value or return null if not present or not set
*
* @param <T> duck type
* @return value or null if not present or not set
*/
public <T> T getValue() {
return this.getValue("value", null);
}
/**
* Get the annotation value with the specified key or return null if not
* present or not set
*
* @param key key to fetch
* @param <T> duck type
* @return value or null if not present or not set
*/
public <T> T getValue(String key) {
return this.getValue(key, null);
}
/**
* Get the primitive boolean value with the specified key or return null if
* not present or not set
*
* @param key key to fetch
* @param defaultValue default value to return if value is not present
* @return value or default if not present or not set
*/
public boolean getBoolean(String key, boolean defaultValue) {
return this.<Boolean>getValue(key, Boolean.valueOf(defaultValue)).booleanValue();
}
/**
* Get an annotation value as an annotation handle
*
* @param key key to search for in the value map
* @return value or <tt>null</tt> if not set
*/
public AnnotationHandle getAnnotation(String key) {
Object value = this.getValue(key);
if (value instanceof AnnotationMirror) {
return AnnotationHandle.of((AnnotationMirror)value);
} else if (value instanceof AnnotationValue) {
Object mirror = ((AnnotationValue)value).getValue();
if (mirror instanceof AnnotationMirror) {
return AnnotationHandle.of((AnnotationMirror)mirror);
}
}
return null;
}
/**
* Retrieve the annotation value as a list with values of the specified
* type. Returns an empty list if the value is not present or not set.
*
* @param <T> list element duck type
* @return list of values
*/
public <T> List<T> getList() {
return this.<T>getList("value");
}
/**
* Retrieve the annotation value with the specified key as a list with
* values of the specified type. Returns an empty list if the value is not
* present or not set.
*
* @param key key to fetch
* @param <T> list element duck type
* @return list of values
*/
public <T> List<T> getList(String key) {
List<AnnotationValue> list = this.<List<AnnotationValue>>getValue(key, Collections.<AnnotationValue>emptyList());
return AnnotationHandle.<T>unwrapAnnotationValueList(list);
}
/**
* Retrieve an annotation key as a list of annotation handles
*
* @param key key to fetch
* @return list of annotations
*/
public List<AnnotationHandle> getAnnotationList(String key) {
Object val = this.getValue(key, null);
if (val == null) {
return Collections.<AnnotationHandle>emptyList();
}
// Fix for JDT, single values are just returned as a bare AnnotationMirror
if (val instanceof AnnotationMirror) {
return ImmutableList.<AnnotationHandle>of(AnnotationHandle.of((AnnotationMirror)val));
}
@SuppressWarnings("unchecked") List<AnnotationValue> list = (List<AnnotationValue>)val;
List<AnnotationHandle> annotations = new ArrayList<AnnotationHandle>(list.size());
for (AnnotationValue value : list) {
annotations.add(new AnnotationHandle((AnnotationMirror)value.getValue()));
}
return Collections.<AnnotationHandle>unmodifiableList(annotations);
}
protected AnnotationValue getAnnotationValue(String key) {
for (ExecutableElement elem : this.annotation.getElementValues().keySet()) {
if (elem.getSimpleName().contentEquals(key)) {
return this.annotation.getElementValues().get(elem);
}
}
return null;
}
@SuppressWarnings("unchecked")
protected static <T> List<T> unwrapAnnotationValueList(List<AnnotationValue> list) {
if (list == null) {
return Collections.<T>emptyList();
}
List<T> unfolded = new ArrayList<T>(list.size());
for (AnnotationValue value : list) {
unfolded.add((T)value.getValue());
}
return unfolded;
}
protected static AnnotationMirror getAnnotation(Element elem, Class<? extends Annotation> annotationClass) {
if (elem == null) {
return null;
}
List<? extends AnnotationMirror> annotations = elem.getAnnotationMirrors();
if (annotations == null) {
return null;
}
for (AnnotationMirror annotation : annotations) {
Element element = annotation.getAnnotationType().asElement();
if (!(element instanceof TypeElement)) {
continue;
}
TypeElement annotationElement = (TypeElement)element;
if (annotationElement.getQualifiedName().contentEquals(annotationClass.getName())) {
return annotation;
}
}
return null;
}
/**
* Returns a new annotation handle for the supplied annotation mirror
*
* @param annotation annotation mirror to wrap (can be null)
* @return new annotation handle
*/
public static AnnotationHandle of(AnnotationMirror annotation) {
return new AnnotationHandle(annotation);
}
/**
* Returns a new annotation handle for the specified annotation on the
* supplied element, consumers should call {@link #exists} in order to check
* whether the requested annotation is present.
*
* @param elem element
* @param annotationClass annotation class to search for
* @return new annotation handle
*/
public static AnnotationHandle of(Element elem, Class<? extends Annotation> annotationClass) {
return new AnnotationHandle(AnnotationHandle.getAnnotation(elem, annotationClass));
}
}