All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.avaje.validation.core.Util Maven / Gradle / Ivy

Go to download

validator for annotated pojos using constraint annotations and source code generation

There is a newer version: 2.9
Show newest version
/*
 * 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 annotations, Class 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 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.
    if (context == Properties.class) {
      return String.class;
    }
    final Type mapType = supertype(context, contextRawType, Map.class);
    if (mapType instanceof ParameterizedType mapParameterizedType) {
      return mapParameterizedType.getActualTypeArguments()[1];
    }
    return Object.class;
  }

  /**
   * Returns the generic form of {@code supertype}. For example, if this is {@code
   * ArrayList}, this returns {@code Iterable} given the input {@code
   * Iterable.class}.
   *
   * @param supertype a superclass of, or interface implemented by, this.
   */
  static Type supertype(Type context, Class contextRawType, Class supertype) {
    if (!supertype.isAssignableFrom(contextRawType)) throw new IllegalArgumentException();
    return resolve(context, contextRawType, genericSupertype(context, contextRawType, supertype));
  }

  /**
   * Returns the element type of {@code type} if it is an array type, or null if it is not an array
   * type.
   */
  static Type arrayComponentType(Type type) {
    if (type instanceof GenericArrayType arrayType) {
      return arrayType.getGenericComponentType();
    } else if (type instanceof Class class1) {
      return class1.getComponentType();
    } else {
      return null;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy