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