/* * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.data.util; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import org.springframework.util.StringUtils; /** * Base class for all types that include parameterization of some kind. Crucial as we have to take note of the parent * class we will have to resolve generic parameters against. * * @author Oliver Gierke * @author Mark Paluch * @author Christoph Strobl */ class ParameterizedTypeInformation<T> extends ParentTypeAwareTypeInformation<T> { private final ParameterizedType type; private Boolean resolved; /** * Creates a new {@link ParameterizedTypeInformation} for the given {@link Type} and parent {@link TypeDiscoverer}. * * @param type must not be {@literal null} * @param parent must not be {@literal null} */ public ParameterizedTypeInformation(ParameterizedType type, TypeDiscoverer<?> parent, Map<TypeVariable<?>, Type> typeVariableMap) { super(type, parent, typeVariableMap); this.type = type; } /* * (non-Javadoc) * @see org.springframework.data.util.TypeDiscoverer#doGetMapValueType() */ @Override protected Optional<TypeInformation<?>> doGetMapValueType() { if (Map.class.isAssignableFrom(getType())) { Type[] arguments = type.getActualTypeArguments(); if (arguments.length > 1) { return Optional.of(createInfo(arguments[1])); } } Class<?> rawType = getType(); Set<Type> supertypes = new HashSet<>(); Optional.ofNullable(rawType.getGenericSuperclass()).ifPresent(supertypes::add); supertypes.addAll(Arrays.asList(rawType.getGenericInterfaces())); Optional<TypeInformation<?>> result = supertypes.stream()// .map(it -> Pair.of(it, resolveType(it)))// .filter(it -> Map.class.isAssignableFrom(it.getSecond()))// .<TypeInformation<?>>map(it -> { ParameterizedType parameterizedSupertype = (ParameterizedType) it.getFirst(); Type[] arguments = parameterizedSupertype.getActualTypeArguments(); return createInfo(arguments[1]); }).findFirst(); return result.isPresent() ? result : super.doGetMapValueType(); } /* * (non-Javadoc) * @see org.springframework.data.util.TypeDiscoverer#getTypeParameters() */ @Override public List<TypeInformation<?>> getTypeArguments() { List<TypeInformation<?>> result = new ArrayList<>(); for (Type argument : type.getActualTypeArguments()) { result.add(createInfo(argument)); } return result; } /* * (non-Javadoc) * @see org.springframework.data.util.TypeDiscoverer#isAssignableFrom(org.springframework.data.util.TypeInformation) */ @Override public boolean isAssignableFrom(TypeInformation<?> target) { if (this.equals(target)) { return true; } Class<T> rawType = getType(); Class<?> rawTargetType = target.getType(); if (!rawType.isAssignableFrom(rawTargetType)) { return false; } TypeInformation<?> otherTypeInformation = rawType.equals(rawTargetType) ? target : target.getSuperTypeInformation(rawType); List<TypeInformation<?>> myParameters = getTypeArguments(); List<TypeInformation<?>> typeParameters = otherTypeInformation.getTypeArguments(); if (myParameters.size() != typeParameters.size()) { return false; } for (int i = 0; i < myParameters.size(); i++) { if (!myParameters.get(i).isAssignableFrom(typeParameters.get(i))) { return false; } } return true; } /* * (non-Javadoc) * @see org.springframework.data.util.TypeDiscoverer#doGetComponentType() */ @Override protected Optional<TypeInformation<?>> doGetComponentType() { return Optional.of(createInfo(type.getActualTypeArguments()[0])); } /* * (non-Javadoc) * @see org.springframework.data.util.ParentTypeAwareTypeInformation#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof ParameterizedTypeInformation)) { return false; } ParameterizedTypeInformation<?> that = (ParameterizedTypeInformation<?>) obj; if (this.isResolvedCompletely() && that.isResolvedCompletely()) { return this.type.equals(that.type); } return super.equals(obj); } /* * (non-Javadoc) * @see org.springframework.data.util.ParentTypeAwareTypeInformation#hashCode() */ @Override public int hashCode() { return isResolvedCompletely() ? this.type.hashCode() : super.hashCode(); } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return String.format("%s<%s>", getType().getName(), StringUtils.collectionToCommaDelimitedString(getTypeArguments())); } private boolean isResolvedCompletely() { if (resolved != null) { return resolved; } Type[] typeArguments = type.getActualTypeArguments(); if (typeArguments.length == 0) { return cacheAndReturn(false); } for (Type typeArgument : typeArguments) { TypeInformation<?> info = createInfo(typeArgument); if (info instanceof ParameterizedTypeInformation) { if (!((ParameterizedTypeInformation<?>) info).isResolvedCompletely()) { return cacheAndReturn(false); } } if (!(info instanceof ClassTypeInformation)) { return cacheAndReturn(false); } } return cacheAndReturn(true); } private boolean cacheAndReturn(boolean resolved) { this.resolved = resolved; return resolved; } }