Author: jhm
Date: Thu Oct 29 13:18:16 2009
New Revision: 830928

URL: http://svn.apache.org/viewvc?rev=830928&view=rev
Log:
Task <autoconf> for discussion

Added:
    ant/sandbox/autoconf/
    ant/sandbox/autoconf/build.properties
    ant/sandbox/autoconf/build.xml
    ant/sandbox/autoconf/docs/
    ant/sandbox/autoconf/docs/autoconf.html
    ant/sandbox/autoconf/src/
    ant/sandbox/autoconf/src/antunit/
    ant/sandbox/autoconf/src/antunit/autoconf-test.xml
    ant/sandbox/autoconf/src/main/
    ant/sandbox/autoconf/src/main/org/
    ant/sandbox/autoconf/src/main/org/apache/
    ant/sandbox/autoconf/src/main/org/apache/tools/
    ant/sandbox/autoconf/src/main/org/apache/tools/ant/
    ant/sandbox/autoconf/src/main/org/apache/tools/ant/taskdefs/
    
ant/sandbox/autoconf/src/main/org/apache/tools/ant/taskdefs/AutoconfTask.java
    ant/sandbox/autoconf/test.xml

Added: ant/sandbox/autoconf/build.properties
URL: 
http://svn.apache.org/viewvc/ant/sandbox/autoconf/build.properties?rev=830928&view=auto
==============================================================================
--- ant/sandbox/autoconf/build.properties (added)
+++ ant/sandbox/autoconf/build.properties Thu Oct 29 13:18:16 2009
@@ -0,0 +1,4 @@
+build.dir=build
+src.dir=src/main
+
+classes.dir=${build.dir}/classes

Added: ant/sandbox/autoconf/build.xml
URL: 
http://svn.apache.org/viewvc/ant/sandbox/autoconf/build.xml?rev=830928&view=auto
==============================================================================
--- ant/sandbox/autoconf/build.xml (added)
+++ ant/sandbox/autoconf/build.xml Thu Oct 29 13:18:16 2009
@@ -0,0 +1,7 @@
+<project>
+    <property file="build.properties"/>
+    <mkdir dir="${classes.dir}"/>
+    <javac srcdir="${src.dir}" destdir="${classes.dir}"/>
+
+    <ant antfile="test.xml"/>
+</project>
\ No newline at end of file

Added: ant/sandbox/autoconf/docs/autoconf.html
URL: 
http://svn.apache.org/viewvc/ant/sandbox/autoconf/docs/autoconf.html?rev=830928&view=auto
==============================================================================
--- ant/sandbox/autoconf/docs/autoconf.html (added)
+++ ant/sandbox/autoconf/docs/autoconf.html Thu Oct 29 13:18:16 2009
@@ -0,0 +1,69 @@
+<html>
+
+<head>
+<meta http-equiv="Content-Language" content="en-us">
+<title>Autoconf Task</title>
+</head>
+
+<body>
+
+<h2><a name="taskname">Autoconf</a></h2>
+<h3>Description</h3>
+
+<p>Starts or stops autoconfiguration for tasks and datatypes.
+Autoconfiguration applies properties to tasks and datatypes if
+their names fit to the tasks/datatype and attribute names and the
+attribute is not set.</p>
+
+<p>The property name must match the following rule:<br>
+<tt>propertyname ::= prefix? taskname "." attributename</tt></p>
+
+<p>Only one Autoconf is active at the same time.</p>
+
+
+<h3>Parameters</h3>
+<table border="1" cellpadding="2" cellspacing="0">
+  <tr>
+    <td valign="top"><b>Attribute</b></td>
+    <td valign="top"><b>Description</b></td>
+    <td align="center" valign="top"><b>Required</b></td>
+  </tr>
+  <tr>
+    <td valign="top">start</td>
+    <td valign="top">'start' or 'stop' autoconfiguration</td>
+    <td align="center" valign="top">no, defaults to 'start'</td>
+  </tr>
+  <tr>
+    <td valign="top">prefix</td>
+    <td valign="top">Prefix to use for property names</td>
+    <td align="center" valign="top">no, empty as default</td>
+  </tr>
+</table>
+
+
+<h3>Examples</h3>
+<pre>
+&lt;property name=&quot;echo.message&quot; value=&quot;Default 
Message&quot;/&gt;
+&lt;autoconf/&gt;
+&lt;echo/&gt;
+</pre>
+Echoes <tt>Default Message</tt>, because the name rule ("" + "echo" + "." + 
"message")
+catches one existing property.
+
+<pre>
+&lt;property name=&quot;echo.notAnAttribute&quot; value=&quot;Default 
Message&quot;/&gt;
+&lt;autoconf/&gt;
+&lt;echo/&gt;
+</pre>
+Echoes an empty line as the default behaviour of &lt;echo/&gt;. The message
+is not applied because the property name does not match the rule.
+
+<pre>
+&lt;property name=&quot;echo.message&quot; value=&quot;Default 
Message&quot;/&gt;
+&lt;autoconf/&gt;
+&lt;echo message=&quot;Individual Message&quot;/&gt;
+</pre>
+Autoconf does not do anything because the attribute of the echo task is 
already set.
+
+</body>
+</html>

Added: ant/sandbox/autoconf/src/antunit/autoconf-test.xml
URL: 
http://svn.apache.org/viewvc/ant/sandbox/autoconf/src/antunit/autoconf-test.xml?rev=830928&view=auto
==============================================================================
--- ant/sandbox/autoconf/src/antunit/autoconf-test.xml (added)
+++ ant/sandbox/autoconf/src/antunit/autoconf-test.xml Thu Oct 29 13:18:16 2009
@@ -0,0 +1,120 @@
+<?xml version="1.0"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<project xmlns:au="antlib:org.apache.ant.antunit" default="antunit">
+  <import 
file="C:/ant/svn-repository/ant/core/src/tests/antunit/antunit-base.xml"/>
+  <taskdef name="autoconf" 
classname="org.apache.tools.ant.taskdefs.AutoconfTask" 
classpath="../../build/classes"/>
+  
+  <target name="testIgnoreNonAttributeValues">
+    <property name="echo.notAnAttribute" value="Default Message"/>
+    <autoconf/>
+    <echo/>
+    <au:assertLogDoesntContain text="Default Message"/>
+  </target>
+  
+  <target name="testNoDefault">
+    <autoconf/>
+    <echo message="Individual Message"/>
+    <au:assertLogContains text="Individual Message"/>
+  </target>
+  
+  <target name="testDirectValue">
+    <property name="echo.message" value="Default Message"/>
+    <autoconf/>
+    <echo message="Individual Message"/>
+    <au:assertLogContains text="Individual Message"/>
+  </target>
+
+  <target name="testDefault">
+    <property name="echo.message" value="Default Message"/>
+    <autoconf/>
+    <echo/>
+    <au:assertLogContains text="Default Message"/>
+  </target>
+  
+  <target name="testPresetDef">
+    <property name="myecho.message" value="Default Message"/>
+    <presetdef name="myecho"><echo/></presetdef>
+    <autoconf/>
+    <myecho/>
+    <au:assertLogContains text="Default Message"/>
+  </target>
+
+  <target name="testPresetDefIndividualValue">
+    <property name="myecho.message" value="Default Message"/>
+    <presetdef name="myecho"><echo/></presetdef>
+    <autoconf/>
+    <myecho message="Individual Message"/>
+    <au:assertLogContains text="Individual Message"/>
+  </target>
+
+  <target name="testPresetDefButTasknameProperty">
+    <property name="myecho.message" value="Default Preset Message"/>
+    <property name="echo.message" value="Default Task Message"/>
+    <presetdef name="myecho"><echo/></presetdef>
+    <autoconf/>
+    <myecho/>
+    <au:assertLogContains text="Default Preset Message"/>
+  </target>
+  
+  <target name="testMacroDef">
+    <property name="myecho.message" value="Default Message"/>
+    <macrodef name="myecho">
+      <attribute name="message"/>
+      <sequential>
+        <echo message="@{message}"/>
+      </sequential>
+    </macrodef>
+    <autoconf/>
+    <myecho/>
+    <au:assertLogContains text="Default Message"/>
+  </target>
+
+  <target name="testMacroDefIndividualValue">
+    <property name="myecho.message" value="Default Message"/>
+    <macrodef name="myecho">
+      <attribute name="message"/>
+      <sequential>
+        <echo message="@{message}"/>
+      </sequential>
+    </macrodef>
+    <autoconf/>
+    <myecho message="Individual Message"/>
+    <au:assertLogContains text="Individual Message"/>
+  </target>
+  
+  <target name="testStartStop">
+    <property name="myecho.message" value="Autoconf Message"/>
+    <presetdef name="myecho"><echo message="Default Message"/></presetdef>
+    <myecho/>
+    <au:assertLogContains text="Default Message"/>
+    <autoconf/>
+    <myecho/>
+    <au:assertLogContains text="Autoconf Message"/>
+    <autoconf action="stop"/>
+    <myecho/>
+    <au:assertLogContains text="Default Message"/>
+  </target>
+  
+  <target name="testPrefix">
+    <property name="echo.message" value="wrong message"/>
+    <property name="prefix.echo.message" value="correct message"/>
+    <autoconf prefix="prefix."/>
+    <echo/>
+    <au:assertLogContains text="correct message"/>
+  </target>
+</project>
\ No newline at end of file

Added: 
ant/sandbox/autoconf/src/main/org/apache/tools/ant/taskdefs/AutoconfTask.java
URL: 
http://svn.apache.org/viewvc/ant/sandbox/autoconf/src/main/org/apache/tools/ant/taskdefs/AutoconfTask.java?rev=830928&view=auto
==============================================================================
--- 
ant/sandbox/autoconf/src/main/org/apache/tools/ant/taskdefs/AutoconfTask.java 
(added)
+++ 
ant/sandbox/autoconf/src/main/org/apache/tools/ant/taskdefs/AutoconfTask.java 
Thu Oct 29 13:18:16 2009
@@ -0,0 +1,302 @@
+package org.apache.tools.ant.taskdefs;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.tools.ant.BuildEvent;
+import org.apache.tools.ant.BuildListener;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.RuntimeConfigurable;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.UnknownElement;
+import org.apache.tools.ant.taskdefs.PreSetDef.PreSetDefinition;
+import org.apache.tools.ant.types.EnumeratedAttribute;
+
+
+/**
+ * <p>Starts or stops autoconfiguration for tasks and datatypes.
+ * Autoconfiguration applies properties to tasks and datatypes if
+ * their names fit to the tasks/datatype and attribute names and the 
+ * attribute is not set.</p>
+ * 
+ * <p>The property name must match the following rule:<br>
+ * <tt>propertyname ::= prefix? taskname "." attributename</tt></p> 
+ * 
+ * <p><b>Example:</b><br>
+ * You have defined properties like <tt>javac.debug=on, javac.target=1.5,
+ * javac.source=1.5</tt>. Instead of applying these values manually you
+ * could write <tt>&lt;autoconf/&gt; &lt;javac&gt;</tt></p>
+ * 
+ * @since Ant 1.8.0
+ */
+public class AutoconfTask extends Task {
+       
+       /** Start or stop the AutoconfListener? */
+       boolean start = true;
+       
+       /** Optional name prefix (before the task name). */
+       String prefix = null;
+       
+    public void setPrefix(String prefix) {
+               this.prefix = prefix;
+       }
+
+       public void execute() {
+               if (start) {
+                       addAutoconfListener();
+               } else {
+                       removeAutoconfListener();
+               }
+       }
+
+       /**
+        * Searches all registered AutoconfListener and removes them.
+        */
+       private void removeAutoconfListener() {
+               Iterator it = getProject().getBuildListeners().iterator();
+               while(it.hasNext()) {
+                       Object next = it.next();
+                       if (next instanceof AutoconfListener) {
+                               
getProject().removeBuildListener(((AutoconfListener) next));
+                       }
+               }
+       }
+
+       /**
+        * Adds a new AutoconfListener.
+        */
+       private void addAutoconfListener() {
+               getProject().addBuildListener(new AutoconfListener(prefix));
+       }
+
+    /**
+     * Sets the action for the autoconf task.
+     *
+     * @param action The action for the entry to take: start or stop.
+     */
+    public void setAction(ActionChoices action) {
+        start = action.getValue().equalsIgnoreCase("start");
+    }
+       
+    
+    //-------------------------------------------------------------------------
+       
+    
+    /**
+     * A list of possible values for the <code>setAction()</code> method.
+     * Possible values include: start and stop.
+     */
+    public static class ActionChoices extends EnumeratedAttribute {
+        private static final String[] VALUES = {"start", "stop"};
+
+        /**
+         * @see EnumeratedAttribute#getValues()
+         */
+        /** {...@inheritdoc}. */
+        public String[] getValues() {
+            return VALUES;
+        }
+    }
+    
+    
+    //-------------------------------------------------------------------------
+
+    
+    /**
+     * Implements the <i>AOP</i>-part of the autoconf task (the real autoconf 
logic).
+     * When starting a task, it searches for the properties and applies them.
+     */
+    public class AutoconfListener implements BuildListener {
+
+       /** Prefix for the property name. */
+        private String prefix;
+
+               public AutoconfListener(String prefix) {
+                       this.prefix = prefix;
+               }
+
+               public void buildStarted(BuildEvent event) {
+            // no-op
+        }
+
+        public void buildFinished(BuildEvent event) {
+            // no-op
+        }
+
+        public void targetStarted(BuildEvent event) {
+            // no-op
+        }
+
+        public void targetFinished(BuildEvent event) {
+            // no-op
+        }
+
+        public void taskFinished(BuildEvent event) {
+            // no-op
+        }
+
+        public void messageLogged(BuildEvent event) {
+            // no-op
+        }
+
+        public void taskStarted(BuildEvent event) {
+            UnknownElement ue = (UnknownElement) event.getTask();
+            
+            // Access to the properties which are available at 'call time'.
+            Hashtable properties = ue.getProject().getProperties();
+            
+            // Name of the task, like 'echo', 'javac'
+            String name = ue.getTag();
+            
+            // Access to the task object before configuration
+            RuntimeConfigurable rc = ue.getWrapper();
+            
+            // Attributes set by parsing the build file: must not overwrite 
these!
+            Set alreadySetAttributes = rc.getAttributeMap().keySet();
+
+            // All supported attributes by the task
+            Set allAttributes = getTaskAttributes(name, ue.getProject());
+            
+            // Try to apply all properties with prefix 'prefix'+'taskname' to 
the task object
+            String propPrefix = prefix == null ? name + "." : prefix + name + 
".";
+            for (Iterator keys = properties.keySet().iterator(); 
keys.hasNext(); ) {
+               String propName = (String) keys.next();
+               if (!propName.startsWith(propPrefix)) {
+                       // skip properties which don't apply to the task
+                       continue;
+               }
+                       String attributeName = 
propName.substring(propPrefix.length());
+                       if (!alreadySetAttributes.contains(attributeName) && 
allAttributes.contains(attributeName)) {
+                               // Register the key-value pair for later 
setting via IntrospectionHelper
+                               // while doing UnknownElement.configure() --> 
RuntimeConfigurable.maybeConfigure()
+                               // --> IntrospectionHelper.setAttribute()
+                                   rc.setAttribute(attributeName, 
String.valueOf(properties.get(propName)));
+                       } else {
+                               // do not overwrite already set attributes
+                               // do not try to set attributes which do not 
exist
+                       }
+            }
+        }
+
+       /**
+        * Tries to analyse a task and retrieve the attributes.
+        * The algorithm creates a dummy instance as template and analyses
+        * that.
+        * @param name name of the task/datatype
+        * @param project Ant Project instance
+        * @return (notnull) list of attributes
+        */
+       private Set getTaskAttributes(String name, Project project) {
+               Object templateObject = createTemplateObject(name, project);
+               
+               if (templateObject == null) {
+                       // can not create a template object, so give up
+                       return new HashSet();
+               }
+               
+               if (templateObject instanceof MacroInstance) {
+                       // we can ask <macrodef> directly for its attributes
+                       return getMacrodefAttributes((MacroInstance) 
templateObject);
+               }
+               
+               // analyse class via reflection
+               Class templateClass = createTemplateClass(project, 
templateObject);
+               return getSetter(templateClass);
+       }
+
+               /**
+                * Returns a list of attributes with public setter for a given 
class. 
+                * @param templateClass class to analyze
+                * @return (notnull) list of attribute names
+                */
+               private Set getSetter(Class templateClass) {
+                       Set rv = new HashSet();
+                       Method[] methods = templateClass.getDeclaredMethods();
+               for (int i = 0; i < methods.length; i++) {
+                       Method m = methods[i];
+                       if (isSetter(m)) {
+                               String attributeName = m.getName().substring(3);
+                               attributeName = 
attributeName.substring(0,1).toLowerCase() + attributeName.substring(1);
+                               rv.add( attributeName );
+                       }
+               }
+               return rv;
+               }
+
+               /**
+                * Checks if a given method is a public setter,
+                * means is public, starts with <tt>set</tt> and requires one
+                * argument.
+                * This method allows return values.
+                * @param method method to check
+                * @return <tt>true</tt> if the method is a setter
+                */
+               private boolean isSetter(Method method) {
+                       return method.getName().startsWith("set") 
+                           && method.getParameterTypes().length == 1 
+                           && method.getModifiers()==Modifier.PUBLIC;
+               }
+
+               /**
+                * Creates a template class to a given object.
+                * Depending on the object type, it is asked differently.
+                * @param project Ant project instance
+                * @param templateObject template object
+                * @return template class for later analyze
+                */
+               private Class createTemplateClass(Project project, Object 
templateObject) {
+                       Class templateClass = null;
+               if (templateObject instanceof PreSetDefinition) {
+                       PreSetDefinition preset = (PreSetDefinition) 
templateObject;
+                       templateClass = preset.getTypeClass(project);
+               } else {
+                       templateClass = templateObject.getClass();
+               }
+                       return templateClass;
+               }
+
+               /**
+                * Returns the attributes of a <macrodef>.
+                * @param macro the macro instance
+                * @return attribute list
+                */
+               private Set getMacrodefAttributes(MacroInstance macro) {
+                       Set rv = new HashSet();
+                       MacroDef macroDef = macro.getMacroDef();
+                       List attributes = macroDef.getAttributes();
+                       for (Iterator it = attributes.iterator(); 
it.hasNext();) {
+                               MacroDef.Attribute object = 
(MacroDef.Attribute) it.next();
+                               rv.add(object.getName());
+                       }
+                       return rv;
+               }
+
+               /**
+                * Creates a template object for the task/datatype of the given 
name.
+                * @param name name of the task/datatype
+                * @param project Ant project instance for resolving the name
+                * @return template object
+                */
+               private Object createTemplateObject(String name, Project 
project) {
+                       Object templateObject = null;
+               try {
+                       templateObject = project.createTask(name);
+               } catch (Exception e) {
+                       // the task name is recognised but task creation fails.
+                       try {
+                               // retry as data type, e.g. <presetdef>
+                               templateObject = project.createDataType(name);
+                       } catch(Exception e2) {
+                               // give it up
+                       }
+               }
+                       return templateObject;
+               }
+        
+    }
+}
\ No newline at end of file

Added: ant/sandbox/autoconf/test.xml
URL: 
http://svn.apache.org/viewvc/ant/sandbox/autoconf/test.xml?rev=830928&view=auto
==============================================================================
--- ant/sandbox/autoconf/test.xml (added)
+++ ant/sandbox/autoconf/test.xml Thu Oct 29 13:18:16 2009
@@ -0,0 +1,7 @@
+<project>
+    <taskdef name="autoconf" 
classname="org.apache.tools.ant.taskdefs.AutoconfTask" 
classpath="build/classes"/>
+    <property name="echo.message" value="Default Message"/>
+    <autoconf/>
+    <echo/>
+    <echo message="Individual Message"/>
+</project>
\ No newline at end of file


Reply via email to