/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.isis.core.metamodel.facets; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collections; import java.util.List; import com.google.common.collect.Lists; import org.apache.isis.applib.annotation.Collection; import org.apache.isis.applib.annotation.CollectionLayout; import org.apache.isis.applib.annotation.MemberOrder; import org.apache.isis.applib.annotation.Property; import org.apache.isis.applib.annotation.PropertyLayout; import org.apache.isis.applib.annotation.Title; import org.apache.isis.core.commons.lang.ThrowableExtensions; import org.apache.isis.core.metamodel.exceptions.MetaModelException; import org.apache.isis.core.metamodel.methodutils.MethodScope; public final class Annotations { private Annotations() {} /** * For convenience of the several annotations that apply only to * {@link String}s. */ public static boolean isString(final Class<?> cls) { return cls.equals(String.class); } @SuppressWarnings("unchecked") public static <T extends Annotation> T getDeclaredAnnotation(Class<?> cls, Class<T> annotationClass) { final Annotation[] declaredAnnotations = cls.getDeclaredAnnotations(); if(declaredAnnotations == null) { return null; } for (Annotation annotation : declaredAnnotations) { if(annotationClass.isAssignableFrom(annotation.getClass())) { return (T) annotation; } } return null; } /** * Searches for annotation on provided class, and if not found for the * superclass. * * <p> * Added to allow bytecode-mangling libraries such as CGLIB to be supported. */ public static <T extends Annotation> T getAnnotation(final Class<?> cls, final Class<T> annotationClass) { if (cls == null) { return null; } final T annotation = cls.getAnnotation(annotationClass); if (annotation != null) { return annotation; } // search superclasses final Class<?> superclass = cls.getSuperclass(); if (superclass != null) { try { final T annotationFromSuperclass = getAnnotation(superclass, annotationClass); if (annotationFromSuperclass != null) { return annotationFromSuperclass; } } catch (final SecurityException e) { // fall through } } // search implemented interfaces final Class<?>[] interfaces = cls.getInterfaces(); for (final Class<?> iface : interfaces) { final T annotationFromInterface = getAnnotation(iface, annotationClass); if (annotationFromInterface != null) { return annotationFromInterface; } } return null; } /** * Searches for annotation on provided method, and if not found for any * inherited methods up from the superclass. */ public static <T extends Annotation> T getAnnotation( final Method method, final Class<T> annotationClass) { if (method == null) { return null; } final Class<?> methodDeclaringClass = method.getDeclaringClass(); final String methodName = method.getName(); final T annotation = method.getAnnotation(annotationClass); if (annotation != null) { return annotation; } // search for field if ( shouldSearchForField(annotationClass) ) { List<String> fieldNameCandidates = fieldNameCandidatesFor(methodName); for (String fieldNameCandidate : fieldNameCandidates) { try { final Field field = methodDeclaringClass.getDeclaredField(fieldNameCandidate); final T fieldAnnotation = field.getAnnotation(annotationClass); if(fieldAnnotation != null) { return fieldAnnotation; } } catch (NoSuchFieldException e) { // fall through } } } // search superclasses final Class<?> superclass = methodDeclaringClass.getSuperclass(); if (superclass != null) { try { final Method parentClassMethod = superclass.getMethod(methodName, method.getParameterTypes()); return getAnnotation(parentClassMethod, annotationClass); } catch (final SecurityException | NoSuchMethodException e) { // fall through } } // search implemented interfaces final Class<?>[] interfaces = methodDeclaringClass.getInterfaces(); for (final Class<?> iface : interfaces) { try { final Method ifaceMethod = iface.getMethod(methodName, method.getParameterTypes()); return getAnnotation(ifaceMethod, annotationClass); } catch (final SecurityException | NoSuchMethodException e) { // fall through } } return null; } /** * Searches for all no-arg methods or fields with a specified title, returning an * {@link Evaluator} object that wraps either. Will search up hierarchy also. */ public static <T extends Annotation> List<Evaluator<T>> getEvaluators( final Class<?> cls, final Class<T> annotationClass) { List<Evaluator<T>> evaluators = Lists.newArrayList(); appendEvaluators(cls, annotationClass, evaluators); return evaluators; } private static <T extends Annotation> void appendEvaluators( final Class<?> cls, final Class<T> annotationClass, final List<Evaluator<T>> evaluators) { for (Method method : cls.getDeclaredMethods()) { if(MethodScope.OBJECT.matchesScopeOf(method) && method.getParameterTypes().length == 0) { final Annotation annotation = method.getAnnotation(annotationClass); if(annotation != null) { evaluators.add(new MethodEvaluator(method, annotation)); } } } for (final Field field: cls.getDeclaredFields()) { final Annotation annotation = field.getAnnotation(annotationClass); if(annotation != null) { evaluators.add(new FieldEvaluator(field, annotation)); } } // search superclasses final Class<?> superclass = cls.getSuperclass(); if (superclass != null) { appendEvaluators(superclass, annotationClass, evaluators); } // search implemented interfaces final Class<?>[] interfaces = cls.getInterfaces(); for (final Class<?> iface : interfaces) { appendEvaluators(iface, annotationClass, evaluators); } } public static abstract class Evaluator<T extends Annotation> { private final T annotation; protected Evaluator(final T annotation) { this.annotation = annotation; } public T getAnnotation() { return annotation; } public abstract Object value(final Object obj) ; } public static class MethodEvaluator<T extends Annotation> extends Evaluator<T> { private final Method method; MethodEvaluator(final Method method, final T annotation) { super(annotation); this.method = method; } public Object value(final Object obj) { try { return method.invoke(obj); } catch (final InvocationTargetException e) { ThrowableExtensions.throwWithinIsisException(e, "Exception executing " + method); return null; } catch (final IllegalAccessException e) { throw new MetaModelException("illegal access of " + method, e); } } public Method getMethod() { return method; } } static class FieldEvaluator<T extends Annotation> extends Evaluator<T> { private final Field field; FieldEvaluator(final Field field, final T annotation) { super(annotation); this.field = field; } public Object value(final Object obj) { try { field.setAccessible(true); return field.get(obj); } catch (final IllegalAccessException e) { throw new MetaModelException("illegal access of " + field, e); } } public Field getField() { return field; } } private static List<Class<?>> fieldAnnotationClasses = Collections.unmodifiableList( Arrays.<Class<?>>asList( Property.class, PropertyLayout.class, Collection.class, CollectionLayout.class, MemberOrder.class, javax.annotation.Nullable.class, Title.class, javax.jdo.annotations.Column.class ) ); private static boolean shouldSearchForField(final Class<?> annotationClass) { return fieldAnnotationClasses.contains(annotationClass); } static List<String> fieldNameCandidatesFor(final String methodName) { if(methodName == null) { return Collections.emptyList(); } int beginIndex; if (methodName.startsWith("get")) { beginIndex = 3; } else if (methodName.startsWith("is")) { beginIndex = 2; } else { beginIndex = -1; } if(beginIndex == -1) { return Collections.emptyList(); } final String suffix = methodName.substring(beginIndex); if(suffix.length() == 0) { return Collections.emptyList(); } final char c = suffix.charAt(0); final char lower = Character.toLowerCase(c); final String candidate = "" + lower + suffix.substring(1); return Arrays.asList(candidate, "_" + candidate); } /** * Searches for annotation on provided method, and if not found for any * inherited methods up from the superclass. * * <p> * Added to allow bytecode-mangling libraries such as CGLIB to be supported. */ public static boolean isAnnotationPresent(final Method method, final Class<? extends Annotation> annotationClass) { if (method == null) { return false; } final boolean present = method.isAnnotationPresent(annotationClass); if (present) { return true; } final Class<?> methodDeclaringClass = method.getDeclaringClass(); // search superclasses final Class<?> superclass = methodDeclaringClass.getSuperclass(); if (superclass != null) { try { final Method parentClassMethod = superclass.getMethod(method.getName(), method.getParameterTypes()); return isAnnotationPresent(parentClassMethod, annotationClass); } catch (final SecurityException | NoSuchMethodException e) { // fall through } } // search implemented interfaces final Class<?>[] interfaces = methodDeclaringClass.getInterfaces(); for (final Class<?> iface : interfaces) { try { final Method ifaceMethod = iface.getMethod(method.getName(), method.getParameterTypes()); return isAnnotationPresent(ifaceMethod, annotationClass); } catch (final SecurityException | NoSuchMethodException e) { // fall through } } return false; } /** * Searches for parameter annotations on provided method, and if not found * for any inherited methods up from the superclass. * * <p> * Added to allow bytecode-mangling libraries such as CGLIB to be supported. */ public static Annotation[][] getParameterAnnotations(final Method method) { if (method == null) { return new Annotation[0][0]; } final Annotation[][] allParamAnnotations = method.getParameterAnnotations(); boolean foundAnnotationsForAnyParameter = false; for (final Annotation[] singleParamAnnotations : allParamAnnotations) { if (singleParamAnnotations.length > 0) { foundAnnotationsForAnyParameter = true; break; } } if (foundAnnotationsForAnyParameter) { return allParamAnnotations; } final Class<?> methodDeclaringClass = method.getDeclaringClass(); // search superclasses final Class<?> superclass = methodDeclaringClass.getSuperclass(); if (superclass != null) { try { final Method parentClassMethod = superclass.getMethod(method.getName(), method.getParameterTypes()); return getParameterAnnotations(parentClassMethod); } catch (final SecurityException | NoSuchMethodException e) { // fall through } } // search implemented interfaces final Class<?>[] interfaces = methodDeclaringClass.getInterfaces(); for (final Class<?> iface : interfaces) { try { final Method ifaceMethod = iface.getMethod(method.getName(), method.getParameterTypes()); return getParameterAnnotations(ifaceMethod); } catch (final SecurityException | NoSuchMethodException e) { // fall through } } return noParamAnnotationsFor(method); } private static Annotation[][] noParamAnnotationsFor(final Method method) { return new Annotation[method.getParameterTypes().length][0]; } }