| /* |
| * 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.commons.lang3; |
| |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.Arrays; |
| |
| import org.apache.commons.lang3.builder.ToStringBuilder; |
| import org.apache.commons.lang3.builder.ToStringStyle; |
| |
| /** |
| * <p>Helper methods for working with {@link Annotation} instances.</p> |
| * |
| * <p>This class contains various utility methods that make working with |
| * annotations simpler.</p> |
| * |
| * <p>{@link Annotation} instances are always proxy objects; unfortunately |
| * dynamic proxies cannot be depended upon to know how to implement certain |
| * methods in the same manner as would be done by "natural" {@link Annotation}s. |
| * The methods presented in this class can be used to avoid that possibility. It |
| * is of course also possible for dynamic proxies to actually delegate their |
| * e.g. {@link Annotation#equals(Object)}/{@link Annotation#hashCode()}/ |
| * {@link Annotation#toString()} implementations to {@link AnnotationUtils}.</p> |
| * |
| * <p>#ThreadSafe#</p> |
| * |
| * @since 3.0 |
| */ |
| public class AnnotationUtils { |
| |
| /** |
| * A style that prints annotations as recommended. |
| */ |
| private static final ToStringStyle TO_STRING_STYLE = new ToStringStyle() { |
| /** Serialization version */ |
| private static final long serialVersionUID = 1L; |
| |
| { |
| setDefaultFullDetail(true); |
| setArrayContentDetail(true); |
| setUseClassName(true); |
| setUseShortClassName(true); |
| setUseIdentityHashCode(false); |
| setContentStart("("); |
| setContentEnd(")"); |
| setFieldSeparator(", "); |
| setArrayStart("["); |
| setArrayEnd("]"); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| protected String getShortClassName(final Class<?> cls) { |
| for (final Class<?> iface : ClassUtils.getAllInterfaces(cls)) { |
| if (Annotation.class.isAssignableFrom(iface)) { |
| return "@" + iface.getName(); |
| } |
| } |
| return StringUtils.EMPTY; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| protected void appendDetail(final StringBuffer buffer, final String fieldName, Object value) { |
| if (value instanceof Annotation) { |
| value = AnnotationUtils.toString((Annotation) value); |
| } |
| super.appendDetail(buffer, fieldName, value); |
| } |
| |
| }; |
| |
| /** |
| * <p>{@code AnnotationUtils} instances should NOT be constructed in |
| * standard programming. Instead, the class should be used statically.</p> |
| * |
| * <p>This constructor is public to permit tools that require a JavaBean |
| * instance to operate.</p> |
| */ |
| public AnnotationUtils() { |
| } |
| |
| /** |
| * <p>Checks if two annotations are equal using the criteria for equality |
| * presented in the {@link Annotation#equals(Object)} API docs.</p> |
| * |
| * @param a1 the first Annotation to compare, {@code null} returns |
| * {@code false} unless both are {@code null} |
| * @param a2 the second Annotation to compare, {@code null} returns |
| * {@code false} unless both are {@code null} |
| * @return {@code true} if the two annotations are {@code equal} or both |
| * {@code null} |
| */ |
| public static boolean equals(final Annotation a1, final Annotation a2) { |
| if (a1 == a2) { |
| return true; |
| } |
| if (a1 == null || a2 == null) { |
| return false; |
| } |
| final Class<? extends Annotation> type1 = a1.annotationType(); |
| final Class<? extends Annotation> type2 = a2.annotationType(); |
| Validate.notNull(type1, "Annotation %s with null annotationType()", a1); |
| Validate.notNull(type2, "Annotation %s with null annotationType()", a2); |
| if (!type1.equals(type2)) { |
| return false; |
| } |
| try { |
| for (final Method m : type1.getDeclaredMethods()) { |
| if (m.getParameterTypes().length == 0 |
| && isValidAnnotationMemberType(m.getReturnType())) { |
| final Object v1 = m.invoke(a1); |
| final Object v2 = m.invoke(a2); |
| if (!memberEquals(m.getReturnType(), v1, v2)) { |
| return false; |
| } |
| } |
| } |
| } catch (final IllegalAccessException | InvocationTargetException ex) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * <p>Generate a hash code for the given annotation using the algorithm |
| * presented in the {@link Annotation#hashCode()} API docs.</p> |
| * |
| * @param a the Annotation for a hash code calculation is desired, not |
| * {@code null} |
| * @return the calculated hash code |
| * @throws RuntimeException if an {@code Exception} is encountered during |
| * annotation member access |
| * @throws IllegalStateException if an annotation method invocation returns |
| * {@code null} |
| */ |
| public static int hashCode(final Annotation a) { |
| int result = 0; |
| final Class<? extends Annotation> type = a.annotationType(); |
| for (final Method m : type.getDeclaredMethods()) { |
| try { |
| final Object value = m.invoke(a); |
| if (value == null) { |
| throw new IllegalStateException( |
| String.format("Annotation method %s returned null", m)); |
| } |
| result += hashMember(m.getName(), value); |
| } catch (final RuntimeException ex) { |
| throw ex; |
| } catch (final Exception ex) { |
| throw new RuntimeException(ex); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * <p>Generate a string representation of an Annotation, as suggested by |
| * {@link Annotation#toString()}.</p> |
| * |
| * @param a the annotation of which a string representation is desired |
| * @return the standard string representation of an annotation, not |
| * {@code null} |
| */ |
| public static String toString(final Annotation a) { |
| final ToStringBuilder builder = new ToStringBuilder(a, TO_STRING_STYLE); |
| for (final Method m : a.annotationType().getDeclaredMethods()) { |
| if (m.getParameterTypes().length > 0) { |
| continue; //wtf? |
| } |
| try { |
| builder.append(m.getName(), m.invoke(a)); |
| } catch (final RuntimeException ex) { |
| throw ex; |
| } catch (final Exception ex) { |
| throw new RuntimeException(ex); |
| } |
| } |
| return builder.build(); |
| } |
| |
| /** |
| * <p>Checks if the specified type is permitted as an annotation member.</p> |
| * |
| * <p>The Java language specification only permits certain types to be used |
| * in annotations. These include {@link String}, {@link Class}, primitive |
| * types, {@link Annotation}, {@link Enum}, and single-dimensional arrays of |
| * these types.</p> |
| * |
| * @param type the type to check, {@code null} |
| * @return {@code true} if the type is a valid type to use in an annotation |
| */ |
| public static boolean isValidAnnotationMemberType(Class<?> type) { |
| if (type == null) { |
| return false; |
| } |
| if (type.isArray()) { |
| type = type.getComponentType(); |
| } |
| return type.isPrimitive() || type.isEnum() || type.isAnnotation() |
| || String.class.equals(type) || Class.class.equals(type); |
| } |
| |
| //besides modularity, this has the advantage of autoboxing primitives: |
| /** |
| * Helper method for generating a hash code for a member of an annotation. |
| * |
| * @param name the name of the member |
| * @param value the value of the member |
| * @return a hash code for this member |
| */ |
| private static int hashMember(final String name, final Object value) { |
| final int part1 = name.hashCode() * 127; |
| if (value.getClass().isArray()) { |
| return part1 ^ arrayMemberHash(value.getClass().getComponentType(), value); |
| } |
| if (value instanceof Annotation) { |
| return part1 ^ hashCode((Annotation) value); |
| } |
| return part1 ^ value.hashCode(); |
| } |
| |
| /** |
| * Helper method for checking whether two objects of the given type are |
| * equal. This method is used to compare the parameters of two annotation |
| * instances. |
| * |
| * @param type the type of the objects to be compared |
| * @param o1 the first object |
| * @param o2 the second object |
| * @return a flag whether these objects are equal |
| */ |
| private static boolean memberEquals(final Class<?> type, final Object o1, final Object o2) { |
| if (o1 == o2) { |
| return true; |
| } |
| if (o1 == null || o2 == null) { |
| return false; |
| } |
| if (type.isArray()) { |
| return arrayMemberEquals(type.getComponentType(), o1, o2); |
| } |
| if (type.isAnnotation()) { |
| return equals((Annotation) o1, (Annotation) o2); |
| } |
| return o1.equals(o2); |
| } |
| |
| /** |
| * Helper method for comparing two objects of an array type. |
| * |
| * @param componentType the component type of the array |
| * @param o1 the first object |
| * @param o2 the second object |
| * @return a flag whether these objects are equal |
| */ |
| private static boolean arrayMemberEquals(final Class<?> componentType, final Object o1, final Object o2) { |
| if (componentType.isAnnotation()) { |
| return annotationArrayMemberEquals((Annotation[]) o1, (Annotation[]) o2); |
| } |
| if (componentType.equals(Byte.TYPE)) { |
| return Arrays.equals((byte[]) o1, (byte[]) o2); |
| } |
| if (componentType.equals(Short.TYPE)) { |
| return Arrays.equals((short[]) o1, (short[]) o2); |
| } |
| if (componentType.equals(Integer.TYPE)) { |
| return Arrays.equals((int[]) o1, (int[]) o2); |
| } |
| if (componentType.equals(Character.TYPE)) { |
| return Arrays.equals((char[]) o1, (char[]) o2); |
| } |
| if (componentType.equals(Long.TYPE)) { |
| return Arrays.equals((long[]) o1, (long[]) o2); |
| } |
| if (componentType.equals(Float.TYPE)) { |
| return Arrays.equals((float[]) o1, (float[]) o2); |
| } |
| if (componentType.equals(Double.TYPE)) { |
| return Arrays.equals((double[]) o1, (double[]) o2); |
| } |
| if (componentType.equals(Boolean.TYPE)) { |
| return Arrays.equals((boolean[]) o1, (boolean[]) o2); |
| } |
| return Arrays.equals((Object[]) o1, (Object[]) o2); |
| } |
| |
| /** |
| * Helper method for comparing two arrays of annotations. |
| * |
| * @param a1 the first array |
| * @param a2 the second array |
| * @return a flag whether these arrays are equal |
| */ |
| private static boolean annotationArrayMemberEquals(final Annotation[] a1, final Annotation[] a2) { |
| if (a1.length != a2.length) { |
| return false; |
| } |
| for (int i = 0; i < a1.length; i++) { |
| if (!equals(a1[i], a2[i])) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Helper method for generating a hash code for an array. |
| * |
| * @param componentType the component type of the array |
| * @param o the array |
| * @return a hash code for the specified array |
| */ |
| private static int arrayMemberHash(final Class<?> componentType, final Object o) { |
| if (componentType.equals(Byte.TYPE)) { |
| return Arrays.hashCode((byte[]) o); |
| } |
| if (componentType.equals(Short.TYPE)) { |
| return Arrays.hashCode((short[]) o); |
| } |
| if (componentType.equals(Integer.TYPE)) { |
| return Arrays.hashCode((int[]) o); |
| } |
| if (componentType.equals(Character.TYPE)) { |
| return Arrays.hashCode((char[]) o); |
| } |
| if (componentType.equals(Long.TYPE)) { |
| return Arrays.hashCode((long[]) o); |
| } |
| if (componentType.equals(Float.TYPE)) { |
| return Arrays.hashCode((float[]) o); |
| } |
| if (componentType.equals(Double.TYPE)) { |
| return Arrays.hashCode((double[]) o); |
| } |
| if (componentType.equals(Boolean.TYPE)) { |
| return Arrays.hashCode((boolean[]) o); |
| } |
| return Arrays.hashCode((Object[]) o); |
| } |
| } |