1
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
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