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.util.Arrays;
022
023import org.apache.commons.lang3.builder.ToStringBuilder;
024import org.apache.commons.lang3.builder.ToStringStyle;
025import org.apache.commons.lang3.exception.UncheckedException;
026
027/**
028 * Helper methods for working with {@link Annotation} instances.
029 *
030 * <p>This class contains various utility methods that make working with
031 * annotations simpler.</p>
032 *
033 * <p>{@link Annotation} instances are always proxy objects; unfortunately
034 * dynamic proxies cannot be depended upon to know how to implement certain
035 * methods in the same manner as would be done by "natural" {@link Annotation}s.
036 * The methods presented in this class can be used to avoid that possibility. It
037 * is of course also possible for dynamic proxies to actually delegate their
038 * e.g. {@link Annotation#equals(Object)}/{@link Annotation#hashCode()}/
039 * {@link Annotation#toString()} implementations to {@link AnnotationUtils}.</p>
040 *
041 * <p>#ThreadSafe#</p>
042 *
043 * @since 3.0
044 */
045public class AnnotationUtils {
046
047    /**
048     * A style that prints annotations as recommended.
049     */
050    private static final ToStringStyle TO_STRING_STYLE = new ToStringStyle() {
051
052        /** Serialization version */
053        private static final long serialVersionUID = 1L;
054
055        {
056            setDefaultFullDetail(true);
057            setArrayContentDetail(true);
058            setUseClassName(true);
059            setUseShortClassName(true);
060            setUseIdentityHashCode(false);
061            setContentStart("(");
062            setContentEnd(")");
063            setFieldSeparator(", ");
064            setArrayStart("[");
065            setArrayEnd("]");
066        }
067
068        /**
069         * {@inheritDoc}
070         */
071        @Override
072        protected void appendDetail(final StringBuffer buffer, final String fieldName, Object value) {
073            if (value instanceof Annotation) {
074                value = AnnotationUtils.toString((Annotation) value);
075            }
076            super.appendDetail(buffer, fieldName, value);
077        }
078
079        /**
080         * {@inheritDoc}
081         */
082        @Override
083        protected String getShortClassName(final Class<?> cls) {
084            // formatter:off
085            return ClassUtils.getAllInterfaces(cls).stream().filter(Annotation.class::isAssignableFrom).findFirst()
086                .map(iface -> "@" + iface.getName())
087                .orElse(StringUtils.EMPTY);
088            // formatter:on
089        }
090
091    };
092
093    /**
094     * Helper method for comparing two arrays of annotations.
095     *
096     * @param a1 the first array
097     * @param a2 the second array
098     * @return a flag whether these arrays are equal
099     */
100    private static boolean annotationArrayMemberEquals(final Annotation[] a1, final Annotation[] a2) {
101        if (a1.length != a2.length) {
102            return false;
103        }
104        for (int i = 0; i < a1.length; i++) {
105            if (!equals(a1[i], a2[i])) {
106                return false;
107            }
108        }
109        return true;
110    }
111
112    /**
113     * Helper method for comparing two objects of an array type.
114     *
115     * @param componentType the component type of the array
116     * @param o1 the first object
117     * @param o2 the second object
118     * @return a flag whether these objects are equal
119     */
120    private static boolean arrayMemberEquals(final Class<?> componentType, final Object o1, final Object o2) {
121        if (componentType.isAnnotation()) {
122            return annotationArrayMemberEquals((Annotation[]) o1, (Annotation[]) o2);
123        }
124        if (componentType.equals(Byte.TYPE)) {
125            return Arrays.equals((byte[]) o1, (byte[]) o2);
126        }
127        if (componentType.equals(Short.TYPE)) {
128            return Arrays.equals((short[]) o1, (short[]) o2);
129        }
130        if (componentType.equals(Integer.TYPE)) {
131            return Arrays.equals((int[]) o1, (int[]) o2);
132        }
133        if (componentType.equals(Character.TYPE)) {
134            return Arrays.equals((char[]) o1, (char[]) o2);
135        }
136        if (componentType.equals(Long.TYPE)) {
137            return Arrays.equals((long[]) o1, (long[]) o2);
138        }
139        if (componentType.equals(Float.TYPE)) {
140            return Arrays.equals((float[]) o1, (float[]) o2);
141        }
142        if (componentType.equals(Double.TYPE)) {
143            return Arrays.equals((double[]) o1, (double[]) o2);
144        }
145        if (componentType.equals(Boolean.TYPE)) {
146            return Arrays.equals((boolean[]) o1, (boolean[]) o2);
147        }
148        return Arrays.equals((Object[]) o1, (Object[]) o2);
149    }
150
151    /**
152     * Helper method for generating a hash code for an array.
153     *
154     * @param componentType the component type of the array
155     * @param o the array
156     * @return a hash code for the specified array
157     */
158    private static int arrayMemberHash(final Class<?> componentType, final Object o) {
159        if (componentType.equals(Byte.TYPE)) {
160            return Arrays.hashCode((byte[]) o);
161        }
162        if (componentType.equals(Short.TYPE)) {
163            return Arrays.hashCode((short[]) o);
164        }
165        if (componentType.equals(Integer.TYPE)) {
166            return Arrays.hashCode((int[]) o);
167        }
168        if (componentType.equals(Character.TYPE)) {
169            return Arrays.hashCode((char[]) o);
170        }
171        if (componentType.equals(Long.TYPE)) {
172            return Arrays.hashCode((long[]) o);
173        }
174        if (componentType.equals(Float.TYPE)) {
175            return Arrays.hashCode((float[]) o);
176        }
177        if (componentType.equals(Double.TYPE)) {
178            return Arrays.hashCode((double[]) o);
179        }
180        if (componentType.equals(Boolean.TYPE)) {
181            return Arrays.hashCode((boolean[]) o);
182        }
183        return Arrays.hashCode((Object[]) o);
184    }
185
186    /**
187     * Checks if two annotations are equal using the criteria for equality
188     * presented in the {@link Annotation#equals(Object)} API docs.
189     *
190     * @param a1 the first Annotation to compare, {@code null} returns
191     * {@code false} unless both are {@code null}
192     * @param a2 the second Annotation to compare, {@code null} returns
193     * {@code false} unless both are {@code null}
194     * @return {@code true} if the two annotations are {@code equal} or both
195     * {@code null}
196     */
197    public static boolean equals(final Annotation a1, final Annotation a2) {
198        if (a1 == a2) {
199            return true;
200        }
201        if (a1 == null || a2 == null) {
202            return false;
203        }
204        final Class<? extends Annotation> type1 = a1.annotationType();
205        final Class<? extends Annotation> type2 = a2.annotationType();
206        Validate.notNull(type1, "Annotation %s with null annotationType()", a1);
207        Validate.notNull(type2, "Annotation %s with null annotationType()", a2);
208        if (!type1.equals(type2)) {
209            return false;
210        }
211        try {
212            for (final Method m : type1.getDeclaredMethods()) {
213                if (m.getParameterTypes().length == 0
214                        && isValidAnnotationMemberType(m.getReturnType())) {
215                    final Object v1 = m.invoke(a1);
216                    final Object v2 = m.invoke(a2);
217                    if (!memberEquals(m.getReturnType(), v1, v2)) {
218                        return false;
219                    }
220                }
221            }
222        } catch (final ReflectiveOperationException ex) {
223            return false;
224        }
225        return true;
226    }
227
228    /**
229     * Generate a hash code for the given annotation using the algorithm
230     * presented in the {@link Annotation#hashCode()} API docs.
231     *
232     * @param a the Annotation for a hash code calculation is desired, not
233     * {@code null}
234     * @return the calculated hash code
235     * @throws RuntimeException if an {@link Exception} is encountered during
236     * annotation member access
237     * @throws IllegalStateException if an annotation method invocation returns
238     * {@code null}
239     */
240    public static int hashCode(final Annotation a) {
241        int result = 0;
242        final Class<? extends Annotation> type = a.annotationType();
243        for (final Method m : type.getDeclaredMethods()) {
244            try {
245                final Object value = m.invoke(a);
246                if (value == null) {
247                    throw new IllegalStateException(String.format("Annotation method %s returned null", m));
248                }
249                result += hashMember(m.getName(), value);
250            } catch (final ReflectiveOperationException ex) {
251                throw new UncheckedException(ex);
252            }
253        }
254        return result;
255    }
256
257    //besides modularity, this has the advantage of autoboxing primitives:
258    /**
259     * Helper method for generating a hash code for a member of an annotation.
260     *
261     * @param name the name of the member
262     * @param value the value of the member
263     * @return a hash code for this member
264     */
265    private static int hashMember(final String name, final Object value) {
266        final int part1 = name.hashCode() * 127;
267        if (ObjectUtils.isArray(value)) {
268            return part1 ^ arrayMemberHash(value.getClass().getComponentType(), value);
269        }
270        if (value instanceof Annotation) {
271            return part1 ^ hashCode((Annotation) value);
272        }
273        return part1 ^ value.hashCode();
274    }
275
276    /**
277     * Checks if the specified type is permitted as an annotation member.
278     *
279     * <p>The Java language specification only permits certain types to be used
280     * in annotations. These include {@link String}, {@link Class}, primitive
281     * types, {@link Annotation}, {@link Enum}, and single-dimensional arrays of
282     * these types.</p>
283     *
284     * @param type the type to check, {@code null}
285     * @return {@code true} if the type is a valid type to use in an annotation
286     */
287    public static boolean isValidAnnotationMemberType(Class<?> type) {
288        if (type == null) {
289            return false;
290        }
291        if (type.isArray()) {
292            type = type.getComponentType();
293        }
294        return type.isPrimitive() || type.isEnum() || type.isAnnotation()
295                || String.class.equals(type) || Class.class.equals(type);
296    }
297
298    /**
299     * Helper method for checking whether two objects of the given type are
300     * equal. This method is used to compare the parameters of two annotation
301     * instances.
302     *
303     * @param type the type of the objects to be compared
304     * @param o1 the first object
305     * @param o2 the second object
306     * @return a flag whether these objects are equal
307     */
308    private static boolean memberEquals(final Class<?> type, final Object o1, final Object o2) {
309        if (o1 == o2) {
310            return true;
311        }
312        if (o1 == null || o2 == null) {
313            return false;
314        }
315        if (type.isArray()) {
316            return arrayMemberEquals(type.getComponentType(), o1, o2);
317        }
318        if (type.isAnnotation()) {
319            return equals((Annotation) o1, (Annotation) o2);
320        }
321        return o1.equals(o2);
322    }
323
324    /**
325     * Generate a string representation of an Annotation, as suggested by
326     * {@link Annotation#toString()}.
327     *
328     * @param a the annotation of which a string representation is desired
329     * @return the standard string representation of an annotation, not
330     * {@code null}
331     */
332    public static String toString(final Annotation a) {
333        final ToStringBuilder builder = new ToStringBuilder(a, TO_STRING_STYLE);
334        for (final Method m : a.annotationType().getDeclaredMethods()) {
335            if (m.getParameterTypes().length > 0) {
336                continue; // what?
337            }
338            try {
339                builder.append(m.getName(), m.invoke(a));
340            } catch (final ReflectiveOperationException ex) {
341                throw new UncheckedException(ex);
342            }
343        }
344        return builder.build();
345    }
346
347    /**
348     * {@link AnnotationUtils} instances should NOT be constructed in
349     * standard programming. Instead, the class should be used statically.
350     *
351     * <p>This constructor is public to permit tools that require a JavaBean
352     * instance to operate.</p>
353     *
354     * @deprecated TODO Make private in 4.0.
355     */
356    @Deprecated
357    public AnnotationUtils() {
358        // empty
359    }
360}