* change nested element name from param to attribute (now the same as scriptdef) * expermintal testing for @attribute notation - controlled by an attributeStyle attribute * checking if correct attribute/element names are used * samedefinition method overloaded git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@275105 13f79535-47bb-0310-9956-ffa450edef68master
@@ -11,7 +11,7 @@ | |||
<h3>Description</h3> | |||
<p> | |||
This defines a new task using a <sequential> or <parallel> | |||
nested task as a template. Nested elements <param> and | |||
nested task as a template. Nested elements <attribute> and | |||
<element> are used to specify attributes and elements of | |||
the new task. These get substituted into the <sequential> | |||
or <parallel> task when the new task is run. | |||
@@ -28,7 +28,7 @@ | |||
</tr> | |||
<tr> | |||
<td valign="top">name</td> | |||
<td valign="top">the name of the new definition</td> | |||
<td valign="top">The name of the new definition</td> | |||
<td valign="top" align="center">Yes</td> | |||
</tr> | |||
<tr> | |||
@@ -38,9 +38,20 @@ | |||
</td> | |||
<td valign="top" align="center">No</td> | |||
</tr> | |||
<tr> | |||
<td valign="top">attributestyle</td> | |||
<td valign="top"> | |||
<em>Temporary</em> | |||
this attribute specifies if the attribute is in ant style | |||
(i.e. ${attributeName}) or xpath style (i.e @attributeName). | |||
Valid values are "ant" and "xpath". The default value | |||
is "ant". | |||
</td> | |||
<td valign="top" align="center">No</td> | |||
</tr> | |||
</table> | |||
<h3>Parameters specified as nested elements</h3> | |||
<h4>param</h4> | |||
<h4>attribute</h4> | |||
<p> | |||
This is used to specify attributes of the new task. The values | |||
of the attributes get substituted into the templated task. | |||
@@ -52,6 +63,11 @@ | |||
task using the ant property notation - ${attribute name}. | |||
Note that is not an actual ant property. | |||
</p> | |||
<p> | |||
If the attribute style is set to "xpath", the attribute is | |||
specified in the body of the template task by prefixing the | |||
name with a "@". | |||
</p> | |||
<h3>Parameters</h3> | |||
<table border="1" cellpadding="2" cellspacing="0"> | |||
<tr> | |||
@@ -61,13 +77,13 @@ | |||
</tr> | |||
<tr> | |||
<td valign="top">name</td> | |||
<td valign="top">the name of the new attribute</td> | |||
<td valign="top">The name of the new attribute</td> | |||
<td valign="top" align="center">Yes</td> | |||
</tr> | |||
<tr> | |||
<td valign="top">default</td> | |||
<td valign="top"> | |||
the default value of the attribute. | |||
The default value of the attribute. | |||
</td> | |||
<td valign="top" align="center">No</td> | |||
</tr> | |||
@@ -87,13 +103,13 @@ | |||
</tr> | |||
<tr> | |||
<td valign="top">name</td> | |||
<td valign="top">the name of the new attribute</td> | |||
<td valign="top">The name of the new attribute</td> | |||
<td valign="top" align="center">Yes</td> | |||
</tr> | |||
<tr> | |||
<td valign="top">optional</td> | |||
<td valign="top"> | |||
if true this nested element is optional. Default is | |||
If true this nested element is optional. Default is | |||
false - i.e the nested element is required in | |||
the new task. | |||
</td> | |||
@@ -109,7 +125,7 @@ | |||
<blockquote> | |||
<pre> | |||
<macrodef name="testing"> | |||
<param name="v" default="NOT SET"/> | |||
<attribute name="v" default="NOT SET"/> | |||
<element name="some-tasks" optional="yes"/> | |||
<sequential> | |||
<echo>v is ${v}</echo> | |||
@@ -124,6 +140,23 @@ | |||
</testing> | |||
</pre> | |||
</blockquote> | |||
<p> | |||
The following fragment sets the attribute style to "xpath" | |||
for the macro definition <testing> and calls the | |||
macro. The fragment should output "attribute is this is a test". | |||
</p> | |||
<blockquote> | |||
<pre> | |||
<macrodef name="testing" attributestyle="xpath"> | |||
<attribute name="abc"/> | |||
<sequential> | |||
<echo>attribute is @abc</echo> | |||
</sequential> | |||
</macrodef> | |||
<testing abc="this is a test"/> | |||
</pre> | |||
</blockquote> | |||
<p> | |||
The following fragment defines a task called <call-cc> which | |||
take the attributes "target", "link" and "target.dir" and the | |||
@@ -134,9 +167,9 @@ | |||
<blockquote> | |||
<pre> | |||
<macrodef name="call-cc"> | |||
<param name="target"/> | |||
<param name="link"/> | |||
<param name="target.dir"/> | |||
<attribute name="target"/> | |||
<attribute name="link"/> | |||
<attribute name="target.dir"/> | |||
<element name="cc-elements"/> | |||
<sequential> | |||
<mkdir dir="${obj.dir}/${target}"/> | |||
@@ -2,7 +2,7 @@ | |||
<target name="simple"> | |||
<macrodef name="my.echo"> | |||
<param name="text"/> | |||
<attribute name="text"/> | |||
<sequential> | |||
<echo message="${text}"/> | |||
</sequential> | |||
@@ -12,7 +12,7 @@ | |||
<target name="text"> | |||
<macrodef name="my.echo"> | |||
<param name="text"/> | |||
<attribute name="text"/> | |||
<sequential> | |||
<echo>${text}</echo> | |||
</sequential> | |||
@@ -22,7 +22,7 @@ | |||
<target name="uri"> | |||
<macrodef name="echo" uri="abc"> | |||
<param name="text"/> | |||
<attribute name="text"/> | |||
<sequential> | |||
<echo message="${text}"/> | |||
</sequential> | |||
@@ -44,4 +44,15 @@ | |||
</nested> | |||
</nested> | |||
</target> | |||
<target name="xpathstyle"> | |||
<macrodef name="testing" attributestyle="xpath"> | |||
<attribute name="abc"/> | |||
<sequential> | |||
<echo>attribute is @abc@abc</echo> | |||
</sequential> | |||
</macrodef> | |||
<testing abc="this is a test"/> | |||
</target> | |||
</project> |
@@ -68,6 +68,8 @@ import org.apache.tools.ant.Task; | |||
import org.apache.tools.ant.TaskContainer; | |||
import org.apache.tools.ant.UnknownElement; | |||
import org.apache.tools.ant.types.EnumeratedAttribute; | |||
/** | |||
* Describe class <code>MacroDef</code> here. | |||
* | |||
@@ -78,9 +80,10 @@ public class MacroDef extends Task implements AntlibInterface, TaskContainer { | |||
private UnknownElement nestedTask; | |||
private String name; | |||
private String componentName; | |||
private List params = new ArrayList(); | |||
private List attributes = new ArrayList(); | |||
private Map elements = new HashMap(); | |||
private String uri; | |||
private String uri; | |||
private int attributeStyle = AttributeStyle.ANT; | |||
/** | |||
* Name of the definition | |||
@@ -105,6 +108,46 @@ public class MacroDef extends Task implements AntlibInterface, TaskContainer { | |||
this.uri = uri; | |||
} | |||
/** | |||
* Enumerated type for attributeStyle attribute | |||
* | |||
* @see EnumeratedAttribute | |||
*/ | |||
public static class AttributeStyle extends EnumeratedAttribute { | |||
/** Enumerated values */ | |||
public static final int ANT = 0, XPATH = 1; | |||
/** | |||
* get the values | |||
* @return an array of the allowed values for this attribute. | |||
*/ | |||
public String[] getValues() { | |||
return new String[] {"ant", "xpath"}; | |||
} | |||
} | |||
/** | |||
* <em>Expermential</em> | |||
* I am uncertain at the moment how to encode attributes | |||
* using ant style ${attribute} or xpath style @attribute. | |||
* The first may get mixed up with ant properties and | |||
* the second may get mixed up with xpath. | |||
* The default at the moment is ant s | |||
* | |||
* @param style an <code>AttributeStyle</code> value | |||
*/ | |||
public void setAttributeStyle(AttributeStyle style) { | |||
attributeStyle = style.getIndex(); | |||
} | |||
/** | |||
* <em>Expermential</em> | |||
* @return the attribute style | |||
*/ | |||
public int getAttributeStyle() { | |||
return attributeStyle; | |||
} | |||
/** | |||
* Set the class loader. | |||
* Not used | |||
@@ -139,10 +182,10 @@ public class MacroDef extends Task implements AntlibInterface, TaskContainer { | |||
} | |||
/** | |||
* @return the nested Params | |||
* @return the nested Attributes | |||
*/ | |||
public List getParams() { | |||
return params; | |||
public List getAttributes() { | |||
return attributes; | |||
} | |||
/** | |||
@@ -153,16 +196,46 @@ public class MacroDef extends Task implements AntlibInterface, TaskContainer { | |||
} | |||
/** | |||
* Add a param element. | |||
* Check if a character is a valid character for an element or | |||
* attribute name | |||
* @param c the character to check | |||
* @return true if the character is a letter or digit or '.' or '-' | |||
* attribute name | |||
*/ | |||
public static boolean isValidNameCharacter(char c) { | |||
// ? is there an xml api for this ? | |||
return Character.isLetterOrDigit(c) || c == '.' || c == '-'; | |||
} | |||
/** | |||
* Check if a string is a valid name for an element or | |||
* attribute | |||
* @param name the string to check | |||
* @return true if the name consists of valid name characters | |||
*/ | |||
private static boolean isValidName(String name) { | |||
if (name.length() == 0) { | |||
return false; | |||
} | |||
for (int i = 0; i < name.length(); ++i) { | |||
if (!isValidNameCharacter(name.charAt(i))) { | |||
return false; | |||
} | |||
} | |||
return true; | |||
} | |||
/** | |||
* Add an attribute element. | |||
* | |||
* @param param a param nested element. | |||
* @param attribute an attribute nested element. | |||
*/ | |||
public void addConfiguredParam(Param param) { | |||
if (param.getName() == null) { | |||
public void addConfiguredAttribute(Attribute attribute) { | |||
if (attribute.getName() == null) { | |||
throw new BuildException( | |||
"the param nested element needed a \"name\" attribute"); | |||
"the attribute nested element needed a \"name\" attribute"); | |||
} | |||
params.add(param); | |||
attributes.add(attribute); | |||
} | |||
/** | |||
@@ -207,20 +280,24 @@ public class MacroDef extends Task implements AntlibInterface, TaskContainer { | |||
* A nested element for the MacroDef task. | |||
* | |||
*/ | |||
public static class Param { | |||
public static class Attribute { | |||
private String name; | |||
private String defaultValue; | |||
/** | |||
* The name of the parameter. | |||
* The name of the attribute. | |||
* | |||
* @param name the name of the parameter | |||
* @param name the name of the attribute | |||
*/ | |||
public void setName(String name) { | |||
if (!isValidName(name)) { | |||
throw new BuildException( | |||
"Illegal name [" + name + "] for attribute"); | |||
} | |||
this.name = name; | |||
} | |||
/** | |||
* @return the name of the parameter. | |||
* @return the name of the attribute | |||
*/ | |||
public String getName() { | |||
return name; | |||
@@ -257,6 +334,10 @@ public class MacroDef extends Task implements AntlibInterface, TaskContainer { | |||
* @param name the name of the element. | |||
*/ | |||
public void setName(String name) { | |||
if (!isValidName(name)) { | |||
throw new BuildException( | |||
"Illegal name [" + name + "] for attribute"); | |||
} | |||
this.name = name; | |||
} | |||
@@ -316,5 +397,17 @@ public class MacroDef extends Task implements AntlibInterface, TaskContainer { | |||
((MacroInstance) o).setTemplate(template); | |||
return o; | |||
} | |||
/** | |||
* Equality method for this definition | |||
* This only checks for pointer equality. | |||
* | |||
* @param other another definition | |||
* @param project the current project | |||
* @return true if the definitions are the same | |||
*/ | |||
public boolean sameDefinition(AntTypeDefinition other, Project project) { | |||
return this == other; | |||
} | |||
} | |||
} |
@@ -146,7 +146,7 @@ public class MacroInstance extends Task implements DynamicConfigurator { | |||
} | |||
} | |||
private static String macroSubs(String s, Map macroMapping) { | |||
private String macroSubsAnt(String s, Map macroMapping) { | |||
StringBuffer ret = new StringBuffer(); | |||
StringBuffer macroName = new StringBuffer(); | |||
boolean inMacro = false; | |||
@@ -179,6 +179,59 @@ public class MacroInstance extends Task implements DynamicConfigurator { | |||
return ret.toString(); | |||
} | |||
private String macroSubsXPath(String s, Map macroMapping) { | |||
StringBuffer ret = new StringBuffer(); | |||
StringBuffer macroName = new StringBuffer(); | |||
boolean inMacro = false; | |||
for (int i = 0; i < s.length(); ++i) { | |||
char c = s.charAt(i); | |||
if (!inMacro) { | |||
if (c == '@') { | |||
inMacro = true; | |||
} else { | |||
ret.append(c); | |||
} | |||
} else { | |||
if (MacroDef.isValidNameCharacter(c)) { | |||
macroName.append(c); | |||
} else { | |||
inMacro = false; | |||
String name = macroName.toString(); | |||
String value = (String) macroMapping.get(name); | |||
if (value == null) { | |||
ret.append("@" + name); | |||
} else { | |||
ret.append(value); | |||
} | |||
if (c == '@') { | |||
inMacro = true; | |||
} else { | |||
ret.append(c); | |||
} | |||
macroName = new StringBuffer(); | |||
} | |||
} | |||
} | |||
if (inMacro) { | |||
String name = macroName.toString(); | |||
String value = (String) macroMapping.get(name); | |||
if (value == null) { | |||
ret.append("@" + name); | |||
} else { | |||
ret.append(value); | |||
} | |||
} | |||
return ret.toString(); | |||
} | |||
private String macroSubs(String s, Map macroMapping) { | |||
if (template.getAttributeStyle() == MacroDef.AttributeStyle.ANT) { | |||
return macroSubsAnt(s, macroMapping); | |||
} else { | |||
return macroSubsXPath(s, macroMapping); | |||
} | |||
} | |||
private UnknownElement copy(UnknownElement ue) { | |||
UnknownElement ret = new UnknownElement(ue.getTag()); | |||
ret.setNamespace(ue.getNamespace()); | |||
@@ -234,25 +287,26 @@ public class MacroInstance extends Task implements DynamicConfigurator { | |||
/** | |||
* Execute the templates instance. | |||
* Copies the unknown element, substitutes the parameters, | |||
* Copies the unknown element, substitutes the attributes, | |||
* and calls perform on the unknown element. | |||
* | |||
*/ | |||
public void execute() { | |||
localProperties = new Hashtable(); | |||
Set copyKeys = new HashSet(map.keySet()); | |||
for (int i = 0; i < template.getParams().size(); ++i) { | |||
MacroDef.Param param = (MacroDef.Param) template.getParams().get(i); | |||
String value = (String) map.get(param.getName()); | |||
for (int i = 0; i < template.getAttributes().size(); ++i) { | |||
MacroDef.Attribute attribute = | |||
(MacroDef.Attribute) template.getAttributes().get(i); | |||
String value = (String) map.get(attribute.getName()); | |||
if (value == null) { | |||
value = param.getDefault(); | |||
value = attribute.getDefault(); | |||
} | |||
if (value == null) { | |||
throw new BuildException( | |||
"required parameter " + param.getName() + " not set"); | |||
"required attribute " + attribute.getName() + " not set"); | |||
} | |||
localProperties.put(param.getName(), value); | |||
copyKeys.remove(param.getName()); | |||
localProperties.put(attribute.getName(), value); | |||
copyKeys.remove(attribute.getName()); | |||
} | |||
if (copyKeys.size() != 0) { | |||
throw new BuildException( | |||
@@ -85,5 +85,9 @@ public class MacroDefTest extends BuildFileTest { | |||
public void testNested() { | |||
expectLog("nested", "A nested element"); | |||
} | |||
public void testXPathStyle() { | |||
expectLog("xpathstyle", "attribute is this is a testthis is a test"); | |||
} | |||
} | |||