
io.avaje.validation.core.Util Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of avaje-validator Show documentation
Show all versions of avaje-validator Show documentation
validator for annotated pojos using constraint annotations and source code generation
/*
* Copyright (C) 2014 Square, Inc.
*
* 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
*
* https://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 io.avaje.validation.core;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
/** Utility methods for defining Types. */
public final class Util {
static final Type[] EMPTY_TYPE_ARRAY = {};
private Util() {}
/** Returns an array type whose elements are all instances of {@code componentType}. */
public static GenericArrayType arrayOf(Type elementType) {
return new Util.GenericArrayTypeImpl(elementType);
}
/**
* Returns a new parameterized type, applying {@code typeArguments} to {@code rawType}. Use this
* method if {@code rawType} is not enclosed in another type.
*/
public static ParameterizedType newParameterizedType(Type rawType, Type... typeArguments) {
if (typeArguments.length == 0) {
throw new IllegalArgumentException("Missing type arguments for " + rawType);
}
return new Util.ParameterizedTypeImpl(null, rawType, typeArguments);
}
static boolean typesMatch(Type pattern, Type candidate) {
return Util.equals(pattern, candidate);
}
static boolean isAnnotationPresent(Set extends Annotation> annotations, Class extends Annotation> annotationClass) {
if (annotations.isEmpty()) return false; // Save an iterator in the common case.
for (final Annotation annotation : annotations) {
if (annotation.annotationType() == annotationClass) return true;
}
return false;
}
static Type canonicalizeClass(Class> cls) {
return cls.isArray() ? new GenericArrayTypeImpl(canonicalize(cls.getComponentType())) : cls;
}
/**
* Returns a type that is functionally equal but not necessarily equal according to {@link
* Object#equals(Object) Object.equals()}.
*/
static Type canonicalize(Type type) {
if (type instanceof Class c) {
return c.isArray() ? new GenericArrayTypeImpl(canonicalize(c.getComponentType())) : c;
} else if (type instanceof ParameterizedType p) {
if (type instanceof ParameterizedTypeImpl) return type;
return new ParameterizedTypeImpl(
p.getOwnerType(), p.getRawType(), p.getActualTypeArguments());
} else if (type instanceof GenericArrayType g) {
if (type instanceof GenericArrayTypeImpl) return type;
return new GenericArrayTypeImpl(g.getGenericComponentType());
} else if (type instanceof WildcardType w) {
if (type instanceof WildcardTypeImpl) return type;
return new WildcardTypeImpl(w.getUpperBounds(), w.getLowerBounds());
} else {
return type; // This type is unsupported!
}
}
/** If type is a "? extends X" wildcard, returns X; otherwise returns type unchanged. */
static Type removeSubtypeWildcard(Type type) {
if (!(type instanceof WildcardType)) return type;
final Type[] lowerBounds = ((WildcardType) type).getLowerBounds();
if (lowerBounds.length != 0) return type;
final Type[] upperBounds = ((WildcardType) type).getUpperBounds();
if (upperBounds.length != 1) throw new IllegalArgumentException();
return upperBounds[0];
}
static Type resolve(Type context, Class> contextRawType, Type toResolve) {
return resolve(context, contextRawType, toResolve, new LinkedHashSet>());
}
private static Type resolve(
Type context,
Class> contextRawType,
Type toResolve,
Collection> visitedTypeVariables) {
// This implementation is made a little more complicated in an attempt to avoid object-creation.
while (true) {
if (toResolve instanceof TypeVariable typeVariable) {
if (visitedTypeVariables.contains(typeVariable)) {
// cannot reduce due to infinite recursion
return toResolve;
} else {
visitedTypeVariables.add(typeVariable);
}
toResolve = resolveTypeVariable(context, contextRawType, typeVariable);
if (toResolve == typeVariable) return toResolve;
} else if (toResolve instanceof Class original && original.isArray()) {
final Type componentType = original.getComponentType();
final Type newComponentType =
resolve(context, contextRawType, componentType, visitedTypeVariables);
return componentType == newComponentType ? original : arrayOf(newComponentType);
} else if (toResolve instanceof GenericArrayType original) {
final Type componentType = original.getGenericComponentType();
final Type newComponentType =
resolve(context, contextRawType, componentType, visitedTypeVariables);
return componentType == newComponentType ? original : arrayOf(newComponentType);
} else if (toResolve instanceof ParameterizedType original) {
final Type ownerType = original.getOwnerType();
final Type newOwnerType = resolve(context, contextRawType, ownerType, visitedTypeVariables);
boolean changed = newOwnerType != ownerType;
Type[] args = original.getActualTypeArguments();
for (int t = 0, length = args.length; t < length; t++) {
final Type resolvedTypeArgument =
resolve(context, contextRawType, args[t], visitedTypeVariables);
if (resolvedTypeArgument != args[t]) {
if (!changed) {
args = args.clone();
changed = true;
}
args[t] = resolvedTypeArgument;
}
}
return changed
? new ParameterizedTypeImpl(newOwnerType, original.getRawType(), args)
: original;
} else if (toResolve instanceof WildcardType original) {
final Type[] originalLowerBound = original.getLowerBounds();
final Type[] originalUpperBound = original.getUpperBounds();
if (originalLowerBound.length == 1) {
final Type lowerBound =
resolve(context, contextRawType, originalLowerBound[0], visitedTypeVariables);
if (lowerBound != originalLowerBound[0]) {
return supertypeOf(lowerBound);
}
} else if (originalUpperBound.length == 1) {
final Type upperBound =
resolve(context, contextRawType, originalUpperBound[0], visitedTypeVariables);
if (upperBound != originalUpperBound[0]) {
return subtypeOf(upperBound);
}
}
return original;
} else {
return toResolve;
}
}
}
static Type resolveTypeVariable(Type context, Class> contextRawType, TypeVariable> unknown) {
final Class> declaredByRaw = declaringClassOf(unknown);
// We can't reduce this further.
if (declaredByRaw == null) {
return unknown;
}
final Type declaredBy = genericSupertype(context, contextRawType, declaredByRaw);
if (declaredBy instanceof ParameterizedType type) {
final int index = indexOf(declaredByRaw.getTypeParameters(), unknown);
return type.getActualTypeArguments()[index];
}
return unknown;
}
/**
* Returns the generic supertype for {@code supertype}. For example, given a class {@code
* IntegerSet}, the result for when supertype is {@code Set.class} is {@code Set} and the
* result when the supertype is {@code Collection.class} is {@code Collection}.
*/
static Type genericSupertype(Type context, Class> rawType, Class> toResolve) {
if (toResolve == rawType) {
return context;
}
// we skip searching through interfaces if unknown is an interface
if (toResolve.isInterface()) {
final Class>[] interfaces = rawType.getInterfaces();
for (int i = 0, length = interfaces.length; i < length; i++) {
if (interfaces[i] == toResolve) {
return rawType.getGenericInterfaces()[i];
} else if (toResolve.isAssignableFrom(interfaces[i])) {
return genericSupertype(rawType.getGenericInterfaces()[i], interfaces[i], toResolve);
}
}
}
// check our supertypes
if (!rawType.isInterface()) {
while (rawType != Object.class) {
final Class> rawSupertype = rawType.getSuperclass();
if (rawSupertype == toResolve) {
return rawType.getGenericSuperclass();
} else if (toResolve.isAssignableFrom(rawSupertype)) {
return genericSupertype(rawType.getGenericSuperclass(), rawSupertype, toResolve);
}
rawType = rawSupertype;
}
}
// we can't resolve this further
return toResolve;
}
static int hashCodeOrZero(Object o) {
return o != null ? o.hashCode() : 0;
}
static String typeToString(Type type) {
return type instanceof Class c ? c.getName() : type.toString();
}
static int indexOf(Object[] array, Object toFind) {
for (int i = 0; i < array.length; i++) {
if (toFind.equals(array[i])) return i;
}
throw new NoSuchElementException();
}
/**
* Returns the declaring class of {@code typeVariable}, or {@code null} if it was not declared by
* a class.
*/
static Class> declaringClassOf(TypeVariable> typeVariable) {
final GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
return genericDeclaration instanceof Class c ? c : null;
}
static void checkNotPrimitive(Type type) {
if ((type instanceof Class> class1) && class1.isPrimitive()) {
throw new IllegalArgumentException("Unexpected primitive " + type + ". Use the boxed type.");
}
}
static final class ParameterizedTypeImpl implements ParameterizedType {
private final Type ownerType;
private final Type rawType;
public final Type[] typeArguments;
public ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) {
// Require an owner type if the raw type needs it.
if (ownerType != null && rawType instanceof Class> class1) {
final Class> enclosingClass = class1.getEnclosingClass();
if (enclosingClass == null) {
throw new IllegalArgumentException("unexpected owner type for " + rawType + ": " + ownerType);
} else if (enclosingClass != null) { // FIXME: Huh?
throw new IllegalArgumentException("unexpected owner type for " + rawType + ": null");
}
}
this.ownerType = ownerType == null ? null : canonicalize(ownerType);
this.rawType = canonicalize(rawType);
this.typeArguments = typeArguments.clone();
for (int t = 0; t < this.typeArguments.length; t++) {
if (this.typeArguments[t] == null) throw new NullPointerException();
checkNotPrimitive(this.typeArguments[t]);
this.typeArguments[t] = canonicalize(this.typeArguments[t]);
}
}
@Override
public Type[] getActualTypeArguments() {
return typeArguments.clone();
}
@Override
public Type getRawType() {
return rawType;
}
@Override
public Type getOwnerType() {
return ownerType;
}
@Override
public boolean equals(Object other) {
return other instanceof ParameterizedType pt && Util.equals(this, pt);
}
@Override
public int hashCode() {
return Arrays.hashCode(typeArguments) ^ rawType.hashCode() ^ hashCodeOrZero(ownerType);
}
@Override
public String toString() {
final StringBuilder result = new StringBuilder(30 * (typeArguments.length + 1));
result.append(typeToString(rawType));
if (typeArguments.length == 0) {
return result.toString();
}
result.append("<").append(typeToString(typeArguments[0]));
for (int i = 1; i < typeArguments.length; i++) {
result.append(", ").append(typeToString(typeArguments[i]));
}
return result.append(">").toString();
}
}
static final class GenericArrayTypeImpl implements GenericArrayType {
private final Type componentType;
GenericArrayTypeImpl(Type componentType) {
this.componentType = canonicalize(componentType);
}
@Override
public Type getGenericComponentType() {
return componentType;
}
@Override
public boolean equals(Object o) {
return o instanceof GenericArrayType gat && Util.equals(this, gat);
}
@Override
public int hashCode() {
return componentType.hashCode();
}
@Override
public String toString() {
return typeToString(componentType) + "[]";
}
}
/**
* The WildcardType interface supports multiple upper bounds and multiple lower bounds. We only
* support what the Java 6 language needs - at most one bound. If a lower bound is set, the upper
* bound must be Object.class.
*/
static final class WildcardTypeImpl implements WildcardType {
private final Type upperBound;
private final Type lowerBound;
WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) {
if ((lowerBounds.length > 1) || (upperBounds.length != 1)) throw new IllegalArgumentException();
if (lowerBounds.length == 1) {
if (lowerBounds[0] == null) throw new NullPointerException();
checkNotPrimitive(lowerBounds[0]);
if (upperBounds[0] != Object.class) throw new IllegalArgumentException();
this.lowerBound = canonicalize(lowerBounds[0]);
this.upperBound = Object.class;
} else {
if (upperBounds[0] == null) throw new NullPointerException();
checkNotPrimitive(upperBounds[0]);
this.lowerBound = null;
this.upperBound = canonicalize(upperBounds[0]);
}
}
@Override
public Type[] getUpperBounds() {
return new Type[] {upperBound};
}
@Override
public Type[] getLowerBounds() {
return lowerBound != null ? new Type[] {lowerBound} : EMPTY_TYPE_ARRAY;
}
@Override
public boolean equals(Object other) {
return other instanceof WildcardType wt && Util.equals(this, wt);
}
@Override
public int hashCode() {
// This equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds()).
return (lowerBound != null ? 31 + lowerBound.hashCode() : 1) ^ (31 + upperBound.hashCode());
}
@Override
public String toString() {
if (lowerBound != null) {
return "? super " + typeToString(lowerBound);
} else if (upperBound == Object.class) {
return "?";
} else {
return "? extends " + typeToString(upperBound);
}
}
}
static String typeAnnotatedWithAnnotations(Type type, Set extends Annotation> annotations) {
return type + (annotations.isEmpty() ? " (with no annotations)" : " annotated " + annotations);
}
/**
* Returns a type that represents an unknown type that extends {@code bound}. For example, if
* {@code bound} is {@code CharSequence.class}, this returns {@code ? extends CharSequence}. If
* {@code bound} is {@code Object.class}, this returns {@code ?}, which is shorthand for {@code ?
* extends Object}.
*/
static WildcardType subtypeOf(Type bound) {
Type[] upperBounds;
if (bound instanceof WildcardType type) {
upperBounds = type.getUpperBounds();
} else {
upperBounds = new Type[] {bound};
}
return new Util.WildcardTypeImpl(upperBounds, EMPTY_TYPE_ARRAY);
}
/**
* Returns a type that represents an unknown supertype of {@code bound}. For example, if {@code
* bound} is {@code String.class}, this returns {@code ? super String}.
*/
static WildcardType supertypeOf(Type bound) {
Type[] lowerBounds;
if (bound instanceof WildcardType type) {
lowerBounds = type.getLowerBounds();
} else {
lowerBounds = new Type[] {bound};
}
return new Util.WildcardTypeImpl(new Type[] {Object.class}, lowerBounds);
}
/**
* Returns the element type of this collection type.
*
* @throws IllegalArgumentException if this type is not a collection.
*/
static Type collectionElementType(Type context) {
Type collectionType = supertype(context, Collection.class, Collection.class);
if (collectionType instanceof WildcardType type) {
collectionType = type.getUpperBounds()[0];
}
if (collectionType instanceof ParameterizedType type) {
return type.getActualTypeArguments()[0];
}
return Object.class;
}
/** Returns true if {@code a} and {@code b} are equal. */
static boolean equals(Type a, Type b) {
if (a == b) {
return true; // Also handles (a == null && b == null).
} else if (a instanceof Class class1) {
if (b instanceof GenericArrayType type) {
return equals(
class1.getComponentType(), type.getGenericComponentType());
}
return a.equals(b); // Class already specifies equals().
} else if (a instanceof ParameterizedType pa) {
if (!(b instanceof ParameterizedType)) return false;
final ParameterizedType pb = (ParameterizedType) b;
final Type[] aTypeArguments =
pa instanceof Util.ParameterizedTypeImpl pti
? pti.typeArguments
: pa.getActualTypeArguments();
final Type[] bTypeArguments =
pb instanceof Util.ParameterizedTypeImpl pti
? pti.typeArguments
: pb.getActualTypeArguments();
return equals(pa.getOwnerType(), pb.getOwnerType())
&& pa.getRawType().equals(pb.getRawType())
&& Arrays.equals(aTypeArguments, bTypeArguments);
} else if (a instanceof GenericArrayType ga) {
if (b instanceof Class class1) {
return equals(
class1.getComponentType(), ((GenericArrayType) a).getGenericComponentType());
}
if (!(b instanceof GenericArrayType)) return false;
final GenericArrayType gb = (GenericArrayType) b;
return equals(ga.getGenericComponentType(), gb.getGenericComponentType());
} else if (a instanceof WildcardType wa) {
if (!(b instanceof WildcardType)) return false;
final WildcardType wb = (WildcardType) b;
return Arrays.equals(wa.getUpperBounds(), wb.getUpperBounds())
&& Arrays.equals(wa.getLowerBounds(), wb.getLowerBounds());
} else if (a instanceof TypeVariable va) {
if (!(b instanceof TypeVariable)) return false;
final TypeVariable> vb = (TypeVariable>) b;
return va.getGenericDeclaration() == vb.getGenericDeclaration()
&& va.getName().equals(vb.getName());
} else {
// This isn't a supported type.
return false;
}
}
/**
* Returns a two element array containing this map's key and value types in positions 0 and 1
* respectively.
*/
static Type mapValueType(Type context, Class> contextRawType) {
// Work around a problem with the declaration of java.util.Properties. That class should extend
// Hashtable, but it's declared to extend Hashtable
© 2015 - 2025 Weber Informatics LLC | Privacy Policy