Hi,

We currently apply convention mappings to various Task subclasses, and for RepositoryHandler. We also indirectly apply them to the root CopySpec of the Copy task. I want to be able to do this for (new domain object) SourceSet. For example, SourceSet might have a classesDir property, which would default to $buildDir/$classesDirName. I could just hardcode this in, but it would be nice to generalise the solution and make it cross-cutting.

We also have a similar problem with dynamic properties and methods. Currently Project and Task can be extended at runtime using convention objects and dynamic properties. We would benefit from making Configuration and SourceSet extensible as well. For example, the maven plugin can do a much better job if it can extend Configuration to have a bunch of maven-specific properties and methods. 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.

Thoughts?


Adam


---------------------------------------------------------------------
To unsubscribe from this list, please visit:

   http://xircles.codehaus.org/manage_email


Reply via email to