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

com.google.inject.throwingproviders.CheckedProviders Maven / Gradle / Ivy

The newest version!
package com.google.inject.throwingproviders;

import static com.google.inject.throwingproviders.ProviderChecker.checkInterface;

import com.google.common.base.Optional;
import com.google.inject.TypeLiteral;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Nullable;

/**
 * Static utility methods for creating and working with instances of {@link CheckedProvider}.
 *
 * @author [email protected] (Russ Harmon)
 * @since 4.2
 */
public final class CheckedProviders {
  private abstract static class CheckedProviderInvocationHandler implements InvocationHandler {
    private boolean isGetMethod(Method method) {
      // Since Java does not allow multiple methods with the same name and number & type of
      // arguments, this is all we need to check to see if it is an overriding method of
      // CheckedProvider#get().
      return method.getName().equals("get") && method.getParameterTypes().length == 0;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      if (method.getDeclaringClass() == Object.class) {
        return method.invoke(this, args);
      } else if (isGetMethod(method)) {
        return invokeGet(proxy, method);
      }
      throw new UnsupportedOperationException(
          String.format(
              "Unsupported method <%s> with args <%s> invoked on <%s>",
              method, Arrays.toString(args), proxy));
    }

    protected abstract T invokeGet(Object proxy, Method method) throws Throwable;

    @Override
    public abstract String toString();
  }

  private static final class ReturningHandler extends CheckedProviderInvocationHandler {
    private final T returned;

    ReturningHandler(T returned) {
      this.returned = returned;
    }

    @Override
    protected T invokeGet(Object proxy, Method method) throws Throwable {
      return returned;
    }

    @Override
    public String toString() {
      return String.format("generated CheckedProvider returning <%s>", returned);
    }
  }

  private static final class ThrowingHandler extends CheckedProviderInvocationHandler {
    private final Constructor throwableCtor;
    private final String typeName;

    private ThrowingHandler(Constructor throwableCtor, String typeName) {
      this.throwableCtor = throwableCtor;
      this.typeName = typeName;

      this.throwableCtor.setAccessible(true);
    }

    static ThrowingHandler forClass(Class throwable) {
      try {
        return new ThrowingHandler(throwable.getDeclaredConstructor(), throwable.getName());
      } catch (NoSuchMethodException e) {
        // This should have been caught by checkThrowable
        throw new AssertionError(
            String.format(
                "Throwable <%s> does not have a no-argument constructor", throwable.getName()));
      }
    }

    @Override
    protected Object invokeGet(Object proxy, Method method) throws Throwable {
      throw this.throwableCtor.newInstance();
    }

    @Override
    public String toString() {
      return String.format("generated CheckedProvider throwing <%s>", this.typeName);
    }
  }

  private CheckedProviders() {}

  private static > P generateProvider(
      Class

providerType, Optional value, InvocationHandler handler) { checkInterface(providerType, getClassOptional(value)); Object proxy = Proxy.newProxyInstance( providerType.getClassLoader(), new Class[] {providerType}, handler); @SuppressWarnings("unchecked") // guaranteed by the newProxyInstance API P proxyP = (P) proxy; return proxyP; } private static > P generateProvider( TypeLiteral

providerType, Optional value, InvocationHandler handler) { // TODO(eatnumber1): Understand why TypeLiteral#getRawType returns a Class rather // than a Class and remove this unsafe cast. Class

providerRaw = (Class) providerType.getRawType(); return generateProvider(providerRaw, value, handler); } private static Optional> getClassOptional(Optional value) { if (!value.isPresent()) { return Optional.absent(); } return Optional.>of(value.get().getClass()); } /** * Returns a {@link CheckedProvider} which always provides {@code instance}. * *

The provider type passed as {@code providerType} must be an interface. Calls to methods * other than {@link CheckedProvider#get} will throw {@link UnsupportedOperationException}. * * @param providerType the type of the {@link CheckedProvider} to return * @param instance the instance that should always be provided */ public static > P of( TypeLiteral

providerType, @Nullable T instance) { return generateProvider( providerType, Optional.fromNullable(instance), new ReturningHandler(instance)); } /** * Returns a {@link CheckedProvider} which always provides {@code instance}. * * @param providerType the type of the {@link CheckedProvider} to return * @param instance the instance that should always be provided * @see #of(TypeLiteral, T) */ public static > P of( Class

providerType, @Nullable T instance) { return of(TypeLiteral.get(providerType), instance); } /** * Returns a {@link CheckedProvider} which always throws exceptions. * *

This method uses the nullary (no argument) constructor of {@code throwable} to create a new * instance of the given {@link Throwable} on each method invocation which is then thrown * immediately. * *

See {@link #of(TypeLiteral, T)} for more information. * * @param providerType the type of the {@link CheckedProvider} to return * @param throwable the type of the {@link Throwable} to throw * @see #of(TypeLiteral, T) */ public static > P throwing( TypeLiteral

providerType, Class throwable) { // TODO(eatnumber1): Understand why TypeLiteral#getRawType returns a Class rather // than a Class and remove this unsafe cast. Class

providerRaw = (Class) providerType.getRawType(); checkThrowable(providerRaw, throwable); return generateProvider( providerType, Optional.absent(), ThrowingHandler.forClass(throwable)); } /** * Returns a {@link CheckedProvider} which always throws exceptions. * * @param providerType the type of the {@link CheckedProvider} to return * @param throwable the type of the {@link Throwable} to throw * @see #throwing(TypeLiteral, Class) */ public static > P throwing( Class

providerType, Class throwable) { return throwing(TypeLiteral.get(providerType), throwable); } private static boolean isCheckedException(Class thrownType) { return Exception.class.isAssignableFrom(thrownType) && !RuntimeException.class.isAssignableFrom(thrownType); } private static void checkThrowable( Class> providerType, Class thrownType) { try { thrownType.getDeclaredConstructor(); } catch (NoSuchMethodException e) { throw new IllegalArgumentException( String.format( "Thrown exception <%s> must have a no-argument constructor", thrownType.getName()), e); } if (!isCheckedException(thrownType)) { return; } Method getMethod; try { getMethod = providerType.getMethod("get"); } catch (NoSuchMethodException e) { throw new IllegalArgumentException( String.format("Provider class <%s> must have a get() method", providerType.getName()), e); } @SuppressWarnings("unchecked") // guaranteed by getExceptionTypes List> exceptionTypes = Arrays.asList((Class[]) getMethod.getExceptionTypes()); if (exceptionTypes.size() == 0) { return; } // Check if the thrown exception is declared to be thrown in the method signature. for (Class exceptionType : exceptionTypes) { if (exceptionType.isAssignableFrom(thrownType)) { return; } } throw new IllegalArgumentException( String.format( "Thrown exception <%s> is not declared to be thrown by <%s>", thrownType.getName(), getMethod)); } }