/*
* 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.asm.util;
import static com.google.common.base.Preconditions.*;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import org.spongepowered.asm.lib.Type;
import org.spongepowered.asm.lib.tree.AnnotationNode;
import org.spongepowered.asm.lib.tree.ClassNode;
import org.spongepowered.asm.lib.tree.FieldNode;
import org.spongepowered.asm.lib.tree.MethodNode;
/**
* Utility class for working with ASM annotations
*/
public final class Annotations {
private Annotations() {
// Utility class
}
/**
* Set a runtime-visible annotation of the specified class on the supplied
* field node
*
* @param field Target field
* @param annotationClass Type of annotation to search for
* @param value Values (interleaved key/value pairs) to set
*/
public static void setVisible(FieldNode field, Class<? extends Annotation> annotationClass, Object... value) {
AnnotationNode node = Annotations.createNode(Type.getDescriptor(annotationClass), value);
field.visibleAnnotations = Annotations.add(field.visibleAnnotations, node);
}
/**
* Set an invisible annotation of the specified class on the supplied field
* node
*
* @param field Target field
* @param annotationClass Type of annotation to search for
* @param value Values (interleaved key/value pairs) to set
*/
public static void setInvisible(FieldNode field, Class<? extends Annotation> annotationClass, Object... value) {
AnnotationNode node = Annotations.createNode(Type.getDescriptor(annotationClass), value);
field.invisibleAnnotations = Annotations.add(field.invisibleAnnotations, node);
}
/**
* Set a runtime-visible annotation of the specified class on the supplied
* method node
*
* @param method Target method
* @param annotationClass Type of annotation to search for
* @param value Values (interleaved key/value pairs) to set
*/
public static void setVisible(MethodNode method, Class<? extends Annotation> annotationClass, Object... value) {
AnnotationNode node = Annotations.createNode(Type.getDescriptor(annotationClass), value);
method.visibleAnnotations = Annotations.add(method.visibleAnnotations, node);
}
/**
* Set a invisible annotation of the specified class on the supplied method
* node
*
* @param method Target method
* @param annotationClass Type of annotation to search for
* @param value Values (interleaved key/value pairs) to set
*/
public static void setInvisible(MethodNode method, Class<? extends Annotation> annotationClass, Object... value) {
AnnotationNode node = Annotations.createNode(Type.getDescriptor(annotationClass), value);
method.invisibleAnnotations = Annotations.add(method.invisibleAnnotations, node);
}
/**
* Create a new annotation node with the supplied values
*
* @param annotationType Name (internal name) of the annotation interface to
* create
* @param value Interleaved key/value pairs. Keys must be strings
* @return new annotation node
*/
private static AnnotationNode createNode(String annotationType, Object... value) {
AnnotationNode node = new AnnotationNode(annotationType);
for (int pos = 0; pos < value.length - 1; pos += 2) {
if (!(value[pos] instanceof String)) {
throw new IllegalArgumentException("Annotation keys must be strings, found " + value[pos].getClass().getSimpleName()
+ " with " + value[pos].toString() + " at index " + pos + " creating " + annotationType);
}
node.visit((String)value[pos], value[pos + 1]);
}
return node;
}
private static List<AnnotationNode> add(List<AnnotationNode> annotations, AnnotationNode node) {
if (annotations == null) {
annotations = new ArrayList<AnnotationNode>(1);
} else {
annotations.remove(Annotations.get(annotations, node.desc));
}
annotations.add(node);
return annotations;
}
/**
* Get a runtime-visible annotation of the specified class from the supplied
* field node
*
* @param field Source field
* @param annotationClass Type of annotation to search for
* @return the annotation, or null if not present
*/
public static AnnotationNode getVisible(FieldNode field, Class<? extends Annotation> annotationClass) {
return Annotations.get(field.visibleAnnotations, Type.getDescriptor(annotationClass));
}
/**
* Get an invisible annotation of the specified class from the supplied
* field node
*
* @param field Source field
* @param annotationClass Type of annotation to search for
* @return the annotation, or null if not present
*/
public static AnnotationNode getInvisible(FieldNode field, Class<? extends Annotation> annotationClass) {
return Annotations.get(field.invisibleAnnotations, Type.getDescriptor(annotationClass));
}
/**
* Get a runtime-visible annotation of the specified class from the supplied
* method node
*
* @param method Source method
* @param annotationClass Type of annotation to search for
* @return the annotation, or null if not present
*/
public static AnnotationNode getVisible(MethodNode method, Class<? extends Annotation> annotationClass) {
return Annotations.get(method.visibleAnnotations, Type.getDescriptor(annotationClass));
}
/**
* Get an invisible annotation of the specified class from the supplied
* method node
*
* @param method Source method
* @param annotationClass Type of annotation to search for
* @return the annotation, or null if not present
*/
public static AnnotationNode getInvisible(MethodNode method, Class<? extends Annotation> annotationClass) {
return Annotations.get(method.invisibleAnnotations, Type.getDescriptor(annotationClass));
}
/**
* Get a runtime-visible annotation of the specified class from the supplied
* method node
*
* @param method Source method
* @param annotationClasses Types of annotation to search for
* @return the annotation, or null if not present
*/
public static AnnotationNode getSingleVisible(MethodNode method, Class<? extends Annotation>... annotationClasses) {
return Annotations.getSingle(method.visibleAnnotations, annotationClasses);
}
/**
* Get an invisible annotation of the specified class from the supplied
* method node
*
* @param method Source method
* @param annotationClasses Types of annotation to search for
* @return the annotation, or null if not present
*/
public static AnnotationNode getSingleInvisible(MethodNode method, Class<? extends Annotation>... annotationClasses) {
return Annotations.getSingle(method.invisibleAnnotations, annotationClasses);
}
/**
* Get a runtime-visible annotation of the specified class from the supplied
* class node
*
* @param classNode Source classNode
* @param annotationClass Type of annotation to search for
* @return the annotation, or null if not present
*/
public static AnnotationNode getVisible(ClassNode classNode, Class<? extends Annotation> annotationClass) {
return Annotations.get(classNode.visibleAnnotations, Type.getDescriptor(annotationClass));
}
/**
* Get an invisible annotation of the specified class from the supplied
* class node
*
* @param classNode Source classNode
* @param annotationClass Type of annotation to search for
* @return the annotation, or null if not present
*/
public static AnnotationNode getInvisible(ClassNode classNode, Class<? extends Annotation> annotationClass) {
return Annotations.get(classNode.invisibleAnnotations, Type.getDescriptor(annotationClass));
}
/**
* Get a runtime-visible parameter annotation of the specified class from
* the supplied method node
*
* @param method Source method
* @param annotationClass Type of annotation to search for
* @param paramIndex Index of the parameter to fetch annotation for
* @return the annotation, or null if not present
*/
public static AnnotationNode getVisibleParameter(MethodNode method, Class<? extends Annotation> annotationClass, int paramIndex) {
return Annotations.getParameter(method.visibleParameterAnnotations, Type.getDescriptor(annotationClass), paramIndex);
}
/**
* Get an invisible parameter annotation of the specified class from the
* supplied method node
*
* @param method Source method
* @param annotationClass Type of annotation to search for
* @param paramIndex Index of the parameter to fetch annotation for
* @return the annotation, or null if not present
*/
public static AnnotationNode getInvisibleParameter(MethodNode method, Class<? extends Annotation> annotationClass, int paramIndex) {
return Annotations.getParameter(method.invisibleParameterAnnotations, Type.getDescriptor(annotationClass), paramIndex);
}
/**
* Get a parameter annotation of the specified class from the supplied
* method node
*
* @param parameterAnnotations Annotations for the parameter
* @param annotationType Type of annotation to search for
* @param paramIndex Index of the parameter to fetch annotation for
* @return the annotation, or null if not present
*/
public static AnnotationNode getParameter(List<AnnotationNode>[] parameterAnnotations, String annotationType, int paramIndex) {
if (parameterAnnotations == null || paramIndex < 0 || paramIndex >= parameterAnnotations.length) {
return null;
}
return Annotations.get(parameterAnnotations[paramIndex], annotationType);
}
/**
* Search for and return an annotation node matching the specified type
* within the supplied
* collection of annotation nodes
*
* @param annotations Haystack
* @param annotationType Needle
* @return matching annotation node or null if the annotation doesn't exist
*/
public static AnnotationNode get(List<AnnotationNode> annotations, String annotationType) {
if (annotations == null) {
return null;
}
for (AnnotationNode annotation : annotations) {
if (annotationType.equals(annotation.desc)) {
return annotation;
}
}
return null;
}
private static AnnotationNode getSingle(List<AnnotationNode> annotations, Class<? extends Annotation>[] annotationClasses) {
List<AnnotationNode> nodes = new ArrayList<AnnotationNode>();
for (Class<? extends Annotation> annotationClass : annotationClasses) {
AnnotationNode annotation = Annotations.get(annotations, Type.getDescriptor(annotationClass));
if (annotation != null) {
nodes.add(annotation);
}
}
int foundNodes = nodes.size();
if (foundNodes > 1) {
throw new IllegalArgumentException("Conflicting annotations found: " + annotationClasses);
}
return foundNodes == 0 ? null : nodes.get(0);
}
/**
* Duck type the "value" entry (if any) of the specified annotation node
*
* @param <T> duck type
* @param annotation Annotation node to query
* @return duck-typed annotation value, null if missing, or inevitable
* {@link ClassCastException} if your duck is actually a rooster
*/
public static <T> T getValue(AnnotationNode annotation) {
return Annotations.getValue(annotation, "value");
}
/**
* Get the value of an annotation node and do pseudo-duck-typing via Java's
* crappy generics
*
* @param <T> duck type
* @param annotation Annotation node to query
* @param key Key to search for
* @param defaultValue Value to return if the specified key is not found or
* is null
* @return duck-typed annotation value, null if missing, or inevitable
* {@link ClassCastException} if your duck is actually a rooster
*/
public static <T> T getValue(AnnotationNode annotation, String key, T defaultValue) {
T returnValue = Annotations.getValue(annotation, key);
return returnValue != null ? returnValue : defaultValue;
}
/**
* Gets an annotation value or returns the default value of the annotation
* if the annotation value is not present
*
* @param <T> duck type
* @param annotation Annotation node to query
* @param key Key to search for
* @param annotationClass Annotation class to query reflectively for the
* default value
* @return Value of the specified annotation node, default value if not
* specified, or null if no value or default
*/
@SuppressWarnings("unchecked")
public static <T> T getValue(AnnotationNode annotation, String key, Class<?> annotationClass) {
checkNotNull(annotationClass, "annotationClass cannot be null");
T value = Annotations.getValue(annotation, key);
if (value == null) {
try {
value = (T)annotationClass.getDeclaredMethod(key).getDefaultValue();
} catch (NoSuchMethodException ex) {
// Don't care
}
}
return value;
}
/**
* Get the value of an annotation node and do pseudo-duck-typing via Java's
* crappy generics
*
* @param <T> duck type
* @param annotation Annotation node to query
* @param key Key to search for
* @return duck-typed annotation value, null if missing, or inevitable
* {@link ClassCastException} if your duck is actually a rooster
*/
@SuppressWarnings("unchecked")
public static <T> T getValue(AnnotationNode annotation, String key) {
boolean getNextValue = false;
if (annotation == null || annotation.values == null) {
return null;
}
// Keys and value are stored in successive pairs, search for the key and if found return the following entry
for (Object value : annotation.values) {
if (getNextValue) {
return (T) value;
}
if (value.equals(key)) {
getNextValue = true;
}
}
return null;
}
/**
* Get the value of an annotation node as the specified enum, returns
* defaultValue if the annotation value is not set
*
* @param <T> duck type
* @param annotation Annotation node to query
* @param key Key to search for
* @param enumClass Class of enum containing the enum constant to search for
* @param defaultValue Value to return if the specified key isn't found
* @return duck-typed annotation value or defaultValue if missing
*/
public static <T extends Enum<T>> T getValue(AnnotationNode annotation, String key, Class<T> enumClass, T defaultValue) {
String[] value = Annotations.<String[]>getValue(annotation, key);
if (value == null) {
return defaultValue;
}
return Annotations.toEnumValue(enumClass, value);
}
/**
* Return the specified annotation node value as a list of nodes
*
* @param <T> list element type
* @param annotation Annotation node to query
* @param key Key to search for
* @param notNull if true, return an empty list instead of null if the
* annotation value is absent
*/
@SuppressWarnings("unchecked")
public static <T> List<T> getValue(AnnotationNode annotation, String key, boolean notNull) {
Object value = Annotations.<Object>getValue(annotation, key);
if (value instanceof List) {
return (List<T>)value;
} else if (value != null) {
List<T> list = new ArrayList<T>();
list.add((T)value);
return list;
}
return Collections.<T>emptyList();
}
/**
* Return the specified annotation node value as a list of enums
*
* @param <T> list element type
* @param annotation Annotation node to query
* @param key Key to search for
* @param notNull if true, return an empty list instead of null if the
* annotation value is absent
* @param enumClass Class of enum containing the enum constant to use
*/
@SuppressWarnings("unchecked")
public static <T extends Enum<T>> List<T> getValue(AnnotationNode annotation, String key, boolean notNull, Class<T> enumClass) {
Object value = Annotations.<Object>getValue(annotation, key);
if (value instanceof List) {
for (ListIterator<Object> iter = ((List<Object>)value).listIterator(); iter.hasNext();) {
iter.set(Annotations.toEnumValue(enumClass, (String[])iter.next()));
}
return (List<T>)value;
} else if (value instanceof String[]) {
List<T> list = new ArrayList<T>();
list.add(Annotations.toEnumValue(enumClass, (String[])value));
return list;
}
return Collections.<T>emptyList();
}
private static <T extends Enum<T>> T toEnumValue(Class<T> enumClass, String[] value) {
if (!enumClass.getName().equals(Type.getType(value[0]).getClassName())) {
throw new IllegalArgumentException("The supplied enum class does not match the stored enum value");
}
return Enum.valueOf(enumClass, value[1]);
}
}