/*
* Copyright 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.gradle.model.internal.type;
import com.google.common.collect.ImmutableList;
import com.google.common.reflect.TypeToken;
import net.jcip.annotations.ThreadSafe;
import org.gradle.api.Nullable;
import org.gradle.internal.Cast;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.Collections;
import java.util.List;
/**
* A type token, representing a resolved type.
* <p>
* Importantly, instances do not hold strong references to class objects.
* <p>
* Construct a type via one of the public static methods, or by creating an AIC…
* <pre>{@code
* ModelType<List<String>> type = new ModelType<List<String>>() {};
* }</pre>
*/
@ThreadSafe
public abstract class ModelType<T> {
public static final ModelType<Object> UNTYPED = ModelType.of(Object.class);
private final TypeWrapper wrapper;
private ModelType(TypeWrapper wrapper) {
this.wrapper = wrapper;
}
protected ModelType() {
this.wrapper = wrap(new TypeToken<T>(getClass()) {
}.getType());
}
public static <T> ModelType<T> of(Class<T> clazz) {
return new Simple<T>(clazz);
}
public static <T> ModelType<T> returnType(Method method) {
return new Simple<T>(method.getGenericReturnType());
}
public static <T> ModelType<T> declaringType(Method method) {
return new Simple<T>(method.getDeclaringClass());
}
@Nullable
public static <T> ModelType<T> paramType(Method method, int i) {
Type[] parameterTypes = method.getGenericParameterTypes();
if (i < parameterTypes.length) {
return new Simple<T>(parameterTypes[i]);
} else {
return null;
}
}
public static <T> ModelType<T> typeOf(T instance) {
// TODO: should validate that clazz is of a non parameterized type
@SuppressWarnings("unchecked") Class<T> clazz = (Class<T>) instance.getClass();
return of(clazz);
}
public static ModelType<?> of(Type type) {
return Simple.typed(type);
}
/**
* Returns true if this type represents a class.
*/
public boolean isClass() {
return wrapper instanceof ClassTypeWrapper;
}
public Class<? super T> getRawClass() {
return Cast.uncheckedCast(wrapper.getRawClass());
}
public Class<T> getConcreteClass() {
return Cast.uncheckedCast(wrapper.getRawClass());
}
public boolean isRawClassOfParameterizedType() {
return wrapper instanceof ClassTypeWrapper && ((ClassTypeWrapper) wrapper).unwrap().getTypeParameters().length > 0;
}
public static ModelType<Object> untyped() {
return UNTYPED;
}
public boolean isParameterized() {
return wrapper instanceof ParameterizedTypeWrapper;
}
public ModelType<?> getRawType() {
return Simple.typed(((ParameterizedTypeWrapper) wrapper).getRawType());
}
public ModelType<?> withArguments(List<ModelType<?>> types) {
return Simple.typed(((ParameterizedTypeWrapper) wrapper).substituteAll(toWrappers(types)));
}
public boolean isGenericArray() {
return wrapper instanceof GenericArrayTypeWrapper;
}
public ModelType<?> getComponentType() {
return Simple.typed(((GenericArrayTypeWrapper) wrapper).getComponentType());
}
public List<ModelType<?>> getTypeVariables() {
if (isParameterized()) {
TypeWrapper[] typeArguments = ((ParameterizedTypeWrapper) wrapper).getActualTypeArguments();
ImmutableList.Builder<ModelType<?>> builder = ImmutableList.builder();
for (TypeWrapper typeArgument : typeArguments) {
builder.add(Simple.typed(typeArgument));
}
return builder.build();
} else {
return Collections.emptyList();
}
}
/**
* Casts this {@code ModelType} object to represent a subclass of the class
* represented by the specified class object. Checks that the cast
* is valid, and throws a {@code ClassCastException} if it is not. If
* this method succeeds, it always returns a reference to this {@code ModelType} object.
*
* @throws ClassCastException if this cannot be cast as the subtype of the given type.
* @throws IllegalStateException if this is a wildcard.
* @throws IllegalArgumentException if the given type is a wildcard.
*/
public <U> ModelType<? extends U> asSubtype(ModelType<U> modelType) {
if (isWildcard()) {
throw new IllegalStateException(this + " is a wildcard type");
}
if (modelType.isWildcard()) {
throw new IllegalArgumentException(modelType + " is a wildcard type");
}
if (modelType.getRawClass().isAssignableFrom(getRawClass())) {
return Cast.uncheckedCast(this);
} else {
throw new ClassCastException(String.format("'%s' cannot be cast as a subtype of '%s'", this, modelType));
}
}
public boolean isAssignableFrom(ModelType<?> modelType) {
return modelType == this || wrapper.isAssignableFrom(modelType.wrapper);
}
public boolean isAnnotationPresent(Class<? extends Annotation> annotation) {
return getRawClass().isAnnotationPresent(annotation);
}
public boolean isWildcard() {
return getWildcardType() != null;
}
@Nullable
public ModelType<?> getUpperBound() {
WildcardWrapper wildcardType = getWildcardType();
if (wildcardType == null) {
return null;
} else {
ModelType<?> upperBound = Simple.typed(wildcardType.getUpperBound());
if (upperBound.equals(UNTYPED)) {
return null;
}
return upperBound;
}
}
@Nullable
public ModelType<?> getLowerBound() {
WildcardWrapper wildcardType = getWildcardType();
if (wildcardType == null) {
return null;
} else {
TypeWrapper lowerBound = wildcardType.getLowerBound();
if (lowerBound == null) {
return null;
}
return Simple.typed(lowerBound);
}
}
private WildcardWrapper getWildcardType() {
if (wrapper instanceof WildcardWrapper) {
return (WildcardWrapper) wrapper;
}
return null;
}
public boolean isHasWildcardTypeVariables() {
if (isWildcard()) {
return true;
} else if (isParameterized()) {
for (ModelType<?> typeVariable : getTypeVariables()) {
if (typeVariable.isHasWildcardTypeVariables()) {
return true;
}
}
}
return false;
}
public List<Class<?>> getAllClasses() {
ImmutableList.Builder<Class<?>> builder = ImmutableList.builder();
wrapper.collectClasses(builder);
return builder.build();
}
public String getName() {
return wrapper.getRepresentation(true);
}
/**
* Returns a human-readable name for the type.
*/
public String getDisplayName() {
return wrapper.getRepresentation(false);
}
public String toString() {
return wrapper.getRepresentation(true);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ModelType)) {
return false;
}
ModelType<?> modelType = (ModelType<?>) o;
return wrapper.equals(modelType.wrapper);
}
@Override
public int hashCode() {
return wrapper.hashCode();
}
abstract public static class Builder<T> {
private ParameterizedTypeWrapper wrapper;
public Builder() {
wrapper = (ParameterizedTypeWrapper) wrap(new TypeToken<T>(getClass()) {
}.getType());
}
@SuppressWarnings("unchecked")
public <I> Builder<T> where(Parameter<I> parameter, ModelType<I> type) {
wrapper = wrapper.substitute(parameter.typeVariable, type.wrapper);
return this;
}
public ModelType<T> build() {
return Simple.typed(wrapper);
}
}
@SuppressWarnings("UnusedDeclaration")
abstract public static class Parameter<T> {
private final TypeVariable<?> typeVariable;
public Parameter() {
Type type = new TypeToken<T>(getClass()) {
}.getType();
if (type instanceof TypeVariable<?>) {
this.typeVariable = (TypeVariable<?>) type;
} else {
throw new IllegalStateException("T for Parameter<T> MUST be a type variable");
}
}
}
private static final TypeWrapper[] EMPTY_TYPE_WRAPPER_ARRAY = new TypeWrapper[0];
@Nullable
private static TypeWrapper wrap(Type type) {
if (type == null) {
return null;
} else if (type instanceof Class) {
return new ClassTypeWrapper((Class<?>) type);
} else if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
return new ParameterizedTypeWrapper(
toWrappers(parameterizedType.getActualTypeArguments()),
(ClassTypeWrapper) wrap(parameterizedType.getRawType()),
wrap(parameterizedType.getOwnerType())
);
} else if (type instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) type;
return new WildcardTypeWrapper(
toWrappers(wildcardType.getUpperBounds()),
toWrappers(wildcardType.getLowerBounds()),
type.hashCode()
);
} else if (type instanceof TypeVariable) {
TypeVariable<?> typeVariable = (TypeVariable<?>) type;
return new TypeVariableTypeWrapper(
typeVariable.getName(),
toWrappers(typeVariable.getBounds()),
type.hashCode()
);
} else if (type instanceof GenericArrayType) {
GenericArrayType genericArrayType = (GenericArrayType) type;
return new GenericArrayTypeWrapper(wrap(genericArrayType.getGenericComponentType()), type.hashCode());
} else {
throw new IllegalArgumentException("cannot wrap type of type " + type.getClass());
}
}
static TypeWrapper[] toWrappers(Type[] types) {
if (types.length == 0) {
return EMPTY_TYPE_WRAPPER_ARRAY;
} else {
TypeWrapper[] wrappers = new TypeWrapper[types.length];
int i = 0;
for (Type type : types) {
wrappers[i++] = wrap(type);
}
return wrappers;
}
}
static TypeWrapper[] toWrappers(List<ModelType<?>> types) {
if (types.isEmpty()) {
return EMPTY_TYPE_WRAPPER_ARRAY;
} else {
TypeWrapper[] wrappers = new TypeWrapper[types.size()];
int i = 0;
for (ModelType<?> type : types) {
wrappers[i++] = type.wrapper;
}
return wrappers;
}
}
private static class Simple<T> extends ModelType<T> {
public static <T> ModelType<T> typed(Type type) {
return new Simple<T>(type);
}
public static <T> ModelType<T> typed(TypeWrapper wrapper) {
return new Simple<T>(wrapper);
}
public Simple(Type type) {
super(wrap(type));
}
public Simple(TypeWrapper type) {
super(type);
}
}
}