blob: 3dcea8a0e9d209a47e8ec5618f6b2c4342d145e8 [file] [log] [blame]
package com.fasterxml.jackson.databind.type;
import java.lang.reflect.*;
import java.util.*;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* Helper class used for resolving type parameters for given class
*/
public class TypeBindings
implements java.io.Serializable
{
private static final long serialVersionUID = 1L;
private final static String[] NO_STRINGS = new String[0];
private final static JavaType[] NO_TYPES = new JavaType[0];
private final static TypeBindings EMPTY = new TypeBindings(NO_STRINGS, NO_TYPES, null);
// // // Pre-resolved instances for minor optimizations
// // // Actual member information
/**
* Array of type (type variable) names.
*/
private final String[] _names;
/**
* Types matching names
*/
private final JavaType[] _types;
/**
* Names of potentially unresolved type variables.
*
* @since 2.3
*/
private final String[] _unboundVariables;
private final int _hashCode;
/*
/**********************************************************************
/* Construction
/**********************************************************************
*/
private TypeBindings(String[] names, JavaType[] types, String[] uvars)
{
_names = (names == null) ? NO_STRINGS : names;
_types = (types == null) ? NO_TYPES : types;
if (_names.length != _types.length) {
throw new IllegalArgumentException("Mismatching names ("+_names.length+"), types ("+_types.length+")");
}
int h = 1;
for (int i = 0, len = _types.length; i < len; ++i) {
h += _types[i].hashCode();
}
_unboundVariables = uvars;
_hashCode = h;
}
public static TypeBindings emptyBindings() {
return EMPTY;
}
// Let's just canonicalize serialized EMPTY back to static instance, if need be
protected Object readResolve() {
if ((_names == null) || (_names.length == 0)) {
return EMPTY;
}
return this;
}
/**
* Factory method for constructing bindings for given class using specified type
* parameters.
*/
public static TypeBindings create(Class<?> erasedType, List<JavaType> typeList)
{
JavaType[] types = (typeList == null || typeList.isEmpty()) ?
NO_TYPES : typeList.toArray(NO_TYPES);
return create(erasedType, types);
}
public static TypeBindings create(Class<?> erasedType, JavaType[] types)
{
if (types == null) {
types = NO_TYPES;
} else switch (types.length) {
case 1:
return create(erasedType, types[0]);
case 2:
return create(erasedType, types[0], types[1]);
}
TypeVariable<?>[] vars = erasedType.getTypeParameters();
String[] names;
if (vars == null || vars.length == 0) {
names = NO_STRINGS;
} else {
int len = vars.length;
names = new String[len];
for (int i = 0; i < len; ++i) {
names[i] = vars[i].getName();
}
}
// Check here to give better error message
if (names.length != types.length) {
throw new IllegalArgumentException("Cannot create TypeBindings for class "+erasedType.getName()
+" with "+types.length+" type parameter"
+((types.length == 1) ? "" : "s")+": class expects "+names.length);
}
return new TypeBindings(names, types, null);
}
public static TypeBindings create(Class<?> erasedType, JavaType typeArg1)
{
// 30-Oct-2015, tatu: Minor optimization for relatively common cases
TypeVariable<?>[] vars = TypeParamStash.paramsFor1(erasedType);
int varLen = (vars == null) ? 0 : vars.length;
if (varLen != 1) {
throw new IllegalArgumentException("Cannot create TypeBindings for class "+erasedType.getName()
+" with 1 type parameter: class expects "+varLen);
}
return new TypeBindings(new String[] { vars[0].getName() },
new JavaType[] { typeArg1 }, null);
}
public static TypeBindings create(Class<?> erasedType, JavaType typeArg1, JavaType typeArg2)
{
// 30-Oct-2015, tatu: Minor optimization for relatively common cases
TypeVariable<?>[] vars = TypeParamStash.paramsFor2(erasedType);
int varLen = (vars == null) ? 0 : vars.length;
if (varLen != 2) {
throw new IllegalArgumentException("Cannot create TypeBindings for class "+erasedType.getName()
+" with 2 type parameters: class expects "+varLen);
}
return new TypeBindings(new String[] { vars[0].getName(), vars[1].getName() },
new JavaType[] { typeArg1, typeArg2 }, null);
}
/**
* Alternate factory method that may be called if it is possible that type
* does or does not require type parameters; this is mostly useful for
* collection- and map-like types.
*/
public static TypeBindings createIfNeeded(Class<?> erasedType, JavaType typeArg1)
{
TypeVariable<?>[] vars = erasedType.getTypeParameters();
int varLen = (vars == null) ? 0 : vars.length;
if (varLen == 0) {
return EMPTY;
}
if (varLen != 1) {
throw new IllegalArgumentException("Cannot create TypeBindings for class "+erasedType.getName()
+" with 1 type parameter: class expects "+varLen);
}
return new TypeBindings(new String[] { vars[0].getName() },
new JavaType[] { typeArg1 }, null);
}
/**
* Alternate factory method that may be called if it is possible that type
* does or does not require type parameters; this is mostly useful for
* collection- and map-like types.
*/
public static TypeBindings createIfNeeded(Class<?> erasedType, JavaType[] types)
{
TypeVariable<?>[] vars = erasedType.getTypeParameters();
if (vars == null || vars.length == 0) {
return EMPTY;
}
if (types == null) {
types = NO_TYPES;
}
int len = vars.length;
String[] names = new String[len];
for (int i = 0; i < len; ++i) {
names[i] = vars[i].getName();
}
// Check here to give better error message
if (names.length != types.length) {
throw new IllegalArgumentException("Cannot create TypeBindings for class "+erasedType.getName()
+" with "+types.length+" type parameter"
+((types.length == 1) ? "" : "s")+": class expects "+names.length);
}
return new TypeBindings(names, types, null);
}
/**
* Method for creating an instance that has same bindings as this object,
* plus an indicator for additional type variable that may be unbound within
* this context; this is needed to resolve recursive self-references.
*/
public TypeBindings withUnboundVariable(String name)
{
int len = (_unboundVariables == null) ? 0 : _unboundVariables.length;
String[] names = (len == 0)
? new String[1] : Arrays.copyOf(_unboundVariables, len+1);
names[len] = name;
return new TypeBindings(_names, _types, names);
}
/*
/**********************************************************************
/* Accessors
/**********************************************************************
*/
/**
* Find type bound to specified name, if there is one; returns bound type if so, null if not.
*/
public JavaType findBoundType(String name)
{
for (int i = 0, len = _names.length; i < len; ++i) {
if (name.equals(_names[i])) {
JavaType t = _types[i];
if (t instanceof ResolvedRecursiveType) {
ResolvedRecursiveType rrt = (ResolvedRecursiveType) t;
JavaType t2 = rrt.getSelfReferencedType();
if (t2 != null) {
t = t2;
} else {
/* 25-Feb-2016, tatu: Looks like a potential problem, but alas
* we have a test where this should NOT fail and things... seem
* to work. So be it.
*/
/*
throw new IllegalStateException(String.format
("Unresolved ResolvedRecursiveType for parameter '%s' (index #%d; erased type %s)",
name, i, t.getRawClass()));
*/
}
}
return t;
}
}
return null;
}
public boolean isEmpty() {
return (_types.length == 0);
}
/**
* Returns number of bindings contained
*/
public int size() {
return _types.length;
}
public String getBoundName(int index)
{
if (index < 0 || index >= _names.length) {
return null;
}
return _names[index];
}
public JavaType getBoundType(int index)
{
if (index < 0 || index >= _types.length) {
return null;
}
return _types[index];
}
/**
* Accessor for getting bound types in declaration order
*/
public List<JavaType> getTypeParameters()
{
if (_types.length == 0) {
return Collections.emptyList();
}
return Arrays.asList(_types);
}
/**
* @since 2.3
*/
public boolean hasUnbound(String name) {
if (_unboundVariables != null) {
for (int i = _unboundVariables.length; --i >= 0; ) {
if (name.equals(_unboundVariables[i])) {
return true;
}
}
}
return false;
}
/**
* Factory method that will create an object that can be used as a key for
* caching purposes by {@link TypeFactory}
*
* @since 2.8
*/
public Object asKey(Class<?> rawBase) {
// safe to pass _types array without copy since it is not exposed via
// any access, nor modified by this class
return new AsKey(rawBase, _types, _hashCode);
}
/*
/**********************************************************************
/* Standard methods
/**********************************************************************
*/
@Override public String toString()
{
if (_types.length == 0) {
return "<>";
}
StringBuilder sb = new StringBuilder();
sb.append('<');
for (int i = 0, len = _types.length; i < len; ++i) {
if (i > 0) {
sb.append(',');
}
// sb = _types[i].appendBriefDescription(sb);
String sig = _types[i].getGenericSignature();
sb.append(sig);
}
sb.append('>');
return sb.toString();
}
@Override public int hashCode() { return _hashCode; }
@Override public boolean equals(Object o)
{
if (o == this) return true;
if (!ClassUtil.hasClass(o, getClass())) {
return false;
}
TypeBindings other = (TypeBindings) o;
int len = _types.length;
if (len != other.size()) {
return false;
}
JavaType[] otherTypes = other._types;
for (int i = 0; i < len; ++i) {
if (!otherTypes[i].equals(_types[i])) {
return false;
}
}
return true;
}
/*
/**********************************************************************
/* Package accessible methods
/**********************************************************************
*/
protected JavaType[] typeParameterArray() {
return _types;
}
/*
/**********************************************************************
/* Helper classes
/**********************************************************************
*/
// 30-Oct-2015, tatu: Surprising, but looks like type parameters access can be bit of
// a hot spot. So avoid for a small number of common generic types. Note that we do
// need both common abstract types and concrete ones; latter for specialization
/**
* Helper class that contains simple logic for avoiding repeated lookups via
* {@link Class#getTypeParameters()} as that can be a performance issue for
* some use cases (wasteful, usually one-off or not reusing mapper).
* Partly isolated to avoid initialization for cases where no generic types are
* used.
*/
static class TypeParamStash {
private final static TypeVariable<?>[] VARS_ABSTRACT_LIST = AbstractList.class.getTypeParameters();
private final static TypeVariable<?>[] VARS_COLLECTION = Collection.class.getTypeParameters();
private final static TypeVariable<?>[] VARS_ITERABLE = Iterable.class.getTypeParameters();
private final static TypeVariable<?>[] VARS_LIST = List.class.getTypeParameters();
private final static TypeVariable<?>[] VARS_ARRAY_LIST = ArrayList.class.getTypeParameters();
private final static TypeVariable<?>[] VARS_MAP = Map.class.getTypeParameters();
private final static TypeVariable<?>[] VARS_HASH_MAP = HashMap.class.getTypeParameters();
private final static TypeVariable<?>[] VARS_LINKED_HASH_MAP = LinkedHashMap.class.getTypeParameters();
public static TypeVariable<?>[] paramsFor1(Class<?> erasedType)
{
if (erasedType == Collection.class) {
return VARS_COLLECTION;
}
if (erasedType == List.class) {
return VARS_LIST;
}
if (erasedType == ArrayList.class) {
return VARS_ARRAY_LIST;
}
if (erasedType == AbstractList.class) {
return VARS_ABSTRACT_LIST;
}
if (erasedType == Iterable.class) {
return VARS_ITERABLE;
}
return erasedType.getTypeParameters();
}
public static TypeVariable<?>[] paramsFor2(Class<?> erasedType)
{
if (erasedType == Map.class) {
return VARS_MAP;
}
if (erasedType == HashMap.class) {
return VARS_HASH_MAP;
}
if (erasedType == LinkedHashMap.class) {
return VARS_LINKED_HASH_MAP;
}
return erasedType.getTypeParameters();
}
}
/**
* Helper type used to allow caching of generic types
*
* @since 2.8
*/
final static class AsKey {
private final Class<?> _raw;
private final JavaType[] _params;
private final int _hash;
public AsKey(Class<?> raw, JavaType[] params, int hash) {
_raw = raw ;
_params = params;
_hash = hash;
}
@Override
public int hashCode() { return _hash; }
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (o == null) return false;
if (o.getClass() != getClass()) return false;
AsKey other = (AsKey) o;
if ((_hash == other._hash) && (_raw == other._raw)) {
final JavaType[] otherParams = other._params;
final int len = _params.length;
if (len == otherParams.length) {
for (int i = 0; i < len; ++i) {
if (!_params[i].equals(otherParams[i])) {
return false;
}
}
return true;
}
}
return false;
}
@Override
public String toString() {
return _raw.getName()+"<>";
}
}
}