package org.springframework.data.util;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ObjectUtils;
import org.springframework.util.TypeUtils;
public class ParameterTypes {
private static final TypeDescriptor OBJECT_DESCRIPTOR = TypeDescriptor.valueOf(Object.class);
private static final ConcurrentMap<List<TypeDescriptor>, ParameterTypes> cache = new ConcurrentReferenceHashMap<>();
private final List<TypeDescriptor> types;
private final Lazy<Collection<ParameterTypes>> alternatives;
private ParameterTypes(List<TypeDescriptor> types) {
this.types = types;
this.alternatives = Lazy.of(() -> getAlternatives());
}
public ParameterTypes(List<TypeDescriptor> types, Lazy<Collection<ParameterTypes>> alternatives) {
this.types = types;
this.alternatives = alternatives;
}
public static ParameterTypes of(List<TypeDescriptor> types) {
Assert.notNull(types, "Types must not be null!");
return cache.computeIfAbsent(types, ParameterTypes::new);
}
static ParameterTypes of(Class<?>... types) {
Assert.notNull(types, "Types must not be null!");
Assert.noNullElements(types, "Types must not have null elements!");
return of(Arrays.stream(types)
.map(TypeDescriptor::valueOf)
.collect(Collectors.toList()));
}
public boolean areValidFor(Method method) {
Assert.notNull(method, "Method must not be null!");
if (areValidTypes(method)) {
return true;
}
return hasValidAlternativeFor(method);
}
private boolean hasValidAlternativeFor(Method method) {
return alternatives.get().stream().anyMatch(it -> it.areValidTypes(method))
|| getParent().map(parent -> parent.hasValidAlternativeFor(method)).orElse(false);
}
List<ParameterTypes> getAllAlternatives() {
List<ParameterTypes> result = new ArrayList<>();
result.addAll(alternatives.get());
getParent().ifPresent(it -> result.addAll(it.getAllAlternatives()));
return result;
}
boolean hasTypes(Class<?>... types) {
Assert.notNull(types, "Types must not be null!");
return Arrays.stream(types)
.map(TypeDescriptor::valueOf)
.collect(Collectors.toList())
.equals(this.types);
}
public boolean exactlyMatchParametersOf(Method method) {
if (method.getParameterCount() != types.size()) {
return false;
}
Class<?>[] parameterTypes = method.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
if (parameterTypes[i] != types.get(i).getType()) {
return false;
}
}
return true;
}
@Override
public String toString() {
return types.stream()
.map(TypeDescriptor::getType)
.map(Class::getSimpleName)
.collect(Collectors.joining(", ", "(", ")"));
}
protected Optional<ParameterTypes> getParent() {
return types.isEmpty() ? Optional.empty() : getParent(getTail());
}
protected final Optional<ParameterTypes> getParent(TypeDescriptor tail) {
return types.size() <= 1
? Optional.empty()
: Optional.of(ParentParameterTypes.of(types.subList(0, types.size() - 1), tail));
}
protected Optional<ParameterTypes> withLastVarArgs() {
TypeDescriptor lastDescriptor = types.get(types.size() - 1);
return lastDescriptor.isArray()
? Optional.empty()
: Optional.ofNullable(withVarArgs(lastDescriptor));
}
@SuppressWarnings("null")
private ParameterTypes withVarArgs(TypeDescriptor descriptor) {
TypeDescriptor lastDescriptor = types.get(types.size() - 1);
if (lastDescriptor.isArray() && lastDescriptor.getElementTypeDescriptor().equals(descriptor)) {
return this;
}
List<TypeDescriptor> result = new ArrayList<>(types.subList(0, types.size() - 1));
result.add(TypeDescriptor.array(descriptor));
return ParameterTypes.of(result);
}
private Collection<ParameterTypes> getAlternatives() {
if (types.isEmpty()) {
return Collections.emptyList();
}
List<ParameterTypes> alternatives = new ArrayList<>();
withLastVarArgs().ifPresent(alternatives::add);
ParameterTypes objectVarArgs = withVarArgs(OBJECT_DESCRIPTOR);
if (!alternatives.contains(objectVarArgs)) {
alternatives.add(objectVarArgs);
}
return alternatives;
}
private boolean areValidTypes(Method method) {
Assert.notNull(method, "Method must not be null!");
if (method.getParameterCount() != types.size()) {
return false;
}
Class<?>[] parameterTypes = method.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
if (!TypeUtils.isAssignable(parameterTypes[i], types.get(i).getType())) {
return false;
}
}
return true;
}
private TypeDescriptor getTail() {
return types.get(types.size() - 1);
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ParameterTypes)) {
return false;
}
ParameterTypes that = (ParameterTypes) o;
return ObjectUtils.nullSafeEquals(types, that.types);
}
@Override
public int hashCode() {
return ObjectUtils.nullSafeHashCode(types);
}
static class ParentParameterTypes extends ParameterTypes {
private final TypeDescriptor tail;
private ParentParameterTypes(List<TypeDescriptor> types, TypeDescriptor tail) {
super(types);
this.tail = tail;
}
public static ParentParameterTypes of(List<TypeDescriptor> types, TypeDescriptor tail) {
return new ParentParameterTypes(types, tail);
}
@Override
protected Optional<ParameterTypes> getParent() {
return super.getParent(tail);
}
@Override
protected Optional<ParameterTypes> withLastVarArgs() {
return !tail.isAssignableTo(super.getTail())
? Optional.empty()
: super.withLastVarArgs();
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ParentParameterTypes)) {
return false;
}
if (!super.equals(o)) {
return false;
}
ParentParameterTypes that = (ParentParameterTypes) o;
return ObjectUtils.nullSafeEquals(tail, that.tail);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + ObjectUtils.nullSafeHashCode(tail);
return result;
}
}
}