1 /*
2  * Hibernate Validator, declare and validate application constraints
3  *
4  * License: Apache License, Version 2.0
5  * See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
6  */

7 package org.hibernate.validator.internal.util.annotation;
8
9 import static org.hibernate.validator.internal.util.CollectionHelper.newHashMap;
10
11 import java.io.Serializable;
12 import java.lang.annotation.Annotation;
13 import java.lang.invoke.MethodHandles;
14 import java.lang.reflect.Method;
15 import java.security.AccessController;
16 import java.security.PrivilegedAction;
17 import java.util.Arrays;
18 import java.util.HashMap;
19 import java.util.Map;
20 import java.util.Map.Entry;
21 import java.util.Set;
22 import java.util.SortedSet;
23 import java.util.TreeSet;
24
25 import org.hibernate.validator.internal.util.CollectionHelper;
26 import org.hibernate.validator.internal.util.StringHelper;
27 import org.hibernate.validator.internal.util.logging.Log;
28 import org.hibernate.validator.internal.util.logging.LoggerFactory;
29 import org.hibernate.validator.internal.util.privilegedactions.GetAnnotationAttributes;
30 import org.hibernate.validator.internal.util.privilegedactions.GetDeclaredMethods;
31
32 /**
33  * Encapsulates the data you need to create an annotation. In
34  * particular, it stores the type of an {@code Annotation} instance
35  * and the values of its elements.
36  * The "elements" we're talking about are the annotation attributes,
37  * not its targets (the term "element" is used ambiguously
38  * in Java's annotations documentation).
39  *
40  * @author Paolo Perrotta
41  * @author Davide Marchignoli
42  * @author Hardy Ferentschik
43  * @author Gunnar Morling
44  * @author Guillaume Smet
45  */

46 public class AnnotationDescriptor<A extends Annotation> implements Serializable {
47
48     private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
49
50     private final Class<A> type;
51
52     private final Map<String, Object> attributes;
53
54     private final int hashCode;
55
56     private final A annotation;
57
58     @SuppressWarnings("unchecked")
59     public AnnotationDescriptor(A annotation) {
60         this.type = (Class<A>) annotation.annotationType();
61         this.attributes = run( GetAnnotationAttributes.action( annotation ) );
62         this.annotation = annotation;
63         this.hashCode = buildHashCode();
64     }
65
66     public AnnotationDescriptor(AnnotationDescriptor<A> descriptor) {
67         this.type = descriptor.type;
68         this.attributes = descriptor.attributes;
69         this.hashCode = descriptor.hashCode;
70         this.annotation = descriptor.annotation;
71     }
72
73     private AnnotationDescriptor(Class<A> annotationType, Map<String, Object> attributes) {
74         this.type = annotationType;
75         this.attributes = CollectionHelper.toImmutableMap( attributes );
76         this.hashCode = buildHashCode();
77         this.annotation = AnnotationFactory.create( this );
78     }
79
80     public Class<A> getType() {
81         return type;
82     }
83
84     public Map<String, Object> getAttributes() {
85         return attributes;
86     }
87
88     @SuppressWarnings("unchecked")
89     public <T> T getMandatoryAttribute(String attributeName, Class<T> attributeType) {
90         Object attribute = attributes.get( attributeName );
91
92         if ( attribute == null ) {
93             throw LOG.getUnableToFindAnnotationAttributeException( type, attributeName, null );
94         }
95
96         if ( !attributeType.isAssignableFrom( attribute.getClass() ) ) {
97             throw LOG.getWrongAnnotationAttributeTypeException( type, attributeName, attributeType, attribute.getClass() );
98         }
99
100         return (T) attribute;
101     }
102
103     @SuppressWarnings("unchecked")
104     public <T> T getAttribute(String attributeName, Class<T> attributeType) {
105         Object attribute = attributes.get( attributeName );
106
107         if ( attribute == null ) {
108             return null;
109         }
110
111         if ( !attributeType.isAssignableFrom( attribute.getClass() ) ) {
112             throw LOG.getWrongAnnotationAttributeTypeException( type, attributeName, attributeType, attribute.getClass() );
113         }
114
115         return (T) attribute;
116     }
117
118     public Object getAttribute(String attributeName) {
119         return attributes.get( attributeName );
120     }
121
122     public A getAnnotation() {
123         return annotation;
124     }
125
126     @Override
127     public boolean equals(Object obj) {
128         if ( this == obj ) {
129             return true;
130         }
131         if ( obj == null || !( obj instanceof AnnotationDescriptor ) ) {
132             return false;
133         }
134
135         AnnotationDescriptor<?> other = (AnnotationDescriptor<?>) obj;
136
137         if ( !type.equals( other.type ) ) {
138             return false;
139         }
140
141         if ( attributes.size() != other.attributes.size() ) {
142             return false;
143         }
144
145         for ( Entry<String, Object> member : attributes.entrySet() ) {
146             Object value = member.getValue();
147             Object otherValue = other.attributes.get( member.getKey() );
148
149             if ( !areEqual( value, otherValue ) ) {
150                 return false;
151             }
152         }
153
154         return true;
155     }
156
157     /**
158      * Calculates the hash code of this annotation descriptor as described in
159      * {@link Annotation#hashCode()}.
160      *
161      * @return The hash code of this descriptor.
162      *
163      * @see Annotation#hashCode()
164      */

165     @Override
166     public int hashCode() {
167         return hashCode;
168     }
169
170     @Override
171     public String toString() {
172         StringBuilder result = new StringBuilder();
173         result.append( '@' ).append( StringHelper.toShortString( type ) ).append( '(' );
174         for ( String s : getRegisteredAttributesInAlphabeticalOrder() ) {
175             result.append( s ).append( '=' ).append( attributes.get( s ) ).append( ", " );
176         }
177         // remove last separator:
178         if ( attributes.size() > 0 ) {
179             result.delete( result.length() - 2, result.length() );
180             result.append( ")" );
181         }
182         else {
183             result.delete( result.length() - 1, result.length() );
184         }
185
186         return result.toString();
187     }
188
189     private SortedSet<String> getRegisteredAttributesInAlphabeticalOrder() {
190         return new TreeSet<String>( attributes.keySet() );
191     }
192
193     private int buildHashCode() {
194         int hashCode = 0;
195
196         for ( Entry<String, Object> member : attributes.entrySet() ) {
197             Object value = member.getValue();
198
199             int nameHashCode = member.getKey().hashCode();
200
201             int valueHashCode = !value.getClass().isArray() ? value.hashCode()
202                     : value.getClass() == boolean[].class ? Arrays.hashCode( (boolean[]) value )
203                     : value.getClass() == byte[].class ? Arrays.hashCode( (byte[]) value )
204                     : value.getClass() == char[].class ? Arrays.hashCode( (char[]) value )
205                     : value.getClass() == double[].class ? Arrays.hashCode( (double[]) value )
206                     : value.getClass() == float[].class ? Arrays.hashCode( (float[]) value )
207                     : value.getClass() == int[].class ? Arrays.hashCode( (int[]) value )
208                     : value.getClass() == long[].class ? Arrays.hashCode( (long[]) value )
209                     : value.getClass() == short[].class ? Arrays.hashCode( (short[]) value )
210                     : Arrays.hashCode( (Object[]) value );
211
212             hashCode += 127 * nameHashCode ^ valueHashCode;
213         }
214
215         return hashCode;
216     }
217
218     private boolean areEqual(Object o1, Object o2) {
219         return !o1.getClass().isArray() ? o1.equals( o2 )
220                 : o1.getClass() == boolean[].class ? Arrays.equals( (boolean[]) o1, (boolean[]) o2 )
221                 : o1.getClass() == byte[].class ? Arrays.equals( (byte[]) o1, (byte[]) o2 )
222                 : o1.getClass() == char[].class ? Arrays.equals( (char[]) o1, (char[]) o2 )
223                 : o1.getClass() == double[].class ? Arrays.equals( (double[]) o1, (double[]) o2 )
224                 : o1.getClass() == float[].class ? Arrays.equals( (float[]) o1, (float[]) o2 )
225                 : o1.getClass() == int[].class ? Arrays.equals( (int[]) o1, (int[]) o2 )
226                 : o1.getClass() == long[].class ? Arrays.equals( (long[]) o1, (long[]) o2 )
227                 : o1.getClass() == short[].class ? Arrays.equals( (short[]) o1, (short[]) o2 )
228                 : Arrays.equals( (Object[]) o1, (Object[]) o2 );
229     }
230
231     public static class Builder<S extends Annotation> {
232
233         private final Class<S> type;
234
235         private final Map<String, Object> attributes;
236
237         public Builder(Class<S> type) {
238             this.type = type;
239             this.attributes = new HashMap<String, Object>();
240         }
241
242         public Builder(Class<S> type, Map<String, Object> attributes) {
243             this.type = type;
244             this.attributes = new HashMap<String, Object>( attributes );
245         }
246
247         @SuppressWarnings("unchecked")
248         public Builder(S annotation) {
249             this.type = (Class<S>) annotation.annotationType();
250             this.attributes = new HashMap<String, Object>( run( GetAnnotationAttributes.action( annotation ) ) );
251         }
252
253         public void setAttribute(String attributeName, Object value) {
254             attributes.put( attributeName, value );
255         }
256
257         public boolean hasAttribute(String key) {
258             return attributes.containsKey( key );
259         }
260
261         public Class<S> getType() {
262             return type;
263         }
264
265         public AnnotationDescriptor<S> build() {
266             return new AnnotationDescriptor<>( type, getAnnotationAttributes() );
267         }
268
269         private Map<String, Object> getAnnotationAttributes() {
270             Map<String, Object> result = newHashMap( attributes.size() );
271             int processedValuesFromDescriptor = 0;
272             final Method[] declaredMethods = run( GetDeclaredMethods.action( type ) );
273             for ( Method m : declaredMethods ) {
274                 Object elementValue = attributes.get( m.getName() );
275                 if ( elementValue != null ) {
276                     result.put( m.getName(), elementValue );
277                     processedValuesFromDescriptor++;
278                 }
279                 else if ( m.getDefaultValue() != null ) {
280                     result.put( m.getName(), m.getDefaultValue() );
281                 }
282                 else {
283                     throw LOG.getNoValueProvidedForAnnotationAttributeException(
284                             m.getName(),
285                             type
286                     );
287                 }
288             }
289             if ( processedValuesFromDescriptor != attributes.size() ) {
290                 Set<String> unknownAttributes = attributes.keySet();
291                 unknownAttributes.removeAll( result.keySet() );
292
293                 throw LOG.getTryingToInstantiateAnnotationWithUnknownAttributesException(
294                         type,
295                         unknownAttributes
296                 );
297             }
298             return result;
299         }
300
301         @Override
302         public String toString() {
303             StringBuilder result = new StringBuilder();
304             result.append( '@' ).append( StringHelper.toShortString( type ) ).append( '(' );
305             for ( String s : getRegisteredAttributesInAlphabeticalOrder() ) {
306                 result.append( s ).append( '=' ).append( attributes.get( s ) ).append( ", " );
307             }
308             // remove last separator:
309             if ( attributes.size() > 0 ) {
310                 result.delete( result.length() - 2, result.length() );
311                 result.append( ")" );
312             }
313             else {
314                 result.delete( result.length() - 1, result.length() );
315             }
316
317             return result.toString();
318         }
319
320         private SortedSet<String> getRegisteredAttributesInAlphabeticalOrder() {
321             return new TreeSet<String>( attributes.keySet() );
322         }
323     }
324
325     /**
326      * Runs the given privileged action, using a privileged block if required.
327      * <b>NOTE:</b> This must never be changed into a publicly available method to avoid execution of arbitrary
328      * privileged actions within HV's protection domain.
329      */

330     private static <V> V run(PrivilegedAction<V> action) {
331         return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run();
332     }
333 }
334