1
7 package org.hibernate.validator.internal.metadata.aggregated;
8
9 import java.lang.invoke.MethodHandles;
10 import java.lang.reflect.Type;
11 import java.lang.reflect.TypeVariable;
12 import java.util.Collections;
13 import java.util.HashMap;
14 import java.util.Map;
15 import java.util.Map.Entry;
16 import java.util.Objects;
17 import java.util.Set;
18 import java.util.stream.Collectors;
19 import java.util.stream.Stream;
20
21 import javax.validation.GroupSequence;
22
23 import org.hibernate.validator.internal.engine.valueextraction.AnnotatedObject;
24 import org.hibernate.validator.internal.engine.valueextraction.ArrayElement;
25 import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorDescriptor;
26 import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorHelper;
27 import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager;
28 import org.hibernate.validator.internal.util.CollectionHelper;
29 import org.hibernate.validator.internal.util.ReflectionHelper;
30 import org.hibernate.validator.internal.util.StringHelper;
31 import org.hibernate.validator.internal.util.TypeVariableBindings;
32 import org.hibernate.validator.internal.util.TypeVariables;
33 import org.hibernate.validator.internal.util.logging.Log;
34 import org.hibernate.validator.internal.util.logging.LoggerFactory;
35 import org.hibernate.validator.internal.util.stereotypes.Immutable;
36
37
43 public class CascadingMetaDataBuilder {
44
45 private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
46
47 private static final CascadingMetaDataBuilder NON_CASCADING =
48 new CascadingMetaDataBuilder( null, null, null, null, false, Collections.emptyMap(), Collections.emptyMap() );
49
50
53 private final Type enclosingType;
54
55
58 private final TypeVariable<?> typeParameter;
59
60
63 private final Class<?> declaredContainerClass;
64
65
68 private final TypeVariable<?> declaredTypeParameter;
69
70
73 @Immutable
74 private final Map<TypeVariable<?>, CascadingMetaDataBuilder> containerElementTypesCascadingMetaData;
75
76
79 private final boolean cascading;
80
81
84 @Immutable
85 private final Map<Class<?>, Class<?>> groupConversions;
86
87
90 private final boolean hasContainerElementsMarkedForCascading;
91
92
95 private final boolean hasGroupConversionsOnAnnotatedObjectOrContainerElements;
96
97 public CascadingMetaDataBuilder(Type enclosingType, TypeVariable<?> typeParameter, boolean cascading,
98 Map<TypeVariable<?>, CascadingMetaDataBuilder> containerElementTypesCascadingMetaData, Map<Class<?>, Class<?>> groupConversions) {
99 this( enclosingType, typeParameter,
100 TypeVariables.getContainerClass( typeParameter ), TypeVariables.getActualTypeParameter( typeParameter ),
101 cascading, containerElementTypesCascadingMetaData, groupConversions );
102 }
103
104 private CascadingMetaDataBuilder(Type enclosingType, TypeVariable<?> typeParameter, Class<?> declaredContainerClass, TypeVariable<?> declaredTypeParameter,
105 boolean cascading, Map<TypeVariable<?>, CascadingMetaDataBuilder> containerElementTypesCascadingMetaData,
106 Map<Class<?>, Class<?>> groupConversions) {
107 this.enclosingType = enclosingType;
108 this.typeParameter = typeParameter;
109 this.declaredContainerClass = declaredContainerClass;
110 this.declaredTypeParameter = declaredTypeParameter;
111 this.cascading = cascading;
112 this.groupConversions = CollectionHelper.toImmutableMap( groupConversions );
113 this.containerElementTypesCascadingMetaData = CollectionHelper.toImmutableMap( containerElementTypesCascadingMetaData );
114
115 boolean tmpHasContainerElementsMarkedForCascading = false;
116 boolean tmpHasGroupConversionsOnAnnotatedObjectOrContainerElements = !groupConversions.isEmpty();
117 for ( CascadingMetaDataBuilder nestedCascadingTypeParameter : containerElementTypesCascadingMetaData.values() ) {
118 tmpHasContainerElementsMarkedForCascading = tmpHasContainerElementsMarkedForCascading
119 || nestedCascadingTypeParameter.cascading || nestedCascadingTypeParameter.hasContainerElementsMarkedForCascading;
120 tmpHasGroupConversionsOnAnnotatedObjectOrContainerElements = tmpHasGroupConversionsOnAnnotatedObjectOrContainerElements
121 || nestedCascadingTypeParameter.hasGroupConversionsOnAnnotatedObjectOrContainerElements;
122 }
123 hasContainerElementsMarkedForCascading = tmpHasContainerElementsMarkedForCascading;
124 hasGroupConversionsOnAnnotatedObjectOrContainerElements = tmpHasGroupConversionsOnAnnotatedObjectOrContainerElements;
125 }
126
127 public static CascadingMetaDataBuilder nonCascading() {
128 return NON_CASCADING;
129 }
130
131 public static CascadingMetaDataBuilder annotatedObject(Type cascadableType, boolean cascading, Map<TypeVariable<?>, CascadingMetaDataBuilder> containerElementTypesCascadingMetaData, Map<Class<?>, Class<?>> groupConversions) {
132 return new CascadingMetaDataBuilder( cascadableType, AnnotatedObject.INSTANCE, cascading, containerElementTypesCascadingMetaData, groupConversions );
133 }
134
135 public TypeVariable<?> getTypeParameter() {
136 return typeParameter;
137 }
138
139 public Type getEnclosingType() {
140 return enclosingType;
141 }
142
143 public Class<?> getDeclaredContainerClass() {
144 return declaredContainerClass;
145 }
146
147 public TypeVariable<?> getDeclaredTypeParameter() {
148 return declaredTypeParameter;
149 }
150
151 public boolean isCascading() {
152 return cascading;
153 }
154
155 public Map<Class<?>, Class<?>> getGroupConversions() {
156 return groupConversions;
157 }
158
159 public boolean hasContainerElementsMarkedForCascading() {
160 return hasContainerElementsMarkedForCascading;
161 }
162
163 public boolean isMarkedForCascadingOnAnnotatedObjectOrContainerElements() {
164 return cascading || hasContainerElementsMarkedForCascading;
165 }
166
167 public boolean hasGroupConversionsOnAnnotatedObjectOrContainerElements() {
168 return hasGroupConversionsOnAnnotatedObjectOrContainerElements;
169 }
170
171 public Map<TypeVariable<?>, CascadingMetaDataBuilder> getContainerElementTypesCascadingMetaData() {
172 return containerElementTypesCascadingMetaData;
173 }
174
175 public CascadingMetaDataBuilder merge(CascadingMetaDataBuilder otherCascadingTypeParameter) {
176 if ( this == NON_CASCADING ) {
177 return otherCascadingTypeParameter;
178 }
179 if ( otherCascadingTypeParameter == NON_CASCADING ) {
180 return this;
181 }
182
183 boolean cascading = this.cascading || otherCascadingTypeParameter.cascading;
184
185 Map<Class<?>, Class<?>> groupConversions = mergeGroupConversion( this.groupConversions, otherCascadingTypeParameter.groupConversions );
186
187 Map<TypeVariable<?>, CascadingMetaDataBuilder> nestedCascadingTypeParameterMap = Stream
188 .concat( this.containerElementTypesCascadingMetaData.entrySet().stream(),
189 otherCascadingTypeParameter.containerElementTypesCascadingMetaData.entrySet().stream() )
190 .collect(
191 Collectors.toMap( entry -> entry.getKey(), entry -> entry.getValue(), ( value1, value2 ) -> value1.merge( value2 ) ) );
192
193 return new CascadingMetaDataBuilder( this.enclosingType, this.typeParameter, cascading, nestedCascadingTypeParameterMap, groupConversions );
194 }
195
196 public CascadingMetaData build(ValueExtractorManager valueExtractorManager, Object context) {
197 validateGroupConversions( context );
198
199
200
201 if ( !cascading ) {
202
203 if ( !containerElementTypesCascadingMetaData.isEmpty() && hasContainerElementsMarkedForCascading ) {
204 return ContainerCascadingMetaData.of( valueExtractorManager, this, context );
205 }
206
207 else {
208 return NonContainerCascadingMetaData.of( this, context );
209 }
210 }
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226 Set<ValueExtractorDescriptor> containerDetectionValueExtractorCandidates = valueExtractorManager.getResolver()
227 .getValueExtractorCandidatesForContainerDetectionOfGlobalCascadedValidation( enclosingType );
228 if ( !containerDetectionValueExtractorCandidates.isEmpty() ) {
229 if ( containerDetectionValueExtractorCandidates.size() > 1 ) {
230 throw LOG.getUnableToGetMostSpecificValueExtractorDueToSeveralMaximallySpecificValueExtractorsDeclaredException(
231 ReflectionHelper.getClassFromType( enclosingType ),
232 ValueExtractorHelper.toValueExtractorClasses( containerDetectionValueExtractorCandidates )
233 );
234 }
235
236 return ContainerCascadingMetaData.of(
237 valueExtractorManager,
238 new CascadingMetaDataBuilder(
239 enclosingType,
240 typeParameter,
241 cascading,
242 addCascadingMetaDataBasedOnContainerDetection( enclosingType, containerElementTypesCascadingMetaData, groupConversions,
243 containerDetectionValueExtractorCandidates.iterator().next() ),
244 groupConversions
245 ),
246 context
247 );
248 }
249
250
251
252
253
254
255
256
257 Set<ValueExtractorDescriptor> potentialValueExtractorCandidates = valueExtractorManager.getResolver()
258 .getPotentialValueExtractorCandidatesForCascadedValidation( enclosingType );
259
260
261
262
263 if ( !potentialValueExtractorCandidates.isEmpty() ) {
264 return PotentiallyContainerCascadingMetaData.of( this, potentialValueExtractorCandidates, context );
265 }
266
267
268 return NonContainerCascadingMetaData.of( this, context );
269 }
270
271 private void validateGroupConversions(Object context) {
272
273 if ( !cascading && !groupConversions.isEmpty() ) {
274 throw LOG.getGroupConversionOnNonCascadingElementException( context );
275 }
276
277
278 for ( Class<?> group : groupConversions.keySet() ) {
279 if ( group.isAnnotationPresent( GroupSequence.class ) ) {
280 throw LOG.getGroupConversionForSequenceException( group );
281 }
282 }
283
284 for ( CascadingMetaDataBuilder containerElementCascadingTypeParameter : containerElementTypesCascadingMetaData.values() ) {
285 containerElementCascadingTypeParameter.validateGroupConversions( context );
286 }
287 }
288
289 @Override
290 public String toString() {
291 StringBuilder sb = new StringBuilder();
292 sb.append( getClass().getSimpleName() );
293 sb.append( " [" );
294 sb.append( "enclosingType=" ).append( StringHelper.toShortString( enclosingType ) ).append( ", " );
295 sb.append( "typeParameter=" ).append( typeParameter ).append( ", " );
296 sb.append( "cascading=" ).append( cascading ).append( ", " );
297 sb.append( "groupConversions=" ).append( groupConversions ).append( ", " );
298 sb.append( "containerElementTypesCascadingMetaData=" ).append( containerElementTypesCascadingMetaData );
299 sb.append( "]" );
300 return sb.toString();
301 }
302
303 @Override
304 public int hashCode() {
305
306
307 final int prime = 31;
308 int result = 1;
309 result = prime * result + typeParameter.hashCode();
310 result = prime * result + ( cascading ? 1 : 0 );
311 result = prime * result + groupConversions.hashCode();
312 result = prime * result + containerElementTypesCascadingMetaData.hashCode();
313 return result;
314 }
315
316 @Override
317 public boolean equals(Object obj) {
318
319
320 if ( this == obj ) {
321 return true;
322 }
323 if ( obj == null ) {
324 return false;
325 }
326 if ( getClass() != obj.getClass() ) {
327 return false;
328 }
329 CascadingMetaDataBuilder other = (CascadingMetaDataBuilder) obj;
330 if ( !typeParameter.equals( other.typeParameter ) ) {
331 return false;
332 }
333 if ( cascading != other.cascading ) {
334 return false;
335 }
336 if ( !groupConversions.equals( other.groupConversions ) ) {
337 return false;
338 }
339 if ( !containerElementTypesCascadingMetaData.equals( other.containerElementTypesCascadingMetaData ) ) {
340 return false;
341 }
342 return true;
343 }
344
345 private static Map<Class<?>, Class<?>> mergeGroupConversion(Map<Class<?>, Class<?>> groupConversions, Map<Class<?>, Class<?>> otherGroupConversions) {
346 if ( groupConversions.isEmpty() && otherGroupConversions.isEmpty() ) {
347
348 return Collections.emptyMap();
349 }
350
351 Map<Class<?>, Class<?>> mergedGroupConversions = new HashMap<>( groupConversions.size() + otherGroupConversions.size() );
352
353 for ( Entry<Class<?>, Class<?>> otherGroupConversionEntry : otherGroupConversions.entrySet() ) {
354 if ( groupConversions.containsKey( otherGroupConversionEntry.getKey() ) ) {
355 throw LOG.getMultipleGroupConversionsForSameSourceException(
356 otherGroupConversionEntry.getKey(),
357 CollectionHelper.<Class<?>>asSet(
358 groupConversions.get( otherGroupConversionEntry.getKey() ),
359 otherGroupConversionEntry.getValue() ) );
360 }
361 }
362
363 mergedGroupConversions.putAll( groupConversions );
364 mergedGroupConversions.putAll( otherGroupConversions );
365
366 return mergedGroupConversions;
367 }
368
369 private static Map<TypeVariable<?>, CascadingMetaDataBuilder> addCascadingMetaDataBasedOnContainerDetection(Type cascadableType, Map<TypeVariable<?>,
370 CascadingMetaDataBuilder> containerElementTypesCascadingMetaData, Map<Class<?>, Class<?>> groupConversions,
371 ValueExtractorDescriptor possibleValueExtractor) {
372 Class<?> cascadableClass = ReflectionHelper.getClassFromType( cascadableType );
373 if ( cascadableClass.isArray() ) {
374
375 return addArrayElementCascadingMetaData( cascadableClass, containerElementTypesCascadingMetaData, groupConversions );
376 }
377 else {
378 Map<TypeVariable<?>, CascadingMetaDataBuilder> cascadingMetaData = containerElementTypesCascadingMetaData;
379 cascadingMetaData = addCascadingMetaData(
380 cascadableClass,
381 possibleValueExtractor.getContainerType(),
382 possibleValueExtractor.getExtractedTypeParameter(),
383 cascadingMetaData,
384 groupConversions
385 );
386 return cascadingMetaData;
387 }
388 }
389
390 private static Map<TypeVariable<?>, CascadingMetaDataBuilder> addCascadingMetaData(final Class<?> enclosingType, Class<?> referenceType,
391 TypeVariable<?> typeParameter, Map<TypeVariable<?>, CascadingMetaDataBuilder> containerElementTypesCascadingMetaData,
392 Map<Class<?>, Class<?>> groupConversions) {
393
394 Map<Class<?>, Map<TypeVariable<?>, TypeVariable<?>>> typeVariableBindings = TypeVariableBindings.getTypeVariableBindings( enclosingType );
395 final TypeVariable<?> correspondingTypeParameter = typeVariableBindings.get( referenceType ).entrySet().stream()
396 .filter( e -> Objects.equals( e.getKey().getGenericDeclaration(), enclosingType ) )
397 .collect( Collectors.toMap( Map.Entry::getValue, Map.Entry::getKey ) )
398 .get( typeParameter );
399
400 Class<?> cascadableClass;
401 TypeVariable<?> cascadableTypeParameter;
402 if ( correspondingTypeParameter != null ) {
403 cascadableClass = enclosingType;
404 cascadableTypeParameter = correspondingTypeParameter;
405 }
406 else {
407
408 cascadableClass = referenceType;
409 cascadableTypeParameter = typeParameter;
410 }
411
412 Map<TypeVariable<?>, CascadingMetaDataBuilder> amendedCascadingMetadata = CollectionHelper.newHashMap( containerElementTypesCascadingMetaData.size() + 1 );
413 amendedCascadingMetadata.putAll( containerElementTypesCascadingMetaData );
414
415 if ( containerElementTypesCascadingMetaData.containsKey( cascadableTypeParameter ) ) {
416 amendedCascadingMetadata.put( cascadableTypeParameter,
417 makeCascading( containerElementTypesCascadingMetaData.get( cascadableTypeParameter ), groupConversions ) );
418 }
419 else {
420 amendedCascadingMetadata.put( cascadableTypeParameter,
421 new CascadingMetaDataBuilder( cascadableClass, cascadableTypeParameter, enclosingType, correspondingTypeParameter, true,
422 Collections.emptyMap(), groupConversions ) );
423 }
424
425 return amendedCascadingMetadata;
426 }
427
428 private static Map<TypeVariable<?>, CascadingMetaDataBuilder> addArrayElementCascadingMetaData(final Class<?> enclosingType,
429 Map<TypeVariable<?>, CascadingMetaDataBuilder> containerElementTypesCascadingMetaData,
430 Map<Class<?>, Class<?>> groupConversions) {
431 Map<TypeVariable<?>, CascadingMetaDataBuilder> amendedCascadingMetadata = CollectionHelper.newHashMap( containerElementTypesCascadingMetaData.size() + 1 );
432 amendedCascadingMetadata.putAll( containerElementTypesCascadingMetaData );
433
434 TypeVariable<?> cascadableTypeParameter = new ArrayElement( enclosingType );
435
436 amendedCascadingMetadata.put( cascadableTypeParameter,
437 new CascadingMetaDataBuilder( enclosingType, cascadableTypeParameter, true, Collections.emptyMap(), groupConversions ) );
438
439 return amendedCascadingMetadata;
440 }
441
442 private static CascadingMetaDataBuilder makeCascading(CascadingMetaDataBuilder cascadingTypeParameter, Map<Class<?>, Class<?>> groupConversions) {
443 return new CascadingMetaDataBuilder( cascadingTypeParameter.enclosingType, cascadingTypeParameter.typeParameter, true,
444 cascadingTypeParameter.containerElementTypesCascadingMetaData,
445 cascadingTypeParameter.groupConversions.isEmpty() ? groupConversions : cascadingTypeParameter.groupConversions );
446 }
447 }
448