Similarly, the groovy, scala, or code-quality plugins could add
groovy, scala, checkstyle, etc specific properties to a SourceSet.
I would like to come up with an approach which allows us to apply the
convention mapping and extensible object capabilities to pretty much
any domain object.
I think that we should aim to make these capabilities usable from
both Java code and Groovy code. This is because Gradle itself will be
a heavy (this only?) user of these mechanisms, and we want to be free
to use Java code as appropriate. Our current hand-coded
implementations allow this.
I can see a few possible approaches:
1. The domain object class, or an abstract superclass, implements the
appropriate interface, such as IConventionAware. This is the
hand-coded approach we use now.
public interface SourceSet extends IConventionAware { }
public class DefaultSourceSet implements SourceSet {
... implements everything
}
SourceSet set = new DefaultSourceSet()
set.conventionMapping('classesDir', {some code})
2. The domain object class is declared abstract and is declared as
implementing the appropriate interface. We generate an implementing
subclass which implements the missing bits to mix in the desired
behaviour. The generation might happen at compile time or load time
or runtime. It might be done in any number of ways. The how doesn't
matter too much at this stage.
public interface SourceSet extends IConventionAware { }
public abstract class DefaultSourceSet implements SourceSet {
... implements the SourceSet specific methods, but not the
IConventionAware methods
}
SourceSet set = generator.newInstance(DefaultSourceSet.class)
set.conventionMapping('classesDir', {some code})
3. The domain object is a pojo, and does not implement any special
interfaces. We generate a subclass which implements the appropriate
interface and mixes in the behaviour.
public interface SourceSet { }
public class DefaultSourceSet implements SourceSet {
... implements SourceSet specific methods
}
SourceSet set = generator.newInstance(DefaultSourceSet.class)
IConventionAware convAware = (IConventionAware)set
convAware.conventionMapping('classesDir', {some code})
4. The domain object is a pojo, and we add groovy meta-methods and
properties to mix in the behaviour. Java classes would use some
helper class to access the appropriate behaviour.
public interface SourceSet { }
public class DefaultSourceSet implements SourceSet {
... implements SourceSet specific methods
}
SourceSet set = generator.newInstance(DefaultSourceSet.class)
// from groovy, a dynamic method call
set.conventionMapping('classesDir', {some code})
// from java
IConventionAware convAware = generator.asConventionAware(set)
convAware.conventionMapping('classesDir', {some code})
Option 1 advantages
- is simple
- the capabilities of a class are declared and documented as part of
the API
Disadvantages:
- we have to stamp out the same boiler plate for each capability
applied to each type of domain object.
- the capabilities applied to a given class is static
- from painful experience, this approach runs into various different
Groovy bugs.
Option 2 advantages:
- allows the capabilities to be declared and documented as part of
the API
- don't have to implement the same boiler plate everywhere.
Disadvantages:
- the capabilities are static.
- quite intrusive on the domain class.
Option 3 advantages:
- allows capabilities to be applied to pretty much any domain class,
including 3rd party classes, with very minimal impact on the domain
class.
- the behaviour is mixed-in at the bottom most subclass, so it is
much less likely to run into Groovy bugs (this is just my gut feel).
- we can use Groovy for now to implement the subclass, but we are not
tied to a Groovy-based implementation, so we could later use bytecode
generation or aspectj or similar.
Disadvantages:
- we lose the static declaration and documentation of the capability.
We would need better documentation than we could otherwise get away
with for this.
- moves some of the complexity from implementer of a domain class to
the client of a domain class
Option 4 is really just a variation on option 3, but ties the
capabilities explicitly to Groovy.
My preference is for option 3.