All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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