org.jclouds.reflect.Reflection2 Maven / Gradle / Ivy
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.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.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 TypeToken typeToken(Type in) {
return (TypeToken) get(typeTokenForType, checkNotNull(in, "class"));
}
/**
* gets a {@link TypeToken} for the given class.
*/
@SuppressWarnings("unchecked")
public static TypeToken typeToken(Class in) {
return (TypeToken) 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 Invokable constructor(Class ownerType, Class>... parameterTypes) {
return (Invokable) get(constructorForParams, new TypeTokenAndParameterTypes(typeToken(ownerType),
parameterTypes));
}
/**
* return all constructors present in the class as {@link Invokable}s.
*
* @param ownerType
* corresponds to {@link Invokable#getOwnerType()}
*/
@SuppressWarnings("unchecked")
public static Collection> constructors(TypeToken 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 Invokable method(TypeToken ownerType, Method method) {
return (Invokable) 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 Invokable method(Class ownerType, String name, Class>... parameterTypes) {
return (Invokable) 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 Collection> methods(Class ownerType) {
return Collection.class.cast(get(methodsForTypeToken, typeToken(ownerType)));
}
/**
* this gets all declared constructors, not just public ones. makes them accessible, as well.
*/
private static LoadingCache, Set>> constructorsForTypeToken = CacheBuilder
.newBuilder().build(new CacheLoader, Set>>() {
public Set> load(TypeToken> key) {
ImmutableSet.Builder> builder = ImmutableSet.> builder();
for (Constructor> ctor : key.getRawType().getDeclaredConstructors()) {
ctor.setAccessible(true);
builder.add(key.constructor(ctor));
}
return builder.build();
}
});
protected static List> toClasses(ImmutableList params) {
return Lists.transform(params, new Function>() {
public Class> apply(Parameter input) {
return input.getType().getRawType();
}
});
}
private static LoadingCache> typeTokenForType = CacheBuilder.newBuilder().build(
new CacheLoader>() {
public TypeToken> load(Type key) {
return TypeToken.of(key);
}
});
private static LoadingCache, TypeToken>> typeTokenForClass = CacheBuilder.newBuilder().build(
new CacheLoader, TypeToken>>() {
public TypeToken> load(Class> key) {
return TypeToken.of(key);
}
});
private static LoadingCache> constructorForParams = CacheBuilder
.newBuilder().build(new CacheLoader>() {
public Invokable, ?> load(final TypeTokenAndParameterTypes key) {
Set> constructors = get(constructorsForTypeToken, key.type);
Optional> constructor = tryFind(constructors, new Predicate>() {
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 class TypeTokenAndParameterTypes {
protected TypeToken> type;
protected List> 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 Objects.toStringHelper("").add("type", type).add("parameterTypes", parameterTypes).toString();
}
}
private static LoadingCache> methodForParams = CacheBuilder
.newBuilder().build(new CacheLoader>() {
public Invokable, ?> load(final TypeTokenNameAndParameterTypes key) {
Set> 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> method = tryFind(methods, new Predicate>() {
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 Objects.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, Set>> methodsForTypeToken = CacheBuilder
.newBuilder().build(new CacheLoader, Set>>() {
public Set> load(TypeToken> key) {
ImmutableSet.Builder> builder = ImmutableSet.> builder();
for (TypeToken> token : key.getTypes()) {
Class> raw = token.getRawType();
if (raw == Object.class)
continue;
for (Method method : raw.getDeclaredMethods()) {
method.setAccessible(true);
builder.add(key.method(method));
}
}
return builder.build();
}
});
/**
* ensures that exceptions are not doubly-wrapped
*/
private static V get(LoadingCache cache, K key) {
try {
return cache.get(key);
} catch (UncheckedExecutionException e) {
throw propagate(e.getCause());
} catch (ExecutionException e) {
throw propagate(e.getCause());
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy