Proxy
Derivandi can be used to generated new classes. Often this is used to generate implementations of interfaces or abstract classes.
In this Example we want to create a proxy, that counts how often Map#put gets called.
To do that we create an abstract class, containing only the logic we need.
We than mark that class with @Proxy and the delegate with @Delegate.
A Derivandi Processor can now be written to generate an implementation of MapProxy that delegates all not implemented methods to the Delegate.
@Proxy
public abstract class MapProxy<K,V> implements Map<K, V> {
private int counter = 0;
@De1egate
protected final Map<K, V> delegate = new HashMap<>();
@Override
public V put(K key, V value) {
counter++;
return delegate.put(key,value);
}
}
import com.derivandi.api.D;
import com.derivandi.api.dsl.JavaDsl;
import com.derivandi.api.dsl.class_.ClassBodyStep;
import com.derivandi.api.dsl.constructor.ConstructorRenderable;
import com.derivandi.api.dsl.method.MethodRenderable;
import com.derivandi.api.processor.Processor;
import com.derivandi.api.processor.ProcessorBuilder;
import com.derivandi.api.processor.ProcessorConfiguration;
import com.derivandi.api.processor.SimpleContext;
import java.util.List;
import java.util.stream.Collectors;
import static com.derivandi.api.dsl.JavaDsl.*;
import static java.util.function.Predicate.not;
public class GenerationProcessor extends Processor {
@Override
public ProcessorConfiguration buildProcessor(ProcessorBuilder processorBuilder) {
return processorBuilder.process(GenerationProcessor::process);
}
private static void process(SimpleContext context) {
D.Annotation proxyAnnotation = context.getAnnotationOrThrow(Proxy.class.getName());
D.Annotation delegateAnnotation = context.getAnnotationOrThrow(Delegate.class.getName());
for (D.Class annotated : context.getClassesAnnotatedWith(proxyAnnotation)) {
D.Declared delegate = getValid(context, annotated, delegateAnnotation);
if (delegate == null) {
continue;
}
context.writeAndCompileSourceFile(renderProxyImpl(context, annotated, delegate));
}
}
private static D.Declared getValid(SimpleContext context,
D.Class annotated,
D.Annotation delegateAnnotation) {
List<D.Field> delegates = annotated.getFields()
.stream()
.filter(field -> field.isAnnotatedWith(delegateAnnotation))
.toList();
if (!annotated.isAbstract()) {
context.logAndRaiseErrorAt(annotated, "Not abstract");
return null;
}
if (annotated.getDirectInterfaces().isEmpty()) {
context.logAndRaiseErrorAt(annotated, "Does not implement any Interface");
return null;
}
if (delegates.size() != 1) {
context.logAndRaiseErrorAt(annotated, "A Proxy has to have one Delegate");
return null;
}
D.Field delegate = delegates.getFirst();
if (!delegate.isProtected() && !delegate.isPublic()) {
context.logAndRaiseErrorAt(delegate, "Delegate needs to be accessible to child classes");
return null;
}
if (!(delegate.getType() instanceof D.Declared declaredDelegate)) {
context.logAndRaiseErrorAt(delegate, "Delegate must be a Declared Type");
return null;
}
if (annotated.getConstructors()
.stream()
.filter(constructor -> constructor.getParameterTypes().size() == 1)
.noneMatch(constructor -> constructor.getParameterTypes()
.stream()
.findFirst()
.orElseThrow()
.equals(declaredDelegate))) {
context.logAndRaiseErrorAt(annotated, "Needs to have a Constructor with with Type of the Delegate");
return null;
}
return declaredDelegate;
}
private static ClassBodyStep renderProxyImpl(SimpleContext context,
D.Class annotated,
D.Declared delegate) {
return class_()
.package_(annotated.getPackage())
.public_().name(annotated.getName() + "Impl")
.genericDeclaration(annotated.getGenericDeclarations())
.extends_(annotated)
.constructor(renderConstructors(delegate))
.method(renderProxyMethods(context, annotated, delegate));
}
private static ConstructorRenderable renderConstructors(D.Declared delegate)
{
return constructor().surroundingType()
.parameter(JavaDsl.parameter(delegate, "delegate"))
.body("super(delegate);");
}
private static List<MethodRenderable> renderProxyMethods(SimpleContext context,
D.Class annotated,
D.Declared delegate) {
return delegate.getMethods()
.stream()
.filter(not(D.StaticModifiable::isStatic))
.filter(method -> annotated.getMethods()
.stream()
.noneMatch(method1 -> method1.overrides(method)))
.map(method -> renderProxyMethod(context, method))
.toList();
}
private static MethodRenderable renderProxyMethod(SimpleContext context,
D.Method method) {
return method()
.annotate(annotationUsage().type(annotation().package_(
"java.lang").name("Override")))
.public_()
.resultType(method.getResultType())
.name(method.getName())
.parameter(method.getParameters())
.throws_(method.getThrows())
.body(renderBody(context, method));
}
private static String renderBody(SimpleContext context,
D.Method method) {
String parameters = method.getParameters()
.stream()
.map(D.Nameable::getName)
.collect(Collectors.joining(", "));
if (method.getResultType().equals(context.getConstants().getVoid())) {
return "delegate." + method.getName() + "(" + parameters + ");";
}
return "return delegate." + method.getName() + "(" + parameters + ");";
}
}