blob: 7302884f81288e74c74e096f37b483bc6ebd8e15 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.jclouds.reflect;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Throwables.propagate;
import static com.google.common.collect.Iterables.tryFind;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import com.google.common.annotations.Beta;
import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.reflect.Invokable;
import com.google.common.reflect.Parameter;
import com.google.common.reflect.TypeToken;
import com.google.common.util.concurrent.UncheckedExecutionException;
/**
* Utilities that allow access to {@link Invokable}s with {@link Invokable#getOwnerType() owner types}.
*
* @since 1.6
*/
@Beta
public class Reflection2 {
/**
* gets a {@link TypeToken} for the given type.
*/
@SuppressWarnings("unchecked")
public static <T> TypeToken<T> typeToken(Type in) {
return (TypeToken<T>) get(typeTokenForType, checkNotNull(in, "class"));
}
/**
* gets a {@link TypeToken} for the given class.
*/
@SuppressWarnings("unchecked")
public static <T> TypeToken<T> typeToken(Class<T> in) {
return (TypeToken<T>) get(typeTokenForClass, checkNotNull(in, "class"));
}
/**
* returns an {@link Invokable} object that reflects a constructor present in the {@link TypeToken} type.
*
* @param ownerType
* corresponds to {@link Invokable#getOwnerType()}
* @param parameterTypes
* corresponds to {@link Constructor#getParameterTypes()}
*
* @throws IllegalArgumentException
* if the constructor doesn't exist or a security exception occurred
*/
@SuppressWarnings("unchecked")
public static <T> Invokable<T, T> constructor(Class<T> ownerType, Class<?>... parameterTypes) {
return (Invokable<T, T>) get(constructorForParams, new TypeTokenAndParameterTypes(typeToken(ownerType),
parameterTypes));
}
/**
* return all constructors or static factory methods present in the class as {@link Invokable}s.
*
* @param ownerType
* corresponds to {@link Invokable#getOwnerType()}
*/
@SuppressWarnings("unchecked")
public static <T> Collection<Invokable<T, T>> constructors(TypeToken<T> ownerType) {
return Collection.class.cast(get(constructorsForTypeToken, ownerType));
}
/**
* returns an {@link Invokable} object that links the {@code method} to its owner.
*
* @param ownerType
* corresponds to {@link Invokable#getOwnerType()}
* @param method
* present in {@code ownerType}
*/
@SuppressWarnings("unchecked")
public static <T, R> Invokable<T, R> method(TypeToken<T> ownerType, Method method) {
return (Invokable<T, R>) method(ownerType.getRawType(), method.getName(), method.getParameterTypes());
}
/**
* returns an {@link Invokable} object that reflects a method present in the {@link TypeToken} type.
* If there are multiple methods of the same name and parameter list, returns the method in the nearest
* ancestor with the most specific return type (see {@link Class#getDeclaredMethod}).
*
* @param ownerType
* corresponds to {@link Invokable#getOwnerType()}
* @param name
* name of the method to be returned
* @param parameterTypes
* corresponds to {@link Method#getParameterTypes()}
*
* @throws IllegalArgumentException
* if the method doesn't exist or a security exception occurred
*/
@SuppressWarnings("unchecked")
public static <T, R> Invokable<T, R> method(Class<T> ownerType, String name, Class<?>... parameterTypes) {
return (Invokable<T, R>) get(methodForParams, new TypeTokenNameAndParameterTypes(typeToken(ownerType), name,
parameterTypes));
}
/**
* return all methods present in the class as {@link Invokable}s.
*
* @param ownerType
* corresponds to {@link Invokable#getOwnerType()}
*/
@SuppressWarnings("unchecked")
public static <T> Collection<Invokable<T, Object>> methods(Class<T> ownerType) {
return Collection.class.cast(get(methodsForTypeToken, typeToken(ownerType)));
}
/**
* This gets all declared constructors or factory methods on abstract types, not just public ones, and makes them
* accessible.
*/
private static LoadingCache<TypeToken<?>, Set<Invokable<?, ?>>> constructorsForTypeToken = CacheBuilder
.newBuilder().build(new CacheLoader<TypeToken<?>, Set<Invokable<?, ?>>>() {
public Set<Invokable<?, ?>> load(TypeToken<?> key) {
ImmutableSet.Builder<Invokable<?, ?>> builder = ImmutableSet.<Invokable<?, ?>> builder();
for (Constructor<?> ctor : key.getRawType().getDeclaredConstructors()) {
ctor.setAccessible(true);
builder.add(key.constructor(ctor));
}
// Look for factory methods, if this is an abstract type.
if (Modifier.isAbstract(key.getRawType().getModifiers())) {
for (Invokable<?, Object> method : methods(key.getRawType())){
if (method.isStatic() && method.getReturnType().equals(key)) {
method.setAccessible(true);
builder.add(method);
}
}
}
return builder.build();
}
});
protected static List<Class<?>> toClasses(ImmutableList<Parameter> params) {
return Lists.transform(params, new Function<Parameter, Class<?>>() {
public Class<?> apply(Parameter input) {
return input.getType().getRawType();
}
});
}
private static LoadingCache<Type, TypeToken<?>> typeTokenForType = CacheBuilder.newBuilder().build(
new CacheLoader<Type, TypeToken<?>>() {
public TypeToken<?> load(Type key) {
return TypeToken.of(key);
}
});
private static LoadingCache<Class<?>, TypeToken<?>> typeTokenForClass = CacheBuilder.newBuilder().build(
new CacheLoader<Class<?>, TypeToken<?>>() {
public TypeToken<?> load(Class<?> key) {
return TypeToken.of(key);
}
});
private static LoadingCache<TypeTokenAndParameterTypes, Invokable<?, ?>> constructorForParams = CacheBuilder
.newBuilder().build(new CacheLoader<TypeTokenAndParameterTypes, Invokable<?, ?>>() {
public Invokable<?, ?> load(final TypeTokenAndParameterTypes key) {
Set<Invokable<?, ?>> constructors = get(constructorsForTypeToken, key.type);
Optional<Invokable<?, ?>> constructor = tryFind(constructors, new Predicate<Invokable<?, ?>>() {
public boolean apply(Invokable<?, ?> input) {
return Objects.equal(toClasses(input.getParameters()), key.parameterTypes);
}
});
if (constructor.isPresent())
return constructor.get();
throw new IllegalArgumentException("no such constructor " + key.toString() + "in: " + constructors);
}
});
private static final LoadingCache<Invokable<?, ?>, ImmutableList<Parameter>> invokableParamsCache =
CacheBuilder.newBuilder().maximumSize(100).build(new CacheLoader<Invokable<?, ?>, ImmutableList<Parameter>>() {
@Override
public ImmutableList<Parameter> load(Invokable<?, ?> invokable) {
return invokable.getParameters();
}
});
/**
* Returns the {@link Parameter}s associated with the given {@link Invokable}. This function is backed by a cache.
*
* @param invokable
* The {@link Invokable} we want to get Parameters from
*/
public static List<Parameter> getInvokableParameters(final Invokable<?, ?> invokable) {
return invokableParamsCache.getUnchecked(invokable);
}
private static class TypeTokenAndParameterTypes {
protected TypeToken<?> type;
protected List<Class<?>> parameterTypes;
public TypeTokenAndParameterTypes(TypeToken<?> type, Class<?>... parameterTypes) {
this.type = checkNotNull(type, "type");
this.parameterTypes = Arrays.asList(checkNotNull(parameterTypes, "parameterTypes"));
}
public int hashCode() {
return Objects.hashCode(type, parameterTypes);
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
TypeTokenAndParameterTypes that = TypeTokenAndParameterTypes.class.cast(obj);
return Objects.equal(this.type, that.type) && Objects.equal(this.parameterTypes, that.parameterTypes);
}
public String toString() {
return MoreObjects.toStringHelper("").add("type", type).add("parameterTypes", parameterTypes).toString();
}
}
private static LoadingCache<TypeTokenNameAndParameterTypes, Invokable<?, ?>> methodForParams = CacheBuilder
.newBuilder().build(new CacheLoader<TypeTokenNameAndParameterTypes, Invokable<?, ?>>() {
public Invokable<?, ?> load(final TypeTokenNameAndParameterTypes key) {
Set<Invokable<?, ?>> methods = get(methodsForTypeToken, key.type);
/*
* There may be multiple instances, even on the most immediate ancestor,
* of a method with the required name and parameter set. This will occur
* if the method overrides one declared in a parent class with a less specific
* return type. These bridge methods inserted by the compiler will be marked
* as "synthetic".
*/
Optional<Invokable<?, ?>> method = tryFind(methods, new Predicate<Invokable<?, ?>>() {
public boolean apply(Invokable<?, ?> input) {
// Invokable doesn't expose Method#isBridge
return !input.isSynthetic() && Objects.equal(input.getName(), key.name)
&& Objects.equal(toClasses(input.getParameters()), key.parameterTypes);
}
});
checkArgument(method.isPresent(), "no such method %s in: %s", key.toString(), methods);
return method.get();
}
});
private static class TypeTokenNameAndParameterTypes extends TypeTokenAndParameterTypes {
private String name;
public TypeTokenNameAndParameterTypes(TypeToken<?> type, String name, Class<?>... parameterTypes) {
super(type, parameterTypes);
this.name = checkNotNull(name, "name");
}
public int hashCode() {
return Objects.hashCode(super.hashCode(), name);
}
public boolean equals(Object obj) {
if (super.equals(obj)) {
TypeTokenNameAndParameterTypes that = TypeTokenNameAndParameterTypes.class.cast(obj);
return name.equals(that.name);
}
return false;
}
public String toString() {
return MoreObjects.toStringHelper("").add("type", type).add("name", name).add("parameterTypes", parameterTypes)
.toString();
}
}
/**
* this gets all declared methods, not just public ones. makes them accessible. Does not include Object methods.
* Invokables for a type are ordered so all invokables on a subtype are always listed before invokables on a
* supertype (see {@link TypeToken#getTypes()}).
*/
private static LoadingCache<TypeToken<?>, Set<Invokable<?, ?>>> methodsForTypeToken = CacheBuilder
.newBuilder().build(new CacheLoader<TypeToken<?>, Set<Invokable<?, ?>>>() {
public Set<Invokable<?, ?>> load(TypeToken<?> key) {
ImmutableSet.Builder<Invokable<?, ?>> builder = ImmutableSet.<Invokable<?, ?>> builder();
for (TypeToken<?> token : key.getTypes()) {
Class<?> raw = token.getRawType();
if (raw == Object.class)
continue;
for (Method method : raw.getDeclaredMethods()) {
if (!coreJavaClass(raw)) {
method.setAccessible(true);
}
builder.add(key.method(method));
}
}
return builder.build();
}
});
private static boolean coreJavaClass(Class<?> clazz) {
// treat null packages (e.g. for proxy objects) as "non-core"
Package clazzPackage = clazz.getPackage();
if (clazzPackage == null) {
return false;
}
String packageName = clazzPackage.getName();
return packageName.startsWith("com.sun.") || packageName.startsWith("java.")
|| packageName.startsWith("javax.") || packageName.startsWith("sun.");
}
/**
* ensures that exceptions are not doubly-wrapped
*/
private static <K, V> V get(LoadingCache<K, V> cache, K key) {
try {
return cache.get(key);
} catch (UncheckedExecutionException e) {
throw propagate(e.getCause());
} catch (ExecutionException e) {
throw propagate(e.getCause());
}
}
}