1
7 package org.hibernate.validator.internal.engine.valueextraction;
8
9 import java.lang.invoke.MethodHandles;
10 import java.lang.reflect.Type;
11 import java.lang.reflect.TypeVariable;
12 import java.util.Collection;
13 import java.util.Collections;
14 import java.util.HashSet;
15 import java.util.Iterator;
16 import java.util.Map;
17 import java.util.Objects;
18 import java.util.Set;
19 import java.util.concurrent.ConcurrentHashMap;
20 import java.util.stream.Collectors;
21
22 import javax.validation.ConstraintDeclarationException;
23 import javax.validation.valueextraction.ValueExtractor;
24
25 import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder;
26 import org.hibernate.validator.internal.metadata.aggregated.ContainerCascadingMetaData;
27 import org.hibernate.validator.internal.metadata.aggregated.PotentiallyContainerCascadingMetaData;
28 import org.hibernate.validator.internal.util.CollectionHelper;
29 import org.hibernate.validator.internal.util.Contracts;
30 import org.hibernate.validator.internal.util.ReflectionHelper;
31 import org.hibernate.validator.internal.util.TypeHelper;
32 import org.hibernate.validator.internal.util.TypeVariableBindings;
33 import org.hibernate.validator.internal.util.TypeVariables;
34 import org.hibernate.validator.internal.util.logging.Log;
35 import org.hibernate.validator.internal.util.logging.LoggerFactory;
36 import org.hibernate.validator.internal.util.stereotypes.Immutable;
37
38
46 public class ValueExtractorResolver {
47
48 private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
49
50 @Immutable
51 private final Set<ValueExtractorDescriptor> registeredValueExtractors;
52
53 private final ConcurrentHashMap<ValueExtractorCacheKey, Set<ValueExtractorDescriptor>> possibleValueExtractorsByRuntimeTypeAndTypeParameter = new ConcurrentHashMap<>();
54
55 private final ConcurrentHashMap<Class<?>, Set<ValueExtractorDescriptor>> possibleValueExtractorsByRuntimeType = new ConcurrentHashMap<>();
56
57 private final Set<Class<?>> nonContainerTypes = Collections.newSetFromMap( new ConcurrentHashMap<>() );
58
59 ValueExtractorResolver(Set<ValueExtractorDescriptor> valueExtractors) {
60 this.registeredValueExtractors = CollectionHelper.toImmutableSet( valueExtractors );
61 }
62
63
70 public Set<ValueExtractorDescriptor> getMaximallySpecificValueExtractors(Class<?> declaredType) {
71 return getRuntimeCompliantValueExtractors( declaredType, registeredValueExtractors );
72 }
73
74
82 public ValueExtractorDescriptor getMaximallySpecificAndContainerElementCompliantValueExtractor(Class<?> declaredType, TypeVariable<?> typeParameter) {
83 return getUniqueValueExtractorOrThrowException(
84 declaredType,
85 getRuntimeAndContainerElementCompliantValueExtractorsFromPossibleCandidates( declaredType, typeParameter, declaredType, registeredValueExtractors )
86 );
87 }
88
89
100 public ValueExtractorDescriptor getMaximallySpecificAndRuntimeContainerElementCompliantValueExtractor(Type declaredType, TypeVariable<?> typeParameter,
101 Class<?> runtimeType, Collection<ValueExtractorDescriptor> valueExtractorCandidates) {
102 Contracts.assertNotEmpty( valueExtractorCandidates, "Value extractor candidates cannot be empty" );
103 if ( valueExtractorCandidates.size() == 1 ) {
104 return valueExtractorCandidates.iterator().next();
105 }
106 else {
107 return getUniqueValueExtractorOrThrowException(
108 runtimeType,
109 getRuntimeAndContainerElementCompliantValueExtractorsFromPossibleCandidates(
110 declaredType, typeParameter, runtimeType, valueExtractorCandidates
111 )
112 );
113 }
114 }
115
116
130 public ValueExtractorDescriptor getMaximallySpecificValueExtractorForAllContainerElements(Class<?> runtimeType, Set<ValueExtractorDescriptor> potentialValueExtractorDescriptors) {
131
132 if ( TypeHelper.isAssignable( Map.class, runtimeType ) ) {
133 return MapValueExtractor.DESCRIPTOR;
134 }
135
136 return getUniqueValueExtractorOrThrowException( runtimeType, getRuntimeCompliantValueExtractors( runtimeType, potentialValueExtractorDescriptors ) );
137 }
138
139
146 public Set<ValueExtractorDescriptor> getValueExtractorCandidatesForCascadedValidation(Type declaredType, TypeVariable<?> typeParameter) {
147 Set<ValueExtractorDescriptor> valueExtractorDescriptors = new HashSet<>();
148
149 valueExtractorDescriptors.addAll( getRuntimeAndContainerElementCompliantValueExtractorsFromPossibleCandidates( declaredType, typeParameter,
150 TypeHelper.getErasedReferenceType( declaredType ), registeredValueExtractors
151 ) );
152 valueExtractorDescriptors.addAll( getPotentiallyRuntimeTypeCompliantAndContainerElementCompliantValueExtractors( declaredType, typeParameter ) );
153
154 return CollectionHelper.toImmutableSet( valueExtractorDescriptors );
155 }
156
157
170 public Set<ValueExtractorDescriptor> getValueExtractorCandidatesForContainerDetectionOfGlobalCascadedValidation(Type enclosingType) {
171
172 boolean mapAssignable = TypeHelper.isAssignable( Map.class, enclosingType );
173
174 Class<?> enclosingClass = ReflectionHelper.getClassFromType( enclosingType );
175 return getRuntimeCompliantValueExtractors( enclosingClass, registeredValueExtractors )
176 .stream()
177 .filter( ved -> !mapAssignable || !ved.equals( MapKeyExtractor.DESCRIPTOR ) )
178 .collect( Collectors.collectingAndThen( Collectors.toSet(), CollectionHelper::toImmutableSet ) );
179 }
180
181
194 public Set<ValueExtractorDescriptor> getPotentialValueExtractorCandidatesForCascadedValidation(Type declaredType) {
195 return registeredValueExtractors
196 .stream()
197 .filter( e -> TypeHelper.isAssignable( declaredType, e.getContainerType() ) )
198 .collect( Collectors.collectingAndThen( Collectors.toSet(), CollectionHelper::toImmutableSet ) );
199 }
200
201 public void clear() {
202 nonContainerTypes.clear();
203 possibleValueExtractorsByRuntimeType.clear();
204 possibleValueExtractorsByRuntimeTypeAndTypeParameter.clear();
205 }
206
207
212 private Set<ValueExtractorDescriptor> getPotentiallyRuntimeTypeCompliantAndContainerElementCompliantValueExtractors(Type declaredType,
213 TypeVariable<?> typeParameter) {
214 boolean isInternal = TypeVariables.isInternal( typeParameter );
215 Type erasedDeclaredType = TypeHelper.getErasedReferenceType( declaredType );
216
217 Set<ValueExtractorDescriptor> typeCompatibleExtractors = registeredValueExtractors
218 .stream()
219 .filter( e -> TypeHelper.isAssignable( erasedDeclaredType, e.getContainerType() ) )
220 .collect( Collectors.toSet() );
221
222 Set<ValueExtractorDescriptor> containerElementCompliantExtractors = new HashSet<>();
223
224 for ( ValueExtractorDescriptor extractorDescriptor : typeCompatibleExtractors ) {
225 TypeVariable<?> typeParameterBoundToExtractorType;
226
227 if ( !isInternal ) {
228 Map<Class<?>, Map<TypeVariable<?>, TypeVariable<?>>> allBindings =
229 TypeVariableBindings.getTypeVariableBindings( extractorDescriptor.getContainerType() );
230
231 Map<TypeVariable<?>, TypeVariable<?>> bindingsForExtractorType = allBindings.get( erasedDeclaredType );
232 typeParameterBoundToExtractorType = bind( extractorDescriptor.getExtractedTypeParameter(), bindingsForExtractorType );
233 }
234 else {
235 typeParameterBoundToExtractorType = typeParameter;
236 }
237
238 if ( Objects.equals( typeParameter, typeParameterBoundToExtractorType ) ) {
239 containerElementCompliantExtractors.add( extractorDescriptor );
240 }
241 }
242
243 return containerElementCompliantExtractors;
244 }
245
246 private ValueExtractorDescriptor getUniqueValueExtractorOrThrowException(Class<?> runtimeType,
247 Set<ValueExtractorDescriptor> maximallySpecificContainerElementCompliantValueExtractors) {
248 if ( maximallySpecificContainerElementCompliantValueExtractors.size() == 1 ) {
249 return maximallySpecificContainerElementCompliantValueExtractors.iterator().next();
250 }
251 else if ( maximallySpecificContainerElementCompliantValueExtractors.isEmpty() ) {
252 return null;
253 }
254 else {
255 throw LOG.getUnableToGetMostSpecificValueExtractorDueToSeveralMaximallySpecificValueExtractorsDeclaredException( runtimeType,
256 ValueExtractorHelper.toValueExtractorClasses( maximallySpecificContainerElementCompliantValueExtractors )
257 );
258 }
259 }
260
261 private Set<ValueExtractorDescriptor> getMaximallySpecificValueExtractors(Set<ValueExtractorDescriptor> possibleValueExtractors) {
262 Set<ValueExtractorDescriptor> valueExtractorDescriptors = CollectionHelper.newHashSet( possibleValueExtractors.size() );
263
264 for ( ValueExtractorDescriptor descriptor : possibleValueExtractors ) {
265 if ( valueExtractorDescriptors.isEmpty() ) {
266 valueExtractorDescriptors.add( descriptor );
267 continue;
268 }
269 Iterator<ValueExtractorDescriptor> candidatesIterator = valueExtractorDescriptors.iterator();
270 boolean isNewRoot = true;
271 while ( candidatesIterator.hasNext() ) {
272 ValueExtractorDescriptor candidate = candidatesIterator.next();
273
274
275
276 if ( candidate.getContainerType().equals( descriptor.getContainerType() ) ) {
277 continue;
278 }
279
280 if ( TypeHelper.isAssignable( candidate.getContainerType(), descriptor.getContainerType() ) ) {
281 candidatesIterator.remove();
282 }
283 else if ( TypeHelper.isAssignable( descriptor.getContainerType(), candidate.getContainerType() ) ) {
284 isNewRoot = false;
285 }
286 }
287 if ( isNewRoot ) {
288 valueExtractorDescriptors.add( descriptor );
289 }
290 }
291 return valueExtractorDescriptors;
292 }
293
294
298 private Set<ValueExtractorDescriptor> getRuntimeCompliantValueExtractors(Class<?> runtimeType, Set<ValueExtractorDescriptor> potentialValueExtractorDescriptors) {
299 if ( nonContainerTypes.contains( runtimeType ) ) {
300 return Collections.emptySet();
301 }
302
303 Set<ValueExtractorDescriptor> valueExtractorDescriptors = possibleValueExtractorsByRuntimeType.get( runtimeType );
304
305 if ( valueExtractorDescriptors != null ) {
306 return valueExtractorDescriptors;
307 }
308
309 Set<ValueExtractorDescriptor> possibleValueExtractors = potentialValueExtractorDescriptors
310 .stream()
311 .filter( e -> TypeHelper.isAssignable( e.getContainerType(), runtimeType ) )
312 .collect( Collectors.toSet() );
313
314 valueExtractorDescriptors = getMaximallySpecificValueExtractors( possibleValueExtractors );
315
316 if ( valueExtractorDescriptors.isEmpty() ) {
317 nonContainerTypes.add( runtimeType );
318 return Collections.emptySet();
319 }
320
321 Set<ValueExtractorDescriptor> valueExtractorDescriptorsToCache = CollectionHelper.toImmutableSet( valueExtractorDescriptors );
322 Set<ValueExtractorDescriptor> cachedValueExtractorDescriptors = possibleValueExtractorsByRuntimeType.putIfAbsent( runtimeType,
323 valueExtractorDescriptorsToCache );
324 return cachedValueExtractorDescriptors != null ? cachedValueExtractorDescriptors : valueExtractorDescriptorsToCache;
325 }
326
327 private Set<ValueExtractorDescriptor> getRuntimeAndContainerElementCompliantValueExtractorsFromPossibleCandidates(Type declaredType,
328 TypeVariable<?> typeParameter, Class<?> runtimeType, Collection<ValueExtractorDescriptor> valueExtractorCandidates) {
329 if ( nonContainerTypes.contains( runtimeType ) ) {
330 return Collections.emptySet();
331 }
332
333 ValueExtractorCacheKey cacheKey = new ValueExtractorCacheKey( runtimeType, typeParameter );
334
335 Set<ValueExtractorDescriptor> valueExtractorDescriptors = possibleValueExtractorsByRuntimeTypeAndTypeParameter.get( cacheKey );
336
337 if ( valueExtractorDescriptors != null ) {
338 return valueExtractorDescriptors;
339 }
340
341 boolean isInternal = TypeVariables.isInternal( typeParameter );
342 Class<?> erasedDeclaredType = TypeHelper.getErasedReferenceType( declaredType );
343
344 Set<ValueExtractorDescriptor> possibleValueExtractors = valueExtractorCandidates
345 .stream()
346 .filter( e -> TypeHelper.isAssignable( e.getContainerType(), runtimeType ) )
347 .filter( extractorDescriptor ->
348 checkValueExtractorTypeCompatibility(
349 typeParameter, isInternal, erasedDeclaredType, extractorDescriptor
350 )
351 ).collect( Collectors.toSet() );
352
353 valueExtractorDescriptors = getMaximallySpecificValueExtractors( possibleValueExtractors );
354
355 if ( valueExtractorDescriptors.isEmpty() ) {
356 nonContainerTypes.add( runtimeType );
357 return Collections.emptySet();
358 }
359
360 Set<ValueExtractorDescriptor> valueExtractorDescriptorsToCache = CollectionHelper.toImmutableSet( valueExtractorDescriptors );
361 Set<ValueExtractorDescriptor> cachedValueExtractorDescriptors = possibleValueExtractorsByRuntimeTypeAndTypeParameter.putIfAbsent( cacheKey,
362 valueExtractorDescriptorsToCache );
363 return cachedValueExtractorDescriptors != null ? cachedValueExtractorDescriptors : valueExtractorDescriptorsToCache;
364 }
365
366 private boolean checkValueExtractorTypeCompatibility(TypeVariable<?> typeParameter, boolean isInternal, Class<?> erasedDeclaredType,
367 ValueExtractorDescriptor extractorDescriptor) {
368 return TypeHelper.isAssignable( extractorDescriptor.getContainerType(), erasedDeclaredType )
369 ? validateValueExtractorCompatibility( isInternal, erasedDeclaredType, extractorDescriptor.getContainerType(), typeParameter,
370 extractorDescriptor.getExtractedTypeParameter()
371 )
372 : validateValueExtractorCompatibility( isInternal, extractorDescriptor.getContainerType(), erasedDeclaredType,
373 extractorDescriptor.getExtractedTypeParameter(), typeParameter
374 );
375 }
376
377 private boolean validateValueExtractorCompatibility(boolean isInternal,
378 Class<?> typeForBinding,
379 Class<?> typeToBind,
380 TypeVariable<?> typeParameterForBinding,
381 TypeVariable<?> typeParameterToCompare) {
382 TypeVariable<?> typeParameterBoundToExtractorType;
383
384 if ( !isInternal ) {
385 Map<Class<?>, Map<TypeVariable<?>, TypeVariable<?>>> allBindings =
386 TypeVariableBindings.getTypeVariableBindings( typeForBinding );
387
388 Map<TypeVariable<?>, TypeVariable<?>> bindingsForExtractorType = allBindings.get( typeToBind );
389 typeParameterBoundToExtractorType = bind( typeParameterForBinding, bindingsForExtractorType );
390 }
391 else {
392 typeParameterBoundToExtractorType = typeParameterForBinding;
393 }
394
395 return Objects.equals( typeParameterToCompare, typeParameterBoundToExtractorType );
396 }
397
398 private TypeVariable<?> bind(TypeVariable<?> typeParameter, Map<TypeVariable<?>, TypeVariable<?>> bindings) {
399 return bindings != null ? bindings.get( typeParameter ) : null;
400 }
401
402 private static class ValueExtractorCacheKey {
403
404
405
406 private Class<?> type;
407 private TypeVariable<?> typeParameter;
408 private int hashCode;
409
410 ValueExtractorCacheKey(Class<?> type, TypeVariable<?> typeParameter) {
411 this.type = type;
412 this.typeParameter = typeParameter;
413 this.hashCode = buildHashCode();
414 }
415
416 @Override
417 public boolean equals(Object o) {
418 if ( this == o ) {
419 return true;
420 }
421 if ( o == null ) {
422 return false;
423 }
424
425 ValueExtractorCacheKey that = (ValueExtractorCacheKey) o;
426 return Objects.equals( this.type, that.type ) &&
427 Objects.equals( this.typeParameter, that.typeParameter );
428 }
429
430 @Override
431 public int hashCode() {
432 return hashCode;
433 }
434
435 private int buildHashCode() {
436 int result = this.type.hashCode();
437 result = 31 * result + ( this.typeParameter != null ? this.typeParameter.hashCode() : 0 );
438 return result;
439 }
440 }
441 }
442