Package org.springframework.data.util

Source Code of org.springframework.data.util.TypeDiscoverer

/*
* Copyright 2011-2014 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 static org.springframework.util.ObjectUtils.*;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.beans.BeanUtils;
import org.springframework.core.GenericTypeResolver;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

/**
* Basic {@link TypeDiscoverer} that contains basic functionality to discover property types.
*
* @author Oliver Gierke
*/
class TypeDiscoverer<S> implements TypeInformation<S> {

  private final Type type;
  private final Map<TypeVariable<?>, Type> typeVariableMap;
  private final Map<String, TypeInformation<?>> fieldTypes = new ConcurrentHashMap<String, TypeInformation<?>>();

  private boolean componentTypeResolved = false;
  private TypeInformation<?> componentType;

  private boolean valueTypeResolved = false;
  private TypeInformation<?> valueType;

  private Class<S> resolvedType;

  /**
   * Creates a ne {@link TypeDiscoverer} for the given type, type variable map and parent.
   *
   * @param type must not be {@literal null}.
   * @param typeVariableMap must not be {@literal null}.
   */
  protected TypeDiscoverer(Type type, Map<TypeVariable<?>, Type> typeVariableMap) {

    Assert.notNull(type);
    Assert.notNull(typeVariableMap);

    this.type = type;
    this.typeVariableMap = typeVariableMap;
  }

  /**
   * Returns the type variable map.
   *
   * @return
   */
  protected Map<TypeVariable<?>, Type> getTypeVariableMap() {
    return typeVariableMap;
  }

  /**
   * Creates {@link TypeInformation} for the given {@link Type}.
   *
   * @param fieldType
   * @return
   */
  @SuppressWarnings({ "rawtypes", "unchecked", "deprecation" })
  protected TypeInformation<?> createInfo(Type fieldType) {

    if (fieldType.equals(this.type)) {
      return this;
    }

    if (fieldType instanceof Class) {
      return new ClassTypeInformation((Class<?>) fieldType);
    }

    Map<TypeVariable, Type> variableMap = GenericTypeResolver.getTypeVariableMap(resolveType(fieldType));

    if (fieldType instanceof ParameterizedType) {
      ParameterizedType parameterizedType = (ParameterizedType) fieldType;
      return new ParameterizedTypeInformation(parameterizedType, this, variableMap);
    }

    if (fieldType instanceof TypeVariable) {
      TypeVariable<?> variable = (TypeVariable<?>) fieldType;
      return new TypeVariableTypeInformation(variable, type, this, variableMap);
    }

    if (fieldType instanceof GenericArrayType) {
      return new GenericArrayTypeInformation((GenericArrayType) fieldType, this, variableMap);
    }

    if (fieldType instanceof WildcardType) {

      WildcardType wildcardType = (WildcardType) fieldType;
      Type[] bounds = wildcardType.getLowerBounds();

      if (bounds.length > 0) {
        return createInfo(bounds[0]);
      }

      bounds = wildcardType.getUpperBounds();

      if (bounds.length > 0) {
        return createInfo(bounds[0]);
      }
    }

    throw new IllegalArgumentException();
  }

  /**
   * Resolves the given type into a plain {@link Class}.
   *
   * @param type
   * @return
   */
  @SuppressWarnings({ "unchecked", "rawtypes", "deprecation" })
  protected Class<S> resolveType(Type type) {

    Map<TypeVariable, Type> map = new HashMap<TypeVariable, Type>();
    map.putAll(getTypeVariableMap());

    return (Class<S>) GenericTypeResolver.resolveType(type, map);
  }

  /*
   * (non-Javadoc)
   * @see org.springframework.data.util.TypeInformation#getParameterTypes(java.lang.reflect.Constructor)
   */
  public List<TypeInformation<?>> getParameterTypes(Constructor<?> constructor) {

    Assert.notNull(constructor);
    List<TypeInformation<?>> result = new ArrayList<TypeInformation<?>>();

    for (Type type : constructor.getGenericParameterTypes()) {
      result.add(createInfo(type));
    }

    return result;
  }

  /*
   * (non-Javadoc)
   * @see org.springframework.data.util.TypeInformation#getProperty(java.lang.String)
   */
  public TypeInformation<?> getProperty(String fieldname) {

    int separatorIndex = fieldname.indexOf('.');

    if (separatorIndex == -1) {
      if (fieldTypes.containsKey(fieldname)) {
        return fieldTypes.get(fieldname);
      }

      TypeInformation<?> propertyInformation = getPropertyInformation(fieldname);
      if (propertyInformation != null) {
        fieldTypes.put(fieldname, propertyInformation);
      }
      return propertyInformation;
    }

    String head = fieldname.substring(0, separatorIndex);
    TypeInformation<?> info = getProperty(head);
    return info == null ? null : info.getProperty(fieldname.substring(separatorIndex + 1));
  }

  /**
   * Returns the {@link TypeInformation} for the given atomic field. Will inspect fields first and return the type of a
   * field if available. Otherwise it will fall back to a {@link PropertyDescriptor}.
   *
   * @see #getGenericType(PropertyDescriptor)
   * @param fieldname
   * @return
   */
  private TypeInformation<?> getPropertyInformation(String fieldname) {

    Class<?> type = getType();
    Field field = ReflectionUtils.findField(type, fieldname);

    if (field != null) {
      return createInfo(field.getGenericType());
    }

    PropertyDescriptor descriptor = findPropertyDescriptor(type, fieldname);
    return descriptor == null ? null : createInfo(getGenericType(descriptor));
  }

  /**
   * Finds the {@link PropertyDescriptor} for the property with the given name on the given type.
   *
   * @param type must not be {@literal null}.
   * @param fieldname must not be {@literal null} or empty.
   * @return
   */
  private static PropertyDescriptor findPropertyDescriptor(Class<?> type, String fieldname) {

    PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(type, fieldname);

    if (descriptor != null) {
      return descriptor;
    }

    List<Class<?>> superTypes = new ArrayList<Class<?>>();
    superTypes.addAll(Arrays.asList(type.getInterfaces()));
    superTypes.add(type.getSuperclass());

    for (Class<?> interfaceType : type.getInterfaces()) {
      descriptor = findPropertyDescriptor(interfaceType, fieldname);
      if (descriptor != null) {
        return descriptor;
      }
    }

    return null;
  }

  /**
   * Returns the generic type for the given {@link PropertyDescriptor}. Will inspect its read method followed by the
   * first parameter of the write method.
   *
   * @param descriptor must not be {@literal null}
   * @return
   */
  private static Type getGenericType(PropertyDescriptor descriptor) {

    Method method = descriptor.getReadMethod();

    if (method != null) {
      return method.getGenericReturnType();
    }

    method = descriptor.getWriteMethod();

    if (method == null) {
      return null;
    }

    Type[] parameterTypes = method.getGenericParameterTypes();
    return parameterTypes.length == 0 ? null : parameterTypes[0];
  }

  /*
   * (non-Javadoc)
   * @see org.springframework.data.util.TypeInformation#getType()
   */
  public Class<S> getType() {

    if (resolvedType == null) {
      this.resolvedType = resolveType(type);
    }

    return this.resolvedType;
  }

  /*
   * (non-Javadoc)
   * @see org.springframework.data.util.TypeInformation#getRawTypeInformation()
   */
  @Override
  public ClassTypeInformation<?> getRawTypeInformation() {
    return ClassTypeInformation.from(getType()).getRawTypeInformation();
  }

  /*
   * (non-Javadoc)
   * @see org.springframework.data.util.TypeInformation#getActualType()
   */
  public TypeInformation<?> getActualType() {

    if (isMap()) {
      return getMapValueType();
    }

    if (isCollectionLike()) {
      return getComponentType();
    }

    return this;
  }

  /*
   * (non-Javadoc)
   * @see org.springframework.data.util.TypeInformation#isMap()
   */
  public boolean isMap() {
    return Map.class.isAssignableFrom(getType());
  }

  /*
   * (non-Javadoc)
   * @see org.springframework.data.util.TypeInformation#getMapValueType()
   */
  public TypeInformation<?> getMapValueType() {

    if (!valueTypeResolved) {
      this.valueType = doGetMapValueType();
      this.valueTypeResolved = true;
    }

    return this.valueType;
  }

  protected TypeInformation<?> doGetMapValueType() {

    if (isMap()) {
      return getTypeArgument(Map.class, 1);
    }

    List<TypeInformation<?>> arguments = getTypeArguments();

    if (arguments.size() > 1) {
      return arguments.get(1);
    }

    return null;
  }

  /*
   * (non-Javadoc)
   * @see org.springframework.data.util.TypeInformation#isCollectionLike()
   */
  public boolean isCollectionLike() {

    Class<?> rawType = getType();

    if (rawType.isArray() || Iterable.class.equals(rawType)) {
      return true;
    }

    return Collection.class.isAssignableFrom(rawType);
  }

  /*
   * (non-Javadoc)
   * @see org.springframework.data.util.TypeInformation#getComponentType()
   */
  public final TypeInformation<?> getComponentType() {

    if (!componentTypeResolved) {
      this.componentType = doGetComponentType();
      this.componentTypeResolved = true;
    }

    return this.componentType;
  }

  protected TypeInformation<?> doGetComponentType() {

    Class<S> rawType = getType();

    if (rawType.isArray()) {
      return createInfo(rawType.getComponentType());
    }

    if (isMap()) {
      return getTypeArgument(Map.class, 0);
    }

    if (Iterable.class.isAssignableFrom(rawType)) {
      return getTypeArgument(Iterable.class, 0);
    }

    List<TypeInformation<?>> arguments = getTypeArguments();

    if (arguments.size() > 0) {
      return arguments.get(0);
    }

    return null;
  }

  /*
   * (non-Javadoc)
   * @see org.springframework.data.util.TypeInformation#getReturnType(java.lang.reflect.Method)
   */
  public TypeInformation<?> getReturnType(Method method) {

    Assert.notNull(method);
    return createInfo(method.getGenericReturnType());
  }

  /*
   * (non-Javadoc)
   * @see org.springframework.data.util.TypeInformation#getMethodParameterTypes(java.lang.reflect.Method)
   */
  public List<TypeInformation<?>> getParameterTypes(Method method) {

    Assert.notNull(method);

    Type[] parameterTypes = method.getGenericParameterTypes();
    List<TypeInformation<?>> result = new ArrayList<TypeInformation<?>>(parameterTypes.length);

    for (Type type : parameterTypes) {
      result.add(createInfo(type));
    }

    return result;
  }

  /*
   * (non-Javadoc)
   * @see org.springframework.data.util.TypeInformation#getSuperTypeInformation(java.lang.Class)
   */
  public TypeInformation<?> getSuperTypeInformation(Class<?> superType) {

    Class<?> type = getType();

    if (!superType.isAssignableFrom(type)) {
      return null;
    }

    if (getType().equals(superType)) {
      return this;
    }

    List<Type> candidates = new ArrayList<Type>();

    Type genericSuperclass = type.getGenericSuperclass();
    if (genericSuperclass != null) {
      candidates.add(genericSuperclass);
    }
    candidates.addAll(Arrays.asList(type.getGenericInterfaces()));

    for (Type candidate : candidates) {

      TypeInformation<?> candidateInfo = createInfo(candidate);

      if (superType.equals(candidateInfo.getType())) {
        return candidateInfo;
      } else {
        TypeInformation<?> nestedSuperType = candidateInfo.getSuperTypeInformation(superType);
        if (nestedSuperType != null) {
          return nestedSuperType;
        }
      }
    }

    return null;
  }

  /*
   * (non-Javadoc)
   * @see org.springframework.data.util.TypeInformation#getTypeParameters()
   */
  public List<TypeInformation<?>> getTypeArguments() {
    return Collections.emptyList();
  }

  /* (non-Javadoc)
   * @see org.springframework.data.util.TypeInformation#isAssignableFrom(org.springframework.data.util.TypeInformation)
   */
  public boolean isAssignableFrom(TypeInformation<?> target) {
    return target.getSuperTypeInformation(getType()).equals(this);
  }

  private TypeInformation<?> getTypeArgument(Class<?> bound, int index) {

    Class<?>[] arguments = GenericTypeResolver.resolveTypeArguments(getType(), bound);

    if (arguments == null) {
      return getSuperTypeInformation(bound) instanceof ParameterizedTypeInformation ? ClassTypeInformation.OBJECT
          : null;
    }

    return createInfo(arguments[index]);
  }

  /*
   * (non-Javadoc)
   * @see java.lang.Object#equals(java.lang.Object)
   */
  @Override
  public boolean equals(Object obj) {

    if (obj == this) {
      return true;
    }

    if (obj == null) {
      return false;
    }

    if (!this.getClass().equals(obj.getClass())) {
      return false;
    }

    TypeDiscoverer<?> that = (TypeDiscoverer<?>) obj;

    boolean typeEqual = nullSafeEquals(this.type, that.type);
    boolean typeVariableMapEqual = nullSafeEquals(this.typeVariableMap, that.typeVariableMap);

    return typeEqual && typeVariableMapEqual;
  }

  /*
   * (non-Javadoc)
   * @see java.lang.Object#hashCode()
   */
  @Override
  public int hashCode() {

    int result = 17;

    result += nullSafeHashCode(type);
    result += nullSafeHashCode(typeVariableMap);

    return result;
  }
}
TOP

Related Classes of org.springframework.data.util.TypeDiscoverer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.