/* * Hibernate Validator, declare and validate application constraints * * License: Apache License, Version 2.0 * See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>. */ package org.hibernate.validator.internal.util.annotationfactory; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import org.hibernate.validator.internal.util.logging.Log; import org.hibernate.validator.internal.util.logging.LoggerFactory; import org.hibernate.validator.internal.util.privilegedactions.GetDeclaredMethod; import org.hibernate.validator.internal.util.privilegedactions.GetDeclaredMethods; import static org.hibernate.validator.internal.util.CollectionHelper.newHashMap; /** * A concrete implementation of <code>Annotation</code> that pretends it is a * "real" source code annotation. It's also an <code>InvocationHandler</code>. * <p> * When you create an <code>AnnotationProxy</code>, you must initialize it * with an <code>AnnotationDescriptor</code>. * The adapter checks that the provided elements are the same elements defined * in the annotation interface. However, it does <i>not</i> check that their * values are the right type. If you omit an element, the adapter will use the * default value for that element from the annotation interface, if it exists. * If no default exists, it will throw an exception. * </p> * * @author Paolo Perrotta * @author Davide Marchignoli * @author Gunnar Morling * @see java.lang.annotation.Annotation */ class AnnotationProxy implements Annotation, InvocationHandler, Serializable { private static final long serialVersionUID = 6907601010599429454L; private static final Log log = LoggerFactory.make(); private final Class<? extends Annotation> annotationType; private final Map<String, Object> values; private final int hashCode; AnnotationProxy(AnnotationDescriptor<?> descriptor) { this.annotationType = descriptor.type(); values = Collections.unmodifiableMap( getAnnotationValues( descriptor ) ); this.hashCode = calculateHashCode(); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ( values.containsKey( method.getName() ) ) { return values.get( method.getName() ); } return method.invoke( this, args ); } @Override public Class<? extends Annotation> annotationType() { return annotationType; } /** * Performs an equality check as described in {@link Annotation#equals(Object)}. * * @param obj The object to compare * * @return Whether the given object is equal to this annotation proxy or not * * @see Annotation#equals(Object) */ @Override public boolean equals(Object obj) { if ( this == obj ) { return true; } if ( obj == null ) { return false; } if ( !annotationType.isInstance( obj ) ) { return false; } Annotation other = annotationType.cast( obj ); //compare annotation member values for ( Entry<String, Object> member : values.entrySet() ) { Object value = member.getValue(); Object otherValue = getAnnotationMemberValue( other, member.getKey() ); if ( !areEqual( value, otherValue ) ) { return false; } } return true; } /** * Calculates the hash code of this annotation proxy as described in * {@link Annotation#hashCode()}. * * @return The hash code of this proxy. * * @see Annotation#hashCode() */ @Override public int hashCode() { return hashCode; } @Override public String toString() { StringBuilder result = new StringBuilder(); result.append( '@' ).append( annotationType.getName() ).append( '(' ); for ( String s : getRegisteredMethodsInAlphabeticalOrder() ) { result.append( s ).append( '=' ).append( values.get( s ) ).append( ", " ); } // remove last separator: if ( values.size() > 0 ) { result.delete( result.length() - 2, result.length() ); result.append( ")" ); } else { result.delete( result.length() - 1, result.length() ); } return result.toString(); } private Map<String, Object> getAnnotationValues(AnnotationDescriptor<?> descriptor) { Map<String, Object> result = newHashMap(); int processedValuesFromDescriptor = 0; final Method[] declaredMethods = run( GetDeclaredMethods.action( annotationType ) ); for ( Method m : declaredMethods ) { if ( descriptor.containsElement( m.getName() ) ) { result.put( m.getName(), descriptor.valueOf( m.getName() ) ); processedValuesFromDescriptor++; } else if ( m.getDefaultValue() != null ) { result.put( m.getName(), m.getDefaultValue() ); } else { throw log.getNoValueProvidedForAnnotationParameterException( m.getName(), annotationType ); } } if ( processedValuesFromDescriptor != descriptor.numberOfElements() ) { Set<String> unknownParameters = descriptor.getElements().keySet(); unknownParameters.removeAll( result.keySet() ); throw log.getTryingToInstantiateAnnotationWithUnknownParametersException( annotationType, unknownParameters ); } return result; } private int calculateHashCode() { int hashCode = 0; for ( Entry<String, Object> member : values.entrySet() ) { Object value = member.getValue(); int nameHashCode = member.getKey().hashCode(); int valueHashCode = !value.getClass().isArray() ? value.hashCode() : value.getClass() == boolean[].class ? Arrays.hashCode( (boolean[]) value ) : value.getClass() == byte[].class ? Arrays.hashCode( (byte[]) value ) : value.getClass() == char[].class ? Arrays.hashCode( (char[]) value ) : value.getClass() == double[].class ? Arrays.hashCode( (double[]) value ) : value.getClass() == float[].class ? Arrays.hashCode( (float[]) value ) : value.getClass() == int[].class ? Arrays.hashCode( (int[]) value ) : value.getClass() == long[].class ? Arrays.hashCode( (long[]) value ) : value.getClass() == short[].class ? Arrays .hashCode( (short[]) value ) : Arrays.hashCode( (Object[]) value ); hashCode += 127 * nameHashCode ^ valueHashCode; } return hashCode; } private SortedSet<String> getRegisteredMethodsInAlphabeticalOrder() { SortedSet<String> result = new TreeSet<String>(); result.addAll( values.keySet() ); return result; } private boolean areEqual(Object o1, Object o2) { return !o1.getClass().isArray() ? o1.equals( o2 ) : o1.getClass() == boolean[].class ? Arrays.equals( (boolean[]) o1, (boolean[]) o2 ) : o1.getClass() == byte[].class ? Arrays.equals( (byte[]) o1, (byte[]) o2 ) : o1.getClass() == char[].class ? Arrays.equals( (char[]) o1, (char[]) o2 ) : o1.getClass() == double[].class ? Arrays.equals( (double[]) o1, (double[]) o2 ) : o1.getClass() == float[].class ? Arrays.equals( (float[]) o1, (float[]) o2 ) : o1.getClass() == int[].class ? Arrays.equals( (int[]) o1, (int[]) o2 ) : o1.getClass() == long[].class ? Arrays.equals( (long[]) o1, (long[]) o2 ) : o1.getClass() == short[].class ? Arrays.equals( (short[]) o1, (short[]) o2 ) : Arrays.equals( (Object[]) o1, (Object[]) o2 ); } private Object getAnnotationMemberValue(Annotation annotation, String name) { try { return run( GetDeclaredMethod.action( annotation.annotationType(), name ) ).invoke( annotation ); } catch (IllegalAccessException e) { throw log.getUnableToRetrieveAnnotationParameterValueException( e ); } catch (IllegalArgumentException e) { throw log.getUnableToRetrieveAnnotationParameterValueException( e ); } catch (InvocationTargetException e) { throw log.getUnableToRetrieveAnnotationParameterValueException( e ); } } /** * Runs the given privileged action, using a privileged block if required. * <p> * <b>NOTE:</b> This must never be changed into a publicly available method to avoid execution of arbitrary * privileged actions within HV's protection domain. */ private <T> T run(PrivilegedAction<T> action) { return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); } }