Java annotation processor for automatically delegating interface APIs to a composed instance of that interface. This project was inspired by Google's auto project and leverages utilities exposed in auto-common.
Intro blog post: https://www.ryandens.com/post/auto_delegate/
Requirements:
- JDK 11 or above
dependencies {
compileOnly("com.ryandens", "auto-delegate-annotations", "0.3.1")
annotationProcessor("com.ryandens", "auto-delegate-processor", "0.3.1")
}
<project>
<dependencies>
<dependency>
<groupId>com.ryandens</groupId>
<artifactId>auto-delegate-annotations</artifactId>
<version>${auto-delegate.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>com.ryandens</groupId>
<artifactId>auto-delegate-processor</artifactId>
<version>${auto-delegate.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
This simple example usage of @AutoDelegate
is based off of the example given
in kotlin's delegation documentation. There are more examples in auto-delegate-examples
public interface Base {
void print();
}
public final class BaseImpl implements Base {
private final int x;
public BaseImpl(final int x) {
this.x = x;
}
@Override
void print() {
System.out.println(x);
}
}
@AutoDelegate(Base.class)
final class Derived extends AutoDelegate_Derived implements Base {
Derived(final Base base) {
super(base);
}
}
Decorates a class with metadata to describe an abstract parent class that automatically delegates to an inner composed instance of an interface. This annotation processor is inspired by the Kotlin language feature delegation.
The goal of this is to encourage the use of composition over inheritance as described by Effective Java Item 18 "Favor
composition over inheritance". In the section of the book, Bloch describes an InstrumentedSet
that counts the number of
items added to it. In order to accomplish this, Bloch creates an abstract implementation of java.util.Set
called ForwardingSet
that simply composes a java.util.Set
instance and forwards all calls to it. This allows Bloch
to write the InstrumentedSet
in a less verbose manner, by extending ForwardingSet
and overriding the "add" related
methods for instrumentation purposes. This is a great solution in the context of Java, but Kotlin lowers the cognitive
barrier of using composition by making it less verbose to do so. In Kotlin, the need for a ForwardingSet
is obviated by
the "delegation" language feature linked above. The InstrumentedSet
can be written concisely without relying on
writing a ForwardingSet
like:
class InstrumentedSet<E>(val inner: MutableSet<E>) : MutableSet<E> by inner {
var count: Int = 0
override fun add(element: E): Boolean {
count++
return inner.add(element)
}
override fun addAll(elements: Collection<E>) : Boolean {
count += elements.size
return inner.addAll(elements)
}
}
This annotation strives to enable developers in the same fashion by generating abstract Forwarding
classes that
delegate to the inner composed instance. An equivalent InstrumentedSet
implementation written with AutoDelegate
is
@AutoDelegate(Set.class)
public final class InstrumentedSet<E> extends AutoDelegate_InstrumentedSet<E> implements Set<E> {
private int addCount;
public InstrumentedSet(final Set<E> inner) {
super(inner);
this.addCount = 0;
}
@Override
public boolean add(final E t) {
addCount++;
return super.add(t);
}
@Override
public boolean addAll(final Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
/**
* @return the number of times a caller has attempted to add an item to this set
*/
public int addCount() {
return addCount;
}
}
While this is not as concise as the Kotlin implementation, it generates a class called AutoDelegate_InstrumentedSet
in
the same package as the declaring class. The declared class can then extend the generated class and call super
APIs where appropriate, only overriding methods that are relevant to the implementation
- JDK 17
- Make sure the
sonatypeUsername
andsonatypePassword
properties are set. - Make sure the
signing.keyId
,signing.password
, andsigning.secretKeyRingFile
properties are set. ./gradlew build signNebulaPublication publishNebulaPublicationToSonatypeRepository closeAndReleaseSonatypeStagingRepository
Note, the stagingProfileId
set in the root build.gradle.kts
was retrieved using the retrieveSonatypeStagingProfile
diagnostic task of the io.github.gradle-nexus.publish-plugin