I updating our current project to use the gradle-idea-plugin to generate our
IDEA projects.  We needed to insert some custom XML pieces, so I wrote this:

withXml { node ->
   def gradleSettings = node.component.find{ i...@name == 'GradleSettings' }
   if (!gradleSettings) {
      def builder = new NodeBuilder()
      node.append(
         builder.component(name: 'GradleSettings') {
            option(name: 'GRADLE_LIB_NAME', value: 'Gradle at Work')
         }
      )
   }
}

This seems very complicated, and when I started to do a large set of
changes, did not scale well.  Here is what I ended up with:

withXml { node ->
   def builder = new CreateOrModifyBuilder(node)
   builder.component(name: 'GradleSettings') {
      option(name: 'GRADLE_LIB_NAME')....@value = 'Gradle at Work'
   }
}

This is built around the idea of a "CreateOrModifyBuilder" (I am terrible at
naming things).  That class is given below.  The thing to note is that
"createNode" will either find a node that matches the criteria and return
it, or create such a node.  Then the "builder" code can update it.  So, the
snippet above means "find or create a component with a "name" attribute
called "GradleSettings".  Then find or create an option with name
"GRADLE_LIB_NAME".  Whether it already existed or not, set the value to
"Gradle at Work".  This is very different behavior than "option(name:
'GRADLE_LIB_NAME', value: 'Gradle at Work')" which would either find or
create that node.  Here is what I mean:

Assume the existing XML contained:

<option name="GRADLE_LIB_NAME" value="Whatever"/>

Using the CreateOrModifyBuilder with option(name: 'GRADLE_LIB_NAME')....@value
= 'Gradle at Work' results in

<option name="GRADLE_LIB_NAME" value="Gradle at Work"/>

But using option(name: 'GRADLE_LIB_NAME', value: 'Gradle at Work') results
in

<option name="GRADLE_LIB_NAME" value="Whatever"/>
<option name="GRADLE_LIB_NAME" value="Gradle at Work"/>

In other words, both nodes are in the result XML.

The "Builder" class looks like this:

public class CreateOrModifyBuilder extends BuilderSupport
{
   private Node root

   public CreateOrModifyBuilder(Node root)
   {
      this.root = root
   }

   protected void setParent(Object parent, Object child)
   {
   }

   protected Object createNode(Object name)
   {
      def node = currentNode."$name"
      if (!node)
         node = new Node(currentNode, name, new ArrayList());
      return node;
   }

   protected Object createNode(Object name, Object value)
   {
      throw new Exception("Cannot handle values");
   }

   protected Object createNode(Object name, Map attributes)
   {
      def node = currentNode."$name".find { testNode ->
         attributes.every { key, value ->
            testNode.@"$key" == value
         }
      }

      if (!node)
         node = new Node(currentNode, name, attributes, new ArrayList());

      return node;
   }

   protected Object createNode(Object name, Map attributes, Object value)
   {
      throw new Exception("Cannot handle values");
   }

   private Node getCurrentNode()
   {
      return (Node) (getCurrent() ? getCurrent() : root);
   }
}

*Does anyone have a better idea for solving this problem?  If not, I would
like the gradle-idea-plugin to provide such a builder perhaps via a
"withXMLBuilder" type method.  What do you think?*


-- 
John Murph
Automated Logic Research Team

Reply via email to