package com.fluentinterface;
import com.fluentinterface.builder.Builder;
import com.fluentinterface.proxy.AttributeAccessStrategy;
import com.fluentinterface.proxy.BuilderDelegate;
import com.fluentinterface.proxy.BuilderProxy;
import com.fluentinterface.proxy.impl.FieldAttributeAccessStrategy;
import com.fluentinterface.proxy.impl.SetterAttributeAccessStrategy;
import com.fluentinterface.utils.GenericsUtils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ReflectionBuilder<T> {
private static BuilderDelegate defaultBuilderDelegate = new InternalBuilderAsSuperClassDelegate();
private BuilderDelegate<? super T> builderDelegate;
private Class<T> builderInterface;
private Class<?> builtClass = null;
private AttributeAccessStrategy attributeAccessStrategy;
@SuppressWarnings("unchecked")
private ReflectionBuilder(Class<T> builderInterface) {
if (!builderInterface.isInterface()) {
throw new IllegalArgumentException(String.format(
"Can only create dynamic builder for interfaces. [%s] is not an interface.", builderInterface));
}
this.builderInterface = builderInterface;
this.builderDelegate = defaultBuilderDelegate;
this.attributeAccessStrategy = new SetterAttributeAccessStrategy();
}
public static void setDefaultBuilderDelegate(BuilderDelegate delegate) {
defaultBuilderDelegate = delegate;
}
public static <T> ReflectionBuilder<T> implementationFor(Class<T> builderInterface) {
return new ReflectionBuilder<T>(builderInterface);
}
public ReflectionBuilder<T> builds(Class<?> objectsOfType) {
this.builtClass = objectsOfType;
return this;
}
public ReflectionBuilder<T> withDelegate(BuilderDelegate<? super T> builderDelegate) {
this.builderDelegate = builderDelegate;
return this;
}
public ReflectionBuilder<T> usingAttributeAccessStrategy(AttributeAccessStrategy strategy) {
this.attributeAccessStrategy = strategy;
return this;
}
public ReflectionBuilder<T> usingFieldsDirectly() {
this.attributeAccessStrategy = new FieldAttributeAccessStrategy();
return this;
}
public Class<?> getBuiltClass() {
if (builtClass != null) {
return builtClass;
}
builtClass = builderDelegate.getClassBuiltBy(builderInterface);
if (builtClass == null) {
throw new IllegalStateException(String.format(
"Could not imply class being built by builder [%s]. " +
"If the interface does not extend [%s], you must explicitly set the type of object being built using the 'builds(class)' method.",
builderInterface, Builder.class
));
}
return builtClass;
}
@SuppressWarnings("unchecked")
public T create() {
InvocationHandler handler = new BuilderProxy(builderInterface, getBuiltClass(), builderDelegate, attributeAccessStrategy);
return (T) Proxy.newProxyInstance(
builderInterface.getClassLoader(),
new Class[]{builderInterface},
handler);
}
private static class InternalBuilderAsSuperClassDelegate implements BuilderDelegate<Builder> {
private static final String BUILD_METHOD_NAME = "build";
private Method buildMethod;
public InternalBuilderAsSuperClassDelegate() {
try {
this.buildMethod = Builder.class.getDeclaredMethod(BUILD_METHOD_NAME, Object[].class);
} catch (NoSuchMethodException e) {
throw new IllegalStateException(
String.format("Could not find [%s] method on [%s] class.", BUILD_METHOD_NAME, Builder.class),
e
);
}
}
@Override
public Class<?> getClassBuiltBy(Class<?> builderInterface) {
return GenericsUtils.getDeclaredGenericType(builderInterface, Builder.class);
}
@Override
public Object build(Builder builder) {
return builder.build();
}
@Override
public boolean isBuilderInstance(Object value) {
return value instanceof Builder;
}
@Override
public boolean isBuildMethod(Method method) {
return method.equals(buildMethod);
}
}
}