/*
* Copyright 2017 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.api.reflect;
import com.google.common.base.Function;
import org.gradle.api.Incubating;
import org.gradle.api.Nullable;
import org.gradle.internal.Cast;
import org.gradle.model.internal.type.ModelType;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import static com.google.common.collect.ImmutableList.copyOf;
import static com.google.common.collect.Iterables.transform;
import static java.util.Arrays.asList;
/**
* Provides a way to preserve high-fidelity {@link Type} information on generic types.
*
* Capture a generic type with an anonymous subclass. For example: <pre> {@code
* new TypeOf<NamedDomainObjectContainer<ArtifactRepository>>() {}}</pre>
*
* @param <T> Parameterized type
* @since 3.5
*/
@Incubating
public abstract class TypeOf<T> {
/**
* Creates an instance of {@literal TypeOf} for the given {@literal Class}.
*
* @param type the {@literal Class}
* @param <T> the parameterized type of the given {@literal Class}
* @return the {@literal TypeOf} that captures the generic type of the given {@literal Class}
*/
public static <T> TypeOf<T> typeOf(Class<T> type) {
return new TypeOf<T>(
ModelType.of(typeWhichCannotBeNull(type))) {
};
}
/**
* Creates an instance of {@literal TypeOf} for the given {@literal Type}.
*
* @param type the {@literal Type}
* @param <T> the parameterized type of the given {@literal Type}
* @return the {@literal TypeOf} that captures the generic type of the given {@literal Type}
*/
public static <T> TypeOf<T> typeOf(Type type) {
return new TypeOf<T>(
Cast.<ModelType<T>>uncheckedCast(
ModelType.of(typeWhichCannotBeNull(type)))) {
};
}
/**
* Constructs a new parameterized type from a given parameterized type definition and an array of type arguments.
*
* For example, {@code parameterizedTypeOf(new TypeOf<List<?>>() {}, new TypeOf<String>() {})} is equivalent to
* {@code new TypeOf<List<String>>() {}}, except both the parameterized type definition and type arguments can be dynamically computed.
*
* @param parameterizedType the parameterized type from which to construct the new parameterized type
* @param typeArguments the arguments with which to construct the new parameterized type
* @see #isParameterized()
*/
public static TypeOf<?> parameterizedTypeOf(TypeOf<?> parameterizedType, TypeOf<?>... typeArguments) {
ModelType<?> parameterizedModelType = parameterizedType.type;
if (!parameterizedModelType.isParameterized()) {
throw new IllegalArgumentException("Expecting a parameterized type, got: " + parameterizedType + ".");
}
return typeOf(parameterizedModelType.withArguments(modelTypeListFrom(typeArguments)));
}
private final ModelType<T> type;
private TypeOf(ModelType<T> type) {
this.type = type;
}
protected TypeOf() {
this.type = captureTypeArgument();
}
/**
* Queries whether this object represents a simple (non-composite) type, not an array and not a generic type.
*
* @return true if this object represents a simple type.
*/
public boolean isSimple() {
return type.isClass()
&& !rawClass().isArray();
}
/**
* Queries whether this object represents a synthetic type as defined by {@link Class#isSynthetic()}.
*
* @return true if this object represents a synthetic type.
*/
public boolean isSynthetic() {
return rawClass().isSynthetic();
}
/**
* Queries whether the type represented by this object is public ({@link java.lang.reflect.Modifier#isPublic(int)}).
*
* @see java.lang.reflect.Modifier#isPublic(int)
* @see Class#getModifiers()
*/
public boolean isPublic() {
return Modifier.isPublic(getModifiers());
}
private int getModifiers() {
return rawClass().getModifiers();
}
/**
* Queries whether this object represents an array, generic or otherwise.
*
* @return true if this object represents an array.
* @see #getComponentType()
*/
public boolean isArray() {
return type.isGenericArray()
|| (type.isClass() && rawClass().isArray());
}
/**
* Returns the component type of the array type this object represents.
*
* @return null if this object does not represent an array type.
* @see #isArray()
*/
@Nullable
public TypeOf<?> getComponentType() {
return type.isGenericArray()
? typeOf(type.getComponentType())
: nullableTypeOf(rawClass().getComponentType());
}
/**
* Queries whether this object represents a parameterized type.
*
* @return true if this object represents a parameterized type.
* @see #getParameterizedTypeDefinition()
* @see #getActualTypeArguments()
*/
public boolean isParameterized() {
return type.isParameterized();
}
/**
* Returns an object that represents the type from which this parameterized type was constructed.
*
* @see #isParameterized()
*/
public TypeOf<?> getParameterizedTypeDefinition() {
return typeOf(type.getRawType());
}
/**
* Returns the list of type arguments used in the construction of this parameterized type.
*
* @see #isParameterized()
*/
public List<TypeOf<?>> getActualTypeArguments() {
return typeOfListFrom(type.getTypeVariables());
}
/**
* Queries whether this object represents a wildcard type expression, such as
* {@code ?}, {@code ? extends Number}, or {@code ? super Integer}.
*
* @return true if this object represents a wildcard type expression.
* @see #getUpperBound()
*/
public boolean isWildcard() {
return type.isWildcard();
}
/**
* Returns the first declared upper-bound of the wildcard type expression represented by this type.
*
* @return null if no upper-bound has been explicitly declared.
*/
@Nullable
public TypeOf<?> getUpperBound() {
return nullableTypeOf(type.getUpperBound());
}
/**
* Is this type assignable from the given type?
*
* @param type the given type
* @return {@literal true} if this type is assignable from the given type, {@literal false otherwise}
*/
public final boolean isAssignableFrom(TypeOf<?> type) {
return this.type.isAssignableFrom(type.type);
}
/**
* Is this type assignable from the given type?
*
* @param type the given type
* @return {@literal true} if this type is assignable from the given type, {@literal false otherwise}
*/
public final boolean isAssignableFrom(Type type) {
return this.type.isAssignableFrom(ModelType.of(type));
}
/**
* Simple name.
*
* @return this type's simple name
*/
public String getSimpleName() {
return type.getDisplayName();
}
@Override
public final String toString() {
return type.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof TypeOf)) {
return false;
}
TypeOf<?> typeOf = (TypeOf<?>) o;
return type.equals(typeOf.type);
}
@Override
public int hashCode() {
return type.hashCode();
}
private ModelType<T> captureTypeArgument() {
Type genericSuperclass = getClass().getGenericSuperclass();
Type type = genericSuperclass instanceof ParameterizedType
? ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0]
: Object.class;
return Cast.uncheckedCast(ModelType.of(type));
}
private Class<? super T> rawClass() {
return type.getRawClass();
}
private static <T> T typeWhichCannotBeNull(T type) {
if (type == null) {
throw new IllegalArgumentException("type cannot be null.");
}
return type;
}
private static List<ModelType<?>> modelTypeListFrom(TypeOf<?>[] typeOfs) {
return map(asList(typeOfs), new Function<TypeOf<?>, ModelType<?>>() {
@Override
public ModelType<?> apply(TypeOf<?> it) {
return it.type;
}
});
}
private static List<TypeOf<?>> typeOfListFrom(List<ModelType<?>> modelTypes) {
return map(modelTypes, new Function<ModelType<?>, TypeOf<?>>() {
@Override
public TypeOf<?> apply(ModelType<?> it) {
return typeOf(it);
}
});
}
private static <U> TypeOf<U> typeOf(ModelType<U> componentType) {
return new TypeOf<U>(componentType) {
};
}
private TypeOf<?> nullableTypeOf(Class<?> type) {
return type != null
? typeOf(type)
: null;
}
private TypeOf<?> nullableTypeOf(ModelType<?> type) {
return type != null
? typeOf(type)
: null;
}
private static <T, U> List<U> map(Iterable<T> iterable, Function<T, U> function) {
return copyOf(transform(iterable, function));
}
}