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.engine.valueextraction;
8
9 import java.lang.invoke.MethodHandles;
10 import java.lang.reflect.AnnotatedArrayType;
11 import java.lang.reflect.AnnotatedParameterizedType;
12 import java.lang.reflect.AnnotatedType;
13 import java.lang.reflect.Type;
14 import java.lang.reflect.TypeVariable;
15 import java.util.ArrayList;
16 import java.util.List;
17 import java.util.Optional;
18
19 import javax.validation.valueextraction.ExtractedValue;
20 import javax.validation.valueextraction.UnwrapByDefault;
21 import javax.validation.valueextraction.ValueExtractor;
22
23 import org.hibernate.validator.internal.util.ReflectionHelper;
24 import org.hibernate.validator.internal.util.StringHelper;
25 import org.hibernate.validator.internal.util.TypeHelper;
26 import org.hibernate.validator.internal.util.logging.Log;
27 import org.hibernate.validator.internal.util.logging.LoggerFactory;
28
29 /**
30  * Describes a {@link ValueExtractor}.
31  *
32  * @author Gunnar Morling
33  * @author Guillaume Smet
34  */

35 public class ValueExtractorDescriptor {
36
37     private static final Log LOG = LoggerFactory.make( MethodHandles.lookup()  );
38
39     private final Key key;
40     private final ValueExtractor<?> valueExtractor;
41     private final boolean unwrapByDefault;
42     private final Optional<Class<?>> extractedType;
43
44     public ValueExtractorDescriptor(ValueExtractor<?> valueExtractor) {
45         AnnotatedParameterizedType valueExtractorDefinition = getValueExtractorDefinition( valueExtractor.getClass() );
46
47         this.key = new Key(
48                 getContainerType( valueExtractorDefinition, valueExtractor.getClass() ),
49                 getExtractedTypeParameter( valueExtractorDefinition, valueExtractor.getClass() )
50         );
51         this.valueExtractor = valueExtractor;
52         this.unwrapByDefault = hasUnwrapByDefaultAnnotation( valueExtractor.getClass() );
53         this.extractedType = getExtractedType( valueExtractorDefinition );
54     }
55
56     @SuppressWarnings("rawtypes")
57     private static TypeVariable<?> getExtractedTypeParameter(AnnotatedParameterizedType valueExtractorDefinition,
58             Class<? extends ValueExtractor> extractorImplementationType) {
59         AnnotatedType containerType = valueExtractorDefinition.getAnnotatedActualTypeArguments()[0];
60         Class<?> containerTypeRaw = (Class<?>) TypeHelper.getErasedType( containerType.getType() );
61
62         TypeVariable<?> extractedTypeParameter = null;
63
64         if ( containerType.isAnnotationPresent( ExtractedValue.class ) ) {
65             if ( containerType instanceof AnnotatedArrayType ) {
66                 extractedTypeParameter = new ArrayElement( (AnnotatedArrayType) containerType );
67             }
68             else {
69                 extractedTypeParameter = AnnotatedObject.INSTANCE;
70             }
71         }
72
73         if ( containerType instanceof AnnotatedParameterizedType ) {
74             AnnotatedParameterizedType parameterizedExtractedType = (AnnotatedParameterizedType) containerType;
75             int i = 0;
76             for ( AnnotatedType typeArgument : parameterizedExtractedType.getAnnotatedActualTypeArguments() ) {
77                 if ( !TypeHelper.isUnboundWildcard( typeArgument.getType() ) ) {
78                     throw LOG.getOnlyUnboundWildcardTypeArgumentsSupportedForContainerTypeOfValueExtractorException( extractorImplementationType );
79                 }
80                 if ( typeArgument.isAnnotationPresent( ExtractedValue.class ) ) {
81                     if ( extractedTypeParameter != null ) {
82                         throw LOG.getValueExtractorDeclaresExtractedValueMultipleTimesException( extractorImplementationType );
83                     }
84                     if ( !void.class.equals( typeArgument.getAnnotation( ExtractedValue.class ).type() ) ) {
85                         throw LOG.getExtractedValueOnTypeParameterOfContainerTypeMayNotDefineTypeAttributeException( extractorImplementationType );
86                     }
87
88                     extractedTypeParameter = containerTypeRaw.getTypeParameters()[i];
89                 }
90                 i++;
91             }
92         }
93
94         if ( extractedTypeParameter == null ) {
95             throw LOG.getValueExtractorFailsToDeclareExtractedValueException( extractorImplementationType );
96         }
97
98         return extractedTypeParameter;
99     }
100
101     private static Optional<Class<?>> getExtractedType(AnnotatedParameterizedType valueExtractorDefinition) {
102         AnnotatedType containerType = valueExtractorDefinition.getAnnotatedActualTypeArguments()[0];
103
104         if ( containerType.isAnnotationPresent( ExtractedValue.class ) ) {
105             Class<?> extractedType = containerType.getAnnotation( ExtractedValue.class ).type();
106             if ( !void.class.equals( extractedType ) ) {
107                 return Optional.of( ReflectionHelper.boxedType( extractedType ) );
108             }
109         }
110
111         return Optional.empty();
112     }
113
114     @SuppressWarnings("rawtypes")
115     private static Class<?> getContainerType(AnnotatedParameterizedType valueExtractorDefinition, Class<? extends ValueExtractor> extractorImplementationType) {
116         AnnotatedType containerType = valueExtractorDefinition.getAnnotatedActualTypeArguments()[0];
117         return TypeHelper.getErasedReferenceType( containerType.getType() );
118     }
119
120     private static AnnotatedParameterizedType getValueExtractorDefinition(Class<?> extractorImplementationType) {
121         List<AnnotatedType> valueExtractorAnnotatedTypes = new ArrayList<>();
122
123         determineValueExtractorDefinitions( valueExtractorAnnotatedTypes, extractorImplementationType );
124
125         if ( valueExtractorAnnotatedTypes.size() == 1 ) {
126             return (AnnotatedParameterizedType) valueExtractorAnnotatedTypes.get( 0 );
127         }
128         else if ( valueExtractorAnnotatedTypes.size() > 1 ) {
129             throw LOG.getParallelDefinitionsOfValueExtractorsException( extractorImplementationType );
130         }
131         else {
132             throw new AssertionError( extractorImplementationType.getName() + " should be a subclass of " + ValueExtractor.class.getSimpleName() );
133         }
134     }
135
136     private static void determineValueExtractorDefinitions(List<AnnotatedType> valueExtractorDefinitions, Class<?> extractorImplementationType) {
137         if ( !ValueExtractor.class.isAssignableFrom( extractorImplementationType ) ) {
138             return;
139         }
140
141         Class<?> superClass = extractorImplementationType.getSuperclass();
142         if ( superClass != null && !Object.class.equals( superClass ) ) {
143             determineValueExtractorDefinitions( valueExtractorDefinitions, superClass );
144         }
145         for ( Class<?> implementedInterface : extractorImplementationType.getInterfaces() ) {
146             if ( !ValueExtractor.class.equals( implementedInterface ) ) {
147                 determineValueExtractorDefinitions( valueExtractorDefinitions, implementedInterface );
148             }
149         }
150         for ( AnnotatedType annotatedInterface : extractorImplementationType.getAnnotatedInterfaces() ) {
151             if ( ValueExtractor.class.equals( ReflectionHelper.getClassFromType( annotatedInterface.getType() ) ) ) {
152                 valueExtractorDefinitions.add( annotatedInterface );
153             }
154         }
155     }
156
157     private static boolean hasUnwrapByDefaultAnnotation(Class<?> extractorImplementationType) {
158         return extractorImplementationType.isAnnotationPresent( UnwrapByDefault.class );
159     }
160
161     public Key getKey() {
162         return key;
163     }
164
165     public Class<?> getContainerType() {
166         return key.containerType;
167     }
168
169     public TypeVariable<?> getExtractedTypeParameter() {
170         return key.extractedTypeParameter;
171     }
172
173     public Optional<Class<?>> getExtractedType() {
174         return extractedType;
175     }
176
177     public ValueExtractor<?> getValueExtractor() {
178         return valueExtractor;
179     }
180
181     public boolean isUnwrapByDefault() {
182         return unwrapByDefault;
183     }
184
185     @Override
186     public String toString() {
187         return "ValueExtractorDescriptor [key=" + key + ", valueExtractor=" + valueExtractor + ", unwrapByDefault=" + unwrapByDefault + "]";
188     }
189
190     public static class Key {
191
192         private final Class<?> containerType;
193         private final TypeVariable<?> extractedTypeParameter;
194         private final int hashCode;
195
196         public Key(Class<?> containerType, TypeVariable<?> extractedTypeParameter) {
197             this.containerType = containerType;
198             this.extractedTypeParameter = extractedTypeParameter;
199             this.hashCode = buildHashCode( containerType, extractedTypeParameter );
200         }
201
202         private static int buildHashCode(Type containerType, TypeVariable<?> extractedTypeParameter) {
203             final int prime = 31;
204             int result = 1;
205             result = prime * result + containerType.hashCode();
206             result = prime * result + extractedTypeParameter.hashCode();
207             return result;
208         }
209
210         @Override
211         public int hashCode() {
212             return hashCode;
213         }
214
215         @Override
216         public boolean equals(Object obj) {
217             if ( this == obj ) {
218                 return true;
219             }
220             if ( obj == null ) {
221                 return false;
222             }
223             if ( getClass() != obj.getClass() ) {
224                 return false;
225             }
226             Key other = (Key) obj;
227
228             return containerType.equals( other.containerType ) &&
229                     extractedTypeParameter.equals( other.extractedTypeParameter );
230         }
231
232         @Override
233         public String toString() {
234             return "Key [containerType=" + StringHelper.toShortString( containerType ) + ", extractedTypeParameter=" + extractedTypeParameter + "]";
235         }
236     }
237 }
238