/* * 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.method; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import org.gradle.api.GradleException; import org.gradle.internal.Cast; import org.gradle.internal.UncheckedException; import org.gradle.model.internal.type.ModelType; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.Arrays; import java.util.List; public class WeaklyTypeReferencingMethod<T, R> { private final ModelType<T> declaringType; private final ModelType<R> returnType; private final String name; private final ImmutableList<ModelType<?>> paramTypes; private final int modifiers; private int cachedHashCode = -1; private WeaklyTypeReferencingMethod(ModelType<T> declaringType, ModelType<R> returnType, Method method) { if (declaringType.getRawClass() != method.getDeclaringClass()) { throw new IllegalArgumentException("Unexpected target class."); } this.declaringType = declaringType; this.returnType = returnType; this.name = method.getName(); paramTypes = ImmutableList.copyOf(Iterables.transform(Arrays.asList(method.getGenericParameterTypes()), new Function<Type, ModelType<?>>() { public ModelType<?> apply(Type type) { return ModelType.of(type); } })); modifiers = method.getModifiers(); } public static WeaklyTypeReferencingMethod<?, ?> of(Method method) { return of(ModelType.declaringType(method), ModelType.returnType(method), method); } public static <T, R> WeaklyTypeReferencingMethod<T, R> of(ModelType<T> target, ModelType<R> returnType, Method method) { return new WeaklyTypeReferencingMethod<T, R>(target, returnType, method); } public ModelType<T> getDeclaringType() { return declaringType; } public ModelType<R> getReturnType() { return returnType; } public String getName() { return name; } public int getModifiers() { return modifiers; } public Annotation[] getAnnotations() { //we could retrieve annotations at construction time and hold references to them but unfortunately //in IBM JDK strong references are held from annotation instance to class in which it is used so we have to reflect return getMethod().getAnnotations(); } public List<ModelType<?>> getGenericParameterTypes() { return paramTypes; } public R invoke(T target, Object... args) { Method method = getMethod(); method.setAccessible(true); try { Object result = method.invoke(target, args); return returnType.getConcreteClass().cast(result); } catch (InvocationTargetException e) { throw UncheckedException.throwAsUncheckedException(e.getCause()); } catch (Exception e) { throw new GradleException(String.format("Could not call %s.%s() on %s", method.getDeclaringClass().getSimpleName(), method.getName(), target), e); } } public Method getMethod() { Class<?>[] paramTypesArray = Iterables.toArray(Iterables.transform(paramTypes, new Function<ModelType<?>, Class<?>>() { public Class<?> apply(ModelType<?> modelType) { return modelType.getRawClass(); } }), Class.class); try { return declaringType.getRawClass().getDeclaredMethod(name, paramTypesArray); } catch (NoSuchMethodException e) { throw UncheckedException.throwAsUncheckedException(e); } } @Override public int hashCode() { if (cachedHashCode != -1) { return cachedHashCode; } // there's a risk, for some methods, that the hash is always // recomputed but it won't be worse than before cachedHashCode = new HashCodeBuilder() .append(declaringType) .append(returnType) .append(name) .append(paramTypes) .toHashCode(); return cachedHashCode; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof WeaklyTypeReferencingMethod)) { return false; } WeaklyTypeReferencingMethod<?, ?> other = Cast.uncheckedCast(obj); return new EqualsBuilder() .append(declaringType, other.declaringType) .append(returnType, other.returnType) .append(name, other.name) .append(paramTypes, other.paramTypes) .isEquals(); } @Override public String toString() { return String.format("%s.%s(%s)", declaringType.getDisplayName(), name, Joiner.on(", ").join(Iterables.transform(paramTypes, new Function<ModelType<?>, String>() { @Override public String apply(ModelType<?> paramType) { return paramType.getDisplayName(); } })) ); } }