
name.remal.annotation.AnnotationUtils Maven / Gradle / Ivy
package name.remal.annotation;
import com.google.common.base.Joiner;
import name.remal.proxy.CompositeInvocationHandler;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.*;
import java.util.Map.Entry;
import static java.lang.String.format;
import static java.lang.reflect.Modifier.isStatic;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static name.remal.ArrayUtils.*;
import static name.remal.PrimitiveTypeUtils.unwrap;
import static name.remal.PrimitiveTypeUtils.wrap;
import static name.remal.SneakyThrow.sneakyThrow;
import static name.remal.UncheckedCast.uncheckedCast;
import static name.remal.reflection.HierarchyUtils.getHierarchy;
import static name.remal.reflection.HierarchyUtils.getPackageHierarchy;
public class AnnotationUtils {
public static boolean isLangCoreAnnotation(@Nonnull Class extends Annotation> type) {
String typeName = type.getName();
if (typeName.startsWith("java.lang.")) return true;
if (typeName.startsWith("kotlin.")) return true;
return false;
}
@Nonnull
public static List getAttributeMethods(@Nonnull Class extends Annotation> type) {
List result = new ArrayList<>();
for (Method method : type.getDeclaredMethods()) {
if (isStatic(method.getModifiers())) continue;
if (method.isSynthetic()) continue;
if (0 != method.getParameterCount()) continue;
if (Void.TYPE == method.getReturnType()) continue;
result.add(method);
}
return result;
}
@Nonnull
public static T newAnnotationInstance(@Nonnull Class type, @Nullable Map attributes) {
List attributeMethods = getAttributeMethods(type);
attributeMethods.forEach(method -> method.setAccessible(true));
Map fullAttributes = new HashMap<>();
attributeMethods.forEach(attributeMethod -> {
String attributeName = attributeMethod.getName();
Object value = null != attributes ? attributes.get(attributeName) : null;
if (null == value) value = attributeMethod.getDefaultValue();
if (null == value) throw new IllegalArgumentException("Missing value for " + attributeName);
if (!wrap(attributeMethod.getReturnType()).isInstance(value)) {
throw new ErrorCreatingAnnotationInstanceException(type, format(
"Attribute %s must be %s, but %s provided",
attributeName,
unwrap(attributeMethod.getReturnType()),
unwrap(value.getClass())
));
}
fullAttributes.put(attributeName, value);
});
if (null != attributes) {
Set unknownAttrs = new LinkedHashSet<>();
for (String attr : attributes.keySet()) {
if (!fullAttributes.containsKey(attr)) unknownAttrs.add(attr);
}
if (!unknownAttrs.isEmpty()) {
if (1 == unknownAttrs.size()) {
throw new ErrorCreatingAnnotationInstanceException(type, "Unknown attribute: " + unknownAttrs.iterator().next());
} else {
throw new ErrorCreatingAnnotationInstanceException(type, "Unknown attribute: " + Joiner.on(", ").join(unknownAttrs));
}
}
}
final int hashCode;
{
int value = 0;
for (Entry entry : fullAttributes.entrySet()) {
value += 127 * entry.getKey().hashCode() ^ arrayHashCode(entry.getValue());
}
hashCode = value;
}
final String stringRepresentation;
{
StringBuilder sb = new StringBuilder();
sb.append('@').append(type.getName()).append('(');
boolean isFirst = true;
for (Entry entry : fullAttributes.entrySet()) {
if (isFirst) isFirst = false;
else sb.append(", ");
sb.append(entry.getKey()).append('=').append(arrayToString(entry.getValue()));
}
sb.append(')');
stringRepresentation = sb.toString();
}
return uncheckedCast(Proxy.newProxyInstance(
type.getClassLoader(),
new Class[]{type},
new CompositeInvocationHandler()
.appendEqualsHandler((proxy, other) -> {
if (!type.isInstance(other)) return false;
for (Method attributeMethod : attributeMethods) {
if (!arrayEquals(
fullAttributes.get(attributeMethod.getName()),
attributeMethod.invoke(other)
)) {
return false;
}
}
return true;
})
.appendHashCodeHandler(hashCode)
.appendToStringHandler(stringRepresentation)
.appendConstMethodHandler(
method -> 0 == method.getParameterCount() && "annotationType".equals(method.getName()),
type
)
.appendMethodHandler(
method -> 0 == method.getParameterCount() && fullAttributes.containsKey(method.getName()),
(proxy, method, args) -> fullAttributes.get(method.getName())
)
));
}
@Nonnull
public static Map getAttributes(@Nonnull T annotation) {
Map attrs = new HashMap<>();
for (Method method : getAttributeMethods(annotation.annotationType())) {
method.setAccessible(true);
try {
attrs.put(method.getName(), method.invoke(annotation));
} catch (Throwable throwable) {
throw sneakyThrow(throwable);
}
}
return attrs;
}
@Nonnull
public static T withAttributes(@Nonnull T annotation, @Nullable Map attributes) {
if (null == attributes || attributes.isEmpty()) return annotation;
Map fullAttributes = new HashMap<>(getAttributes(annotation));
fullAttributes.putAll(attributes);
return newAnnotationInstance(uncheckedCast(annotation.annotationType()), fullAttributes);
}
public static boolean canAnnotate(@Nonnull Class type, @Nonnull ElementType elementType) {
Target target = type.getDeclaredAnnotation(Target.class);
return null == target || contains(target.value(), elementType);
}
public static boolean canAnnotate(@Nonnull Class extends Annotation> type, @Nonnull AnnotatedElement annotatedElement) {
if (annotatedElement instanceof Annotation) return canAnnotate(type, ElementType.ANNOTATION_TYPE);
if (annotatedElement instanceof Class) return canAnnotate(type, ElementType.TYPE);
if (annotatedElement instanceof Field) return canAnnotate(type, ElementType.FIELD);
if (annotatedElement instanceof Method) return canAnnotate(type, ElementType.METHOD);
if (annotatedElement instanceof Parameter) return canAnnotate(type, ElementType.PARAMETER);
if (annotatedElement instanceof Constructor) return canAnnotate(type, ElementType.CONSTRUCTOR);
if (annotatedElement instanceof Package) return canAnnotate(type, ElementType.PACKAGE);
if (annotatedElement instanceof AnnotatedTypeVariable) return canAnnotate(type, ElementType.TYPE_PARAMETER) || canAnnotate(type, ElementType.TYPE_USE);
if (annotatedElement instanceof AnnotatedType) return canAnnotate(type, ElementType.TYPE_USE);
return false;
}
@Nullable
public static T getMetaAnnotation(@Nonnull AnnotatedElement annotatedElement, @Nonnull Class type) {
List annotations = getMetaAnnotationsImpl(annotatedElement, type, true);
if (!annotations.isEmpty()) return annotations.get(0);
return null;
}
@Nonnull
public static List getMetaAnnotations(@Nonnull AnnotatedElement annotatedElement, @Nonnull Class type) {
return getMetaAnnotationsImpl(annotatedElement, type, false);
}
@Nonnull
private static List getMetaAnnotationsImpl(@Nonnull AnnotatedElement rootAnnotatedElement, @Nonnull Class type, boolean doReturnOnlyFirst) {
Set result = null;
boolean canAnnotateAnnotations = canAnnotate(type, ElementType.ANNOTATION_TYPE);
Set processedMetaAnnotationScanContexts = new HashSet<>();
Queue annotatedElementsQueue = new LinkedList<>();
annotatedElementsQueue.add(new MetaAnnotationScanContext(rootAnnotatedElement, null));
while (true) {
MetaAnnotationScanContext metaAnnotationScanContext = annotatedElementsQueue.poll();
if (null == metaAnnotationScanContext) break;
if (!processedMetaAnnotationScanContexts.add(metaAnnotationScanContext)) continue;
AnnotatedElement annotatedElement = metaAnnotationScanContext.getAnnotatedElement();
List attributes = metaAnnotationScanContext.getAttributes();
{
// Get directly declared annotation:
T annotation = annotatedElement.getDeclaredAnnotation(type);
if (null != annotation) {
T resultAnnotation = withAttributes(annotation, attributes);
if (doReturnOnlyFirst) return singletonList(resultAnnotation);
if (null == result) result = new LinkedHashSet<>();
result.add(resultAnnotation);
}
}
List otherDeclaredAnnotations = new ArrayList<>();
for (Annotation annotation : annotatedElement.getDeclaredAnnotations()) {
Class extends Annotation> annotationType = annotation.annotationType();
if (type != annotationType && !isLangCoreAnnotation(annotationType)) {
otherDeclaredAnnotations.add(annotation);
}
}
// Parse repeatable-container annotations:
for (Annotation annotation : otherDeclaredAnnotations) {
List attributeMethods = getAttributeMethods(annotation.annotationType());
if (1 != attributeMethods.size()) continue;
Method valueMethod = attributeMethods.get(0);
if (!valueMethod.getReturnType().isArray() || type != valueMethod.getReturnType().getComponentType()) continue;
if (!"value".equals(valueMethod.getName())) continue;
valueMethod.setAccessible(true);
final T[] values;
try {
values = uncheckedCast(valueMethod.invoke(annotation));
} catch (Throwable throwable) {
throw sneakyThrow(throwable);
}
for (T value : values) {
T resultAnnotation = withAttributes(value, attributes);
if (doReturnOnlyFirst) return singletonList(resultAnnotation);
if (null == result) result = new LinkedHashSet<>();
result.add(resultAnnotation);
}
}
// Parse annotations on annotations:
if (canAnnotateAnnotations) {
for (Annotation annotation : otherDeclaredAnnotations) {
Class extends Annotation> annotationType = annotation.annotationType();
List innerAttributes = null;
for (Method attributeMethod : getAttributeMethods(annotationType)) {
for (AnnotationAttributeAlias alias : attributeMethod.getDeclaredAnnotationsByType(AnnotationAttributeAlias.class)) {
if (null == innerAttributes) {
innerAttributes = new ArrayList<>();
if (null != attributes) innerAttributes.addAll(attributes);
}
Object value = null;
if (null != attributes) {
for (AnnotationAttribute attr : attributes) {
if (annotationType == attr.getAnnotationType() && Objects.equals(attributeMethod.getName(), attr.getName())) {
value = attr.getValue();
break;
}
}
}
if (null == value) {
attributeMethod.setAccessible(true);
try {
value = attributeMethod.invoke(annotation);
} catch (Throwable throwable) {
throw sneakyThrow(throwable);
}
}
innerAttributes.add(new AnnotationAttribute(
alias.annotationClass(),
alias.attributeName(),
value
));
}
}
annotatedElementsQueue.add(new MetaAnnotationScanContext(
annotationType,
null != innerAttributes ? innerAttributes : attributes
));
}
}
// Scan inheritance hierarchy for root annotated element:
if (rootAnnotatedElement != annotatedElement) continue;
if (null == type.getDeclaredAnnotation(Inherited.class)) continue;
if (annotatedElement instanceof Parameter) {
List hierarchy = getHierarchy((Parameter) annotatedElement);
if (2 <= hierarchy.size()) {
for (AnnotatedElement curAnnotatedElement : hierarchy.subList(1, hierarchy.size())) {
annotatedElementsQueue.add(new MetaAnnotationScanContext(curAnnotatedElement, attributes));
}
}
} else if (annotatedElement instanceof Method) {
List hierarchy = getHierarchy((Method) annotatedElement);
if (2 <= hierarchy.size()) {
for (AnnotatedElement curAnnotatedElement : hierarchy.subList(1, hierarchy.size())) {
annotatedElementsQueue.add(new MetaAnnotationScanContext(curAnnotatedElement, attributes));
}
}
} else if (annotatedElement instanceof Class) {
List> hierarchy = uncheckedCast(getHierarchy((Class>) annotatedElement));
if (2 <= hierarchy.size()) {
for (AnnotatedElement curAnnotatedElement : hierarchy.subList(1, hierarchy.size())) {
annotatedElementsQueue.add(new MetaAnnotationScanContext(curAnnotatedElement, attributes));
}
}
if (canAnnotate(type, ElementType.PACKAGE)) {
for (Class> curClass : hierarchy) {
for (Package curPackage : getPackageHierarchy(curClass)) {
annotatedElementsQueue.add(new MetaAnnotationScanContext(curPackage, attributes));
}
}
}
}
}
return null == result ? emptyList() : new ArrayList<>(result);
}
@Nonnull
private static T withAttributes(@Nonnull T annotation, @Nullable Collection attributes) {
if (null == attributes || attributes.isEmpty()) return annotation;
Map attrsMap = new HashMap<>();
Class annotationType = uncheckedCast(annotation.annotationType());
for (AnnotationAttribute attr : attributes) {
if (annotationType == attr.getAnnotationType()) {
attrsMap.putIfAbsent(attr.getName(), attr.getValue());
}
}
getAttributes(annotation).forEach(attrsMap::putIfAbsent);
return newAnnotationInstance(annotationType, attrsMap);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy