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