001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.lang3;
018
019import java.lang.annotation.Annotation;
020import java.lang.reflect.Method;
021import java.lang.reflect.Modifier;
022import java.util.Arrays;
023
024import org.apache.commons.lang3.builder.AbstractReflection;
025import org.apache.commons.lang3.builder.ToStringBuilder;
026import org.apache.commons.lang3.builder.ToStringStyle;
027import org.apache.commons.lang3.exception.UncheckedException;
028
029/**
030 * Helper methods for working with {@link Annotation} instances.
031 *
032 * <p>
033 * This class contains various utility methods that make working with
034 * annotations simpler.
035 * </p>
036 *
037 * <p>
038 * {@link Annotation} instances are always proxy objects; unfortunately
039 * dynamic proxies cannot be depended upon to know how to implement certain
040 * methods in the same manner as would be done by "natural" {@link Annotation}s.
041 * The methods presented in this class can be used to avoid that possibility. It
042 * is of course also possible for dynamic proxies to actually delegate their
043 * e.g. {@link Annotation#equals(Object)}/{@link Annotation#hashCode()}/
044 * {@link Annotation#toString()} implementations to {@link AnnotationUtils}.
045 * </p>
046 *
047 * <p>
048 * #ThreadSafe#
049 * </p>
050 *
051 * @since 3.0
052 */
053public class AnnotationUtils {
054
055    /**
056     * A style that prints annotations as recommended.
057     */
058    private static final ToStringStyle TO_STRING_STYLE = new ToStringStyle() {
059
060        /** Serialization version */
061        private static final long serialVersionUID = 1L;
062
063        {
064            setDefaultFullDetail(true);
065            setArrayContentDetail(true);
066            setUseClassName(true);
067            setUseShortClassName(true);
068            setUseIdentityHashCode(false);
069            setContentStart("(");
070            setContentEnd(")");
071            setFieldSeparator(", ");
072            setArrayStart("[");
073            setArrayEnd("]");
074        }
075
076        /**
077         * {@inheritDoc}
078         */
079        @Override
080        protected void appendDetail(final StringBuffer buffer, final String fieldName, Object value) {
081            if (value instanceof Annotation) {
082                value = AnnotationUtils.toString((Annotation) value);
083            }
084            super.appendDetail(buffer, fieldName, value);
085        }
086
087        /**
088         * {@inheritDoc}
089         */
090        @Override
091        protected String getShortClassName(final Class<?> cls) {
092            // formatter:off
093            return ClassUtils.getAllInterfaces(cls).stream().filter(Annotation.class::isAssignableFrom).findFirst()
094                .map(iface -> "@" + iface.getName())
095                .orElse(StringUtils.EMPTY);
096            // formatter:on
097        }
098
099    };
100
101    /**
102     * Helper method for comparing two arrays of annotations.
103     *
104     * @param a1 The first array
105     * @param a2 The second array
106     * @return A flag whether these arrays are equal
107     */
108    private static boolean annotationArrayMemberEquals(final Annotation[] a1, final Annotation[] a2) {
109        if (a1.length != a2.length) {
110            return false;
111        }
112        for (int i = 0; i < a1.length; i++) {
113            if (!equals(a1[i], a2[i])) {
114                return false;
115            }
116        }
117        return true;
118    }
119
120    /**
121     * Generates an array hash code using {@link #hashCode(Annotation)} for each non-null element.
122     *
123     * <p>Uses the element order, initial value, multiplier, and null-element handling of {@link Arrays#hashCode(Object[])}.</p>
124     *
125     * @param annotations The array
126     * @return A hash code for the specified array
127     */
128    private static int annotationArrayMemberHash(final Annotation[] annotations) {
129        int result = 1;
130        for (final Annotation annotation : annotations) {
131            result = 31 * result + (annotation == null ? 0 : hashCode(annotation));
132        }
133        return result;
134    }
135
136    /**
137     * Helper method for comparing two objects of an array type.
138     *
139     * @param componentType The component type of the array
140     * @param o1 The first object
141     * @param o2 The second object
142     * @return A flag whether these objects are equal
143     */
144    private static boolean arrayMemberEquals(final Class<?> componentType, final Object o1, final Object o2) {
145        if (componentType.isAnnotation()) {
146            return annotationArrayMemberEquals((Annotation[]) o1, (Annotation[]) o2);
147        }
148        if (componentType.equals(Byte.TYPE)) {
149            return Arrays.equals((byte[]) o1, (byte[]) o2);
150        }
151        if (componentType.equals(Short.TYPE)) {
152            return Arrays.equals((short[]) o1, (short[]) o2);
153        }
154        if (componentType.equals(Integer.TYPE)) {
155            return Arrays.equals((int[]) o1, (int[]) o2);
156        }
157        if (componentType.equals(Character.TYPE)) {
158            return Arrays.equals((char[]) o1, (char[]) o2);
159        }
160        if (componentType.equals(Long.TYPE)) {
161            return Arrays.equals((long[]) o1, (long[]) o2);
162        }
163        if (componentType.equals(Float.TYPE)) {
164            return Arrays.equals((float[]) o1, (float[]) o2);
165        }
166        if (componentType.equals(Double.TYPE)) {
167            return Arrays.equals((double[]) o1, (double[]) o2);
168        }
169        if (componentType.equals(Boolean.TYPE)) {
170            return Arrays.equals((boolean[]) o1, (boolean[]) o2);
171        }
172        return Arrays.equals((Object[]) o1, (Object[]) o2);
173    }
174
175    /**
176     * Helper method for generating a hash code for an array.
177     *
178     * @param componentType The component type of the array
179     * @param o The array
180     * @return A hash code for the specified array
181     */
182    private static int arrayMemberHash(final Class<?> componentType, final Object o) {
183        if (componentType.isAnnotation()) {
184            return annotationArrayMemberHash((Annotation[]) o);
185        }
186        if (componentType.equals(Byte.TYPE)) {
187            return Arrays.hashCode((byte[]) o);
188        }
189        if (componentType.equals(Short.TYPE)) {
190            return Arrays.hashCode((short[]) o);
191        }
192        if (componentType.equals(Integer.TYPE)) {
193            return Arrays.hashCode((int[]) o);
194        }
195        if (componentType.equals(Character.TYPE)) {
196            return Arrays.hashCode((char[]) o);
197        }
198        if (componentType.equals(Long.TYPE)) {
199            return Arrays.hashCode((long[]) o);
200        }
201        if (componentType.equals(Float.TYPE)) {
202            return Arrays.hashCode((float[]) o);
203        }
204        if (componentType.equals(Double.TYPE)) {
205            return Arrays.hashCode((double[]) o);
206        }
207        if (componentType.equals(Boolean.TYPE)) {
208            return Arrays.hashCode((boolean[]) o);
209        }
210        return Arrays.hashCode((Object[]) o);
211    }
212
213    /**
214     * Checks if two annotations are equal using the criteria for equality
215     * presented in the {@link Annotation#equals(Object)} API docs.
216     *
217     * @param a1 The first Annotation to compare, {@code null} returns
218     * {@code false} unless both are {@code null}
219     * @param a2 The second Annotation to compare, {@code null} returns
220     * {@code false} unless both are {@code null}
221     * @return {@code true} if the two annotations are {@code equal} or both
222     * {@code null}
223     */
224    public static boolean equals(final Annotation a1, final Annotation a2) {
225        if (a1 == a2) {
226            return true;
227        }
228        if (a1 == null || a2 == null) {
229            return false;
230        }
231        final Class<? extends Annotation> type1 = a1.annotationType();
232        final Class<? extends Annotation> type2 = a2.annotationType();
233        Validate.notNull(type1, "Annotation %s with null annotationType()", a1);
234        Validate.notNull(type2, "Annotation %s with null annotationType()", a2);
235        if (!type1.equals(type2)) {
236            return false;
237        }
238        try {
239            for (final Method m : type1.getDeclaredMethods()) {
240                if (isAnnotationMember(m)) {
241                    final Object v1 = invokeAnnotationMember(m, a1);
242                    final Object v2 = invokeAnnotationMember(m, a2);
243                    if (!memberEquals(m.getReturnType(), v1, v2)) {
244                        return false;
245                    }
246                }
247            }
248        } catch (final ReflectiveOperationException ex) {
249            throw new IllegalStateException(ex);
250        }
251        return true;
252    }
253
254    /**
255     * Generate a hash code for the given annotation using the algorithm
256     * presented in the {@link Annotation#hashCode()} API docs.
257     *
258     * @param a The Annotation for a hash code calculation is desired, not
259     * {@code null}
260     * @return The calculated hash code
261     * @throws RuntimeException Thrown if an {@link Exception} is encountered during annotation member access.
262     * @throws IllegalStateException Thrown if an annotation method invocation returns {@code null}.
263     */
264    public static int hashCode(final Annotation a) {
265        int result = 0;
266        final Class<? extends Annotation> type = a.annotationType();
267        for (final Method m : type.getDeclaredMethods()) {
268            if (!isAnnotationMember(m)) {
269                continue;
270            }
271            try {
272                final Object value = invokeAnnotationMember(m, a);
273                if (value == null) {
274                    throw new IllegalStateException(String.format("Annotation method %s returned null", m));
275                }
276                result += hashMember(m.getName(), value);
277            } catch (final ReflectiveOperationException ex) {
278                throw new UncheckedException(ex);
279            }
280        }
281        return result;
282    }
283
284    //besides modularity, this has the advantage of autoboxing primitives:
285    /**
286     * Helper method for generating a hash code for a member of an annotation.
287     *
288     * @param name The name of the member
289     * @param value The value of the member
290     * @return A hash code for this member
291     */
292    private static int hashMember(final String name, final Object value) {
293        final int part1 = name.hashCode() * 127;
294        if (ObjectUtils.isArray(value)) {
295            return part1 ^ arrayMemberHash(value.getClass().getComponentType(), value);
296        }
297        if (value instanceof Annotation) {
298            return part1 ^ hashCode((Annotation) value);
299        }
300        return part1 ^ value.hashCode();
301    }
302
303    /**
304     * Invokes an annotation member, enabling reflective access only when needed.
305     *
306     * @param method the annotation member method
307     * @param annotation the annotation instance
308     * @return the annotation member value
309     * @throws ReflectiveOperationException if the method cannot be invoked
310     */
311    private static Object invokeAnnotationMember(final Method method, final Annotation annotation)
312            throws ReflectiveOperationException {
313        try {
314            return method.invoke(annotation);
315        } catch (final IllegalAccessException ex) {
316            if (!AbstractReflection.setAccessible(AbstractReflection.getForceAccessible(), method)) {
317                throw ex;
318            }
319            return method.invoke(annotation);
320        }
321    }
322
323    /**
324     * Tests whether the specified method declares an annotation member.
325     *
326     * @param method The method to check
327     * @return whether the method is public, abstract, non-synthetic, has no parameters,
328     *         and has a valid annotation member return type
329     */
330    private static boolean isAnnotationMember(final Method method) {
331        final int modifiers = method.getModifiers();
332        return Modifier.isPublic(modifiers) && Modifier.isAbstract(modifiers) && !method.isSynthetic()
333                && method.getParameterCount() == 0 && isValidAnnotationMemberType(method.getReturnType());
334    }
335
336    /**
337     * Tests whether the specified type is permitted as an annotation member.
338     *
339     * <p>
340     * The Java language specification only permits certain types to be used
341     * in annotations. These include {@link String}, {@link Class}, primitive
342     * types, {@link Annotation}, {@link Enum}, and single-dimensional arrays of
343     * these types.
344     * </p>
345     *
346     * @param type The type to check, {@code null}
347     * @return {@code true} if the type is a valid type to use in an annotation
348     */
349    public static boolean isValidAnnotationMemberType(Class<?> type) {
350        if (type == null) {
351            return false;
352        }
353        if (type.isArray()) {
354            type = type.getComponentType();
355        }
356        return type.isPrimitive() || type.isEnum() || type.isAnnotation()
357                || String.class.equals(type) || Class.class.equals(type);
358    }
359
360    /**
361     * Helper method for checking whether two objects of the given type are
362     * equal. This method is used to compare the parameters of two annotation
363     * instances.
364     *
365     * @param type The type of the objects to be compared
366     * @param o1 The first object
367     * @param o2 The second object
368     * @return A flag whether these objects are equal
369     */
370    private static boolean memberEquals(final Class<?> type, final Object o1, final Object o2) {
371        if (o1 == o2) {
372            return true;
373        }
374        if (o1 == null || o2 == null) {
375            return false;
376        }
377        if (type.isArray()) {
378            return arrayMemberEquals(type.getComponentType(), o1, o2);
379        }
380        if (type.isAnnotation()) {
381            return equals((Annotation) o1, (Annotation) o2);
382        }
383        return o1.equals(o2);
384    }
385
386    /**
387     * Generate a string representation of an Annotation, as suggested by
388     * {@link Annotation#toString()}.
389     *
390     * @param a The annotation of which a string representation is desired
391     * @return The standard string representation of an annotation, not
392     * {@code null}
393     */
394    public static String toString(final Annotation a) {
395        final ToStringBuilder builder = new ToStringBuilder(a, TO_STRING_STYLE);
396        for (final Method m : a.annotationType().getDeclaredMethods()) {
397            if (!isAnnotationMember(m)) {
398                continue;
399            }
400            try {
401                builder.append(m.getName(), invokeAnnotationMember(m, a));
402            } catch (final ReflectiveOperationException ex) {
403                throw new UncheckedException(ex);
404            }
405        }
406        return builder.build();
407    }
408
409    /**
410     * {@link AnnotationUtils} instances should NOT be constructed in
411     * standard programming. Instead, the class should be used statically.
412     *
413     * <p>
414     * This constructor is public to permit tools that require a JavaBean
415     * instance to operate.
416     * </p>
417     *
418     * @deprecated TODO Make private in 4.0.
419     */
420    @Deprecated
421    public AnnotationUtils() {
422        // empty
423    }
424}