package tc.oc.evil; import java.util.concurrent.ExecutionException; import javax.inject.Inject; import javax.inject.Singleton; import com.google.common.base.Throwables; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.util.concurrent.UncheckedExecutionException; import tc.oc.commons.core.reflect.MethodResolver; import tc.oc.commons.core.reflect.Methods; /** * Generates classes that implement the decorator pattern, wrapping another object * of a common supertype, and forwarding some methods to that object, while overriding * others to alter its behavior. * * Specifically, this factory takes a possibly abstract class implementing {@link Decorator}, * and generates a non-abstract subclass that forwards all methods to whatever is returned * from {@link Decorator#delegate()}. The decorated type can be an interface, an abstract class, * or a concrete class. Methods that are overridden in the decorator are not forwarded, * nor are methods that do not exist in the decorated class at all. * * The generated subclass will inherit all accessible constructors from its superclass. * However, these cannot be called directly. Instances must be created through the * create methods in this class. * * Example: * <pre> * interface Thing { * void woot(); * void donk(); * } * * class BoringThing implements Thing { * @Override public void woot() { ... } * @Override public void donk() { ... } * } * * abstract class SuperThing implements Thing, Decorator<Thing> { * private final Thing thing; * protected SuperThing(Thing thing) { this.thing = thing; } * @Override public Thing delegate() { return thing; } * @Override public void woot() { ... } * } * * SuperThing st = decoratorFactory.create( * Thing.class, * SuperThing.class, * new Class[]{ Thing.class }, * new Object[]{ new BoringThing() } * ); * * st.woot() // calls SuperThing#woot() * st.donk() // calls BoringThing#donk() * * </pre> */ public class DecoratorFactory { private static final DecoratorFactory INSTANCE = new DecoratorFactory(new LibCGDecoratorGenerator()); public static DecoratorFactory get() { return INSTANCE; } private final DecoratorGenerator generator; private final Cache<Class<? extends Decorator<?>>, DecoratorGenerator.Meta<?, ?>> cache = CacheBuilder.newBuilder().build(); private DecoratorFactory(DecoratorGenerator generator) { this.generator = generator; } public <T, D extends Decorator<T>> D create(Class<T> type, Class<D> decorator, Class<?>[] argumentTypes, Object[] arguments) { try { return meta(type, decorator).newInstance(argumentTypes, arguments); } catch(Exception e) { throw Throwables.propagate(e); } } public <T, D extends Decorator<T>> D create(Class<T> type, Class<D> decorator) { try { return meta(type, decorator).newInstance(); } catch(Exception e) { throw Throwables.propagate(e); } } /** * Given a type, and another type that decorates it, return a non-abstract class * extending the decorator type, generating any needed delegating methods. * * Generated classes are cached and reused per decorator class. */ public <T, D extends Decorator<T>> Class<? extends D> implement(Class<T> type, Class<D> decorator) { return meta(type, decorator).implementation; } private <T, D extends Decorator<T>> void validate(Class<T> type, Class<D> decorator) { final MethodResolver resolver = new MethodResolver(decorator); Methods.accessibleMethods(decorator).forEach(method -> { if(!(Methods.respondsTo(decorator, method) || resolver.hasMethod(type, method))) { throw new IllegalStateException("Method " + method + " is abstract in decorator " + decorator.getName() + " and cannot be forwarded to " + type.getName()); } }); } private <T, D extends Decorator<T>> DecoratorGenerator.Meta<T, D> meta(Class<T> type, Class<D> decorator) { // Try once without locking or creating the loader final DecoratorGenerator.Meta<?, ?> meta = cache.getIfPresent(decorator); if(meta != null) return (DecoratorGenerator.Meta<T, D>) meta; // Try again try { return (DecoratorGenerator.Meta<T, D>) cache.get(decorator, () -> { validate(type, decorator); return generator.implement(type, decorator); }); } catch(ExecutionException | UncheckedExecutionException e) { throw (RuntimeException) e.getCause(); } } }