/*
* 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 org.gradle.api.Nullable;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
class ParameterizedTypeWrapper implements TypeWrapper {
private final TypeWrapper[] actualTypeArguments;
private final ClassTypeWrapper rawType;
private final TypeWrapper ownerType;
private final int hashCode;
public ParameterizedTypeWrapper(TypeWrapper[] actualTypeArguments, ClassTypeWrapper rawType, @Nullable TypeWrapper ownerType) {
this.actualTypeArguments = actualTypeArguments;
this.rawType = rawType;
this.ownerType = ownerType;
this.hashCode = hashCode(actualTypeArguments, rawType, ownerType);
}
private int hashCode(TypeWrapper[] actualTypeArguments, ClassTypeWrapper rawType, TypeWrapper ownerType) {
int hashCode = rawType.hashCode();
for (TypeWrapper actualTypeArgument : actualTypeArguments) {
hashCode ^= actualTypeArgument.hashCode();
}
if (ownerType != null) {
hashCode ^= ownerType.hashCode();
}
return hashCode;
}
public TypeWrapper getRawType() {
return rawType;
}
@Override
public Class<?> getRawClass() {
return rawType.unwrap();
}
@Override
public boolean isAssignableFrom(TypeWrapper wrapper) {
if (wrapper instanceof ParameterizedTypeWrapper) {
ParameterizedTypeWrapper parameterizedTypeWrapper = (ParameterizedTypeWrapper) wrapper;
if (!rawType.isAssignableFrom(parameterizedTypeWrapper.rawType)) {
return false;
}
for (int i = 0; i < actualTypeArguments.length; i++) {
if (!contains(actualTypeArguments[i], parameterizedTypeWrapper.actualTypeArguments[i])) {
return false;
}
}
return true;
}
if (wrapper instanceof ClassTypeWrapper) {
if (!rawType.equals(wrapper)) {
return false;
}
for (TypeWrapper typeArgument : actualTypeArguments) {
if (!(typeArgument instanceof WildcardWrapper)) {
return false;
}
WildcardWrapper wildcard = (WildcardWrapper) typeArgument;
if (wildcard.getLowerBound() != null || !wildcard.getUpperBound().getRawClass().equals(Object.class)) {
return false;
}
}
return true;
}
return false;
}
public static boolean contains(TypeWrapper type1, TypeWrapper type2) {
if (type1 instanceof WildcardWrapper) {
WildcardWrapper wildcardType1 = (WildcardWrapper) type1;
TypeWrapper bound1 = wildcardType1.getLowerBound();
if (bound1 != null) {
// type1 = ? super T
TypeWrapper bound2;
if (type2 instanceof WildcardWrapper) {
bound2 = ((WildcardWrapper) type2).getLowerBound();
if (bound2 == null) {
// type2 = ? extends S, never contained
return false;
}
} else {
bound2 = type2;
}
return bound2.isAssignableFrom(bound1);
} else {
// type 1 = ? extends T
bound1 = wildcardType1.getUpperBound();
TypeWrapper bound2;
if (type2 instanceof WildcardWrapper) {
bound2 = ((WildcardWrapper) type2).getUpperBound();
} else {
bound2 = type2;
}
return bound1.isAssignableFrom(bound2);
}
}
return type1.equals(type2);
}
@Override
public void collectClasses(ImmutableList.Builder<Class<?>> builder) {
rawType.collectClasses(builder);
for (TypeWrapper actualTypeArgument : actualTypeArguments) {
actualTypeArgument.collectClasses(builder);
}
}
@Override
public boolean equals(Object o) {
if (o instanceof ParameterizedTypeWrapper) {
ParameterizedTypeWrapper that = (ParameterizedTypeWrapper) o;
if (this == that) {
return true;
} else {
return (ownerType == null ? that.ownerType == null : ownerType.equals(that.ownerType))
&& (rawType == null ? that.rawType == null : rawType.equals(that.rawType))
&& Arrays.equals(actualTypeArguments, that.actualTypeArguments);
}
} else {
return false;
}
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public String toString() {
return getRepresentation(true);
}
@Override
public String getRepresentation(boolean full) {
StringBuilder sb = new StringBuilder();
if (ownerType != null) {
sb.append(ownerType.getRepresentation(full));
sb.append('.');
sb.append(rawType.unwrap().getSimpleName());
} else {
sb.append(rawType.getRepresentation(full));
}
if (actualTypeArguments != null && actualTypeArguments.length > 0) {
sb.append("<");
boolean first = true;
for (TypeWrapper t : actualTypeArguments) {
if (!first) {
sb.append(", ");
}
sb.append(t.getRepresentation(full));
first = false;
}
sb.append(">");
}
return sb.toString();
}
public ParameterizedTypeWrapper substitute(TypeVariable<?> typeVariable, TypeWrapper type) {
TypeWrapper[] newArguments = new TypeWrapper[actualTypeArguments.length];
for (int i = 0; i < newArguments.length; i++) {
TypeWrapper argument = actualTypeArguments[i];
if (argument instanceof TypeVariableTypeWrapper) {
TypeVariableTypeWrapper candidate = (TypeVariableTypeWrapper) argument;
if (candidate.getName().equals(typeVariable.getName())) {
newArguments[i] = type;
continue;
}
}
newArguments[i] = argument;
}
return new ParameterizedTypeWrapper(newArguments, rawType, ownerType);
}
ParameterizedTypeWrapper substituteAll(TypeWrapper[] newArguments) {
if (actualTypeArguments.length != newArguments.length) {
throw new IllegalArgumentException(
"Expecting " + actualTypeArguments.length + " type arguments but got " + newArguments.length + ".");
}
return new ParameterizedTypeWrapper(newArguments, rawType, ownerType);
}
public TypeWrapper[] getActualTypeArguments() {
return actualTypeArguments;
}
}