specifying/testing name validity. * NameValidator is used by DefaultProjectBuilder for project and target names, and by DefaultTaskContext for Property names. * Added ProjectException, thrown by ProjectBuilder.build(). * Tidy-up error messages for project building errors. * Added a bunch of tests Submitted by Darrell DeBoer [darrell@apache.org] git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@271810 13f79535-47bb-0310-9956-ffa450edef68master
@@ -50,4 +50,16 @@ | |||||
<property-test-type value="value 3"/> | <property-test-type value="value 3"/> | ||||
</property> | </property> | ||||
</target> | </target> | ||||
<!-- Test properties with invalid names --> | |||||
<target name="bad-prop-name1"> | |||||
<property name="badname!" value="value"/> | |||||
</target> | |||||
<target name="bad-prop-name2"> | |||||
<property name="bad name" value="value"/> | |||||
</target> | |||||
<target name="bad-prop-name3"> | |||||
<property name="" value="value"/> | |||||
</target> | |||||
</project> | </project> |
@@ -0,0 +1,4 @@ | |||||
<!-- Project with an invalid name --> | |||||
<project version="2.0" name="!badname"> | |||||
<target name="main"/> | |||||
</project> |
@@ -0,0 +1,5 @@ | |||||
<!-- Target with an invalid name --> | |||||
<project version="2.0" name="ok name"> | |||||
<target name="main"/> | |||||
<target name="bad ^ name"/> | |||||
</project> |
@@ -8,6 +8,7 @@ | |||||
package org.apache.myrmidon.components.builder; | package org.apache.myrmidon.components.builder; | ||||
import java.io.File; | import java.io.File; | ||||
import java.net.MalformedURLException; | |||||
import java.net.URL; | import java.net.URL; | ||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.HashMap; | import java.util.HashMap; | ||||
@@ -22,11 +23,13 @@ import org.apache.avalon.framework.configuration.Configuration; | |||||
import org.apache.avalon.framework.configuration.ConfigurationException; | import org.apache.avalon.framework.configuration.ConfigurationException; | ||||
import org.apache.avalon.framework.configuration.SAXConfigurationHandler; | import org.apache.avalon.framework.configuration.SAXConfigurationHandler; | ||||
import org.apache.avalon.framework.logger.AbstractLogEnabled; | import org.apache.avalon.framework.logger.AbstractLogEnabled; | ||||
import org.apache.myrmidon.framework.conditions.Condition; | |||||
import org.apache.myrmidon.framework.conditions.AndCondition; | import org.apache.myrmidon.framework.conditions.AndCondition; | ||||
import org.apache.myrmidon.framework.conditions.Condition; | |||||
import org.apache.myrmidon.framework.conditions.IsSetCondition; | import org.apache.myrmidon.framework.conditions.IsSetCondition; | ||||
import org.apache.myrmidon.framework.conditions.NotCondition; | import org.apache.myrmidon.framework.conditions.NotCondition; | ||||
import org.apache.myrmidon.interfaces.builder.ProjectBuilder; | import org.apache.myrmidon.interfaces.builder.ProjectBuilder; | ||||
import org.apache.myrmidon.interfaces.builder.ProjectException; | |||||
import org.apache.myrmidon.interfaces.model.DefaultNameValidator; | |||||
import org.apache.myrmidon.interfaces.model.Project; | import org.apache.myrmidon.interfaces.model.Project; | ||||
import org.apache.myrmidon.interfaces.model.Target; | import org.apache.myrmidon.interfaces.model.Target; | ||||
import org.apache.myrmidon.interfaces.model.TypeLib; | import org.apache.myrmidon.interfaces.model.TypeLib; | ||||
@@ -37,6 +40,7 @@ import org.xml.sax.XMLReader; | |||||
* | * | ||||
* @author <a href="mailto:peter@apache.org">Peter Donald</a> | * @author <a href="mailto:peter@apache.org">Peter Donald</a> | ||||
* @version $Revision$ $Date$ | * @version $Revision$ $Date$ | ||||
* | |||||
* @ant:type type="project-builder" name="xml" | * @ant:type type="project-builder" name="xml" | ||||
* @ant:type type="project-builder" name="ant" | * @ant:type type="project-builder" name="ant" | ||||
*/ | */ | ||||
@@ -54,36 +58,37 @@ public class DefaultProjectBuilder | |||||
private final static int IMPLICIT_TASKS = 2; | private final static int IMPLICIT_TASKS = 2; | ||||
private final static int TARGETS = 3; | private final static int TARGETS = 3; | ||||
// Use a name validator with the default rules. | |||||
private DefaultNameValidator m_nameValidator = new DefaultNameValidator(); | |||||
/** | /** | ||||
* build a project from file. | * build a project from file. | ||||
* | * | ||||
* @param source the source | * @param source the source | ||||
* @return the constructed Project | * @return the constructed Project | ||||
* @exception Exception if an error occurs | |||||
* @exception ProjectException if an error occurs | |||||
*/ | */ | ||||
public Project build( final String source ) | public Project build( final String source ) | ||||
throws Exception | |||||
throws ProjectException | |||||
{ | { | ||||
final File file = new File( source ); | final File file = new File( source ); | ||||
return build( file, new HashMap() ); | return build( file, new HashMap() ); | ||||
} | } | ||||
private Project build( final File file, final HashMap projects ) | private Project build( final File file, final HashMap projects ) | ||||
throws Exception | |||||
throws ProjectException | |||||
{ | { | ||||
final URL systemID = file.toURL(); | |||||
final URL systemID = extractURL( file ); | |||||
final Project result = (Project)projects.get( systemID.toString() ); | final Project result = (Project)projects.get( systemID.toString() ); | ||||
if( null != result ) | if( null != result ) | ||||
{ | { | ||||
return result; | return result; | ||||
} | } | ||||
final SAXConfigurationHandler handler = new SAXConfigurationHandler(); | |||||
process( systemID, handler ); | |||||
final Configuration configuration = handler.getConfiguration(); | |||||
// Parse the project file | |||||
final Configuration configuration = parseProject( systemID ); | |||||
// Build the project model | |||||
final DefaultProject project = buildProject( file, configuration ); | final DefaultProject project = buildProject( file, configuration ); | ||||
projects.put( systemID.toString(), project ); | projects.put( systemID.toString(), project ); | ||||
@@ -94,20 +99,33 @@ public class DefaultProjectBuilder | |||||
return project; | return project; | ||||
} | } | ||||
protected void process( final URL systemID, | |||||
final SAXConfigurationHandler handler ) | |||||
throws Exception | |||||
/** | |||||
* Parses the project. | |||||
*/ | |||||
private Configuration parseProject( final URL systemID ) | |||||
throws ProjectException | |||||
{ | { | ||||
final SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); | |||||
final SAXParser saxParser = saxParserFactory.newSAXParser(); | |||||
final XMLReader parser = saxParser.getXMLReader(); | |||||
parser.setFeature( "http://xml.org/sax/features/namespace-prefixes", false ); | |||||
parser.setFeature( "http://xml.org/sax/features/namespaces", false ); | |||||
//parser.setFeature( "http://xml.org/sax/features/validation", false ); | |||||
parser.setContentHandler( handler ); | |||||
parser.setErrorHandler( handler ); | |||||
parser.parse( systemID.toString() ); | |||||
try | |||||
{ | |||||
final SAXConfigurationHandler handler = new SAXConfigurationHandler(); | |||||
final SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); | |||||
final SAXParser saxParser = saxParserFactory.newSAXParser(); | |||||
final XMLReader parser = saxParser.getXMLReader(); | |||||
parser.setFeature( "http://xml.org/sax/features/namespace-prefixes", false ); | |||||
parser.setFeature( "http://xml.org/sax/features/namespaces", false ); | |||||
//parser.setFeature( "http://xml.org/sax/features/validation", false ); | |||||
parser.setContentHandler( handler ); | |||||
parser.setErrorHandler( handler ); | |||||
parser.parse( systemID.toString() ); | |||||
return handler.getConfiguration(); | |||||
} | |||||
catch( Exception e ) | |||||
{ | |||||
String message = REZ.getString( "ant.project-parse.error" ); | |||||
throw new ProjectException( message, e ); | |||||
} | |||||
} | } | ||||
/** | /** | ||||
@@ -116,23 +134,20 @@ public class DefaultProjectBuilder | |||||
* @param file the file from which configuration was loaded | * @param file the file from which configuration was loaded | ||||
* @param configuration the configuration loaded | * @param configuration the configuration loaded | ||||
* @return the created Project | * @return the created Project | ||||
* @exception Exception if an error occurs | |||||
* @exception Exception if an error occurs | |||||
* @exception ConfigurationException if an error occurs | |||||
* @exception ProjectException if an error occurs building the project | |||||
*/ | */ | ||||
private DefaultProject buildProject( final File file, | private DefaultProject buildProject( final File file, | ||||
final Configuration configuration ) | final Configuration configuration ) | ||||
throws Exception | |||||
throws ProjectException | |||||
{ | { | ||||
if( !configuration.getName().equals( "project" ) ) | if( !configuration.getName().equals( "project" ) ) | ||||
{ | { | ||||
final String message = REZ.getString( "ant.no-project-element.error" ); | final String message = REZ.getString( "ant.no-project-element.error" ); | ||||
throw new Exception( message ); | |||||
throw new ProjectException( message ); | |||||
} | } | ||||
//get project-level attributes | //get project-level attributes | ||||
final String projectName = configuration.getAttribute( "name", | |||||
FileUtil.removeExtension( file.getName() ) ); | |||||
final String projectName = getProjectName( configuration, file ); | |||||
final String baseDirectoryName = configuration.getAttribute( "basedir", null ); | final String baseDirectoryName = configuration.getAttribute( "basedir", null ); | ||||
final String defaultTarget = configuration.getAttribute( "default", "main" ); | final String defaultTarget = configuration.getAttribute( "default", "main" ); | ||||
final Version version = getVersion( configuration ); | final Version version = getVersion( configuration ); | ||||
@@ -141,7 +156,7 @@ public class DefaultProjectBuilder | |||||
{ | { | ||||
final String message = | final String message = | ||||
REZ.getString( "ant.bad-version.error", VERSION, version ); | REZ.getString( "ant.bad-version.error", VERSION, version ); | ||||
throw new Exception( message ); | |||||
throw new ProjectException( message ); | |||||
} | } | ||||
//determine base directory for project. Use the directory containing | //determine base directory for project. Use the directory containing | ||||
@@ -168,12 +183,52 @@ public class DefaultProjectBuilder | |||||
return project; | return project; | ||||
} | } | ||||
/** | |||||
* Get the project name from the configuration, or create a default name if none | |||||
* was supplied. | |||||
*/ | |||||
private String getProjectName( final Configuration configuration, final File file ) | |||||
throws ProjectException | |||||
{ | |||||
String projectName = configuration.getAttribute( "name", null ); | |||||
if( projectName == null ) | |||||
{ | |||||
// Create a name based on the file name. | |||||
String fileNameBase = FileUtil.removeExtension( file.getName() ); | |||||
try | |||||
{ | |||||
projectName = m_nameValidator.makeValidName( fileNameBase ); | |||||
} | |||||
catch( Exception e ) | |||||
{ | |||||
String message = REZ.getString( "ant.project-create-name.error" ); | |||||
throw new ProjectException( message, e ); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
// Make sure the supplied name is valid. | |||||
try | |||||
{ | |||||
m_nameValidator.validate( projectName ); | |||||
} | |||||
catch( Exception e ) | |||||
{ | |||||
String message = REZ.getString( "ant.project-bad-name.error" ); | |||||
throw new ProjectException( message, e ); | |||||
} | |||||
} | |||||
return projectName; | |||||
} | |||||
/** | /** | ||||
* Retrieve the version attribute from the specified configuration element. | * Retrieve the version attribute from the specified configuration element. | ||||
* Throw exceptions with meaningful errors if malformed or missing. | * Throw exceptions with meaningful errors if malformed or missing. | ||||
*/ | */ | ||||
private Version getVersion( final Configuration configuration ) | private Version getVersion( final Configuration configuration ) | ||||
throws Exception | |||||
throws ProjectException | |||||
{ | { | ||||
try | try | ||||
{ | { | ||||
@@ -183,7 +238,7 @@ public class DefaultProjectBuilder | |||||
catch( final ConfigurationException ce ) | catch( final ConfigurationException ce ) | ||||
{ | { | ||||
final String message = REZ.getString( "ant.version-missing.error" ); | final String message = REZ.getString( "ant.version-missing.error" ); | ||||
throw new ConfigurationException( message, ce ); | |||||
throw new ProjectException( message, ce ); | |||||
} | } | ||||
} | } | ||||
@@ -191,7 +246,7 @@ public class DefaultProjectBuilder | |||||
* Utility function to extract version | * Utility function to extract version | ||||
*/ | */ | ||||
private Version parseVersion( final String versionString ) | private Version parseVersion( final String versionString ) | ||||
throws Exception | |||||
throws ProjectException | |||||
{ | { | ||||
try | try | ||||
@@ -202,8 +257,7 @@ public class DefaultProjectBuilder | |||||
{ | { | ||||
final String message = | final String message = | ||||
REZ.getString( "ant.malformed.version", versionString ); | REZ.getString( "ant.malformed.version", versionString ); | ||||
getLogger().warn( message ); | |||||
throw new ConfigurationException( message, e ); | |||||
throw new ProjectException( message, e ); | |||||
} | } | ||||
} | } | ||||
@@ -212,12 +266,12 @@ public class DefaultProjectBuilder | |||||
* | * | ||||
* @param project the project | * @param project the project | ||||
* @param configuration the Configuration | * @param configuration the Configuration | ||||
* @exception Exception if an error occurs | |||||
* @exception ProjectException if an error occurs | |||||
*/ | */ | ||||
private void buildTopLevelProject( final DefaultProject project, | private void buildTopLevelProject( final DefaultProject project, | ||||
final Configuration configuration, | final Configuration configuration, | ||||
final HashMap projects ) | final HashMap projects ) | ||||
throws Exception | |||||
throws ProjectException | |||||
{ | { | ||||
final ArrayList implicitTaskList = new ArrayList(); | final ArrayList implicitTaskList = new ArrayList(); | ||||
final Configuration[] children = configuration.getChildren(); | final Configuration[] children = configuration.getChildren(); | ||||
@@ -277,7 +331,7 @@ public class DefaultProjectBuilder | |||||
{ | { | ||||
final String message = | final String message = | ||||
REZ.getString( "ant.unknown-toplevel-element.error", name, element.getLocation() ); | REZ.getString( "ant.unknown-toplevel-element.error", name, element.getLocation() ); | ||||
throw new Exception( message ); | |||||
throw new ProjectException( message ); | |||||
} | } | ||||
} | } | ||||
@@ -291,7 +345,7 @@ public class DefaultProjectBuilder | |||||
private void buildProjectRef( final DefaultProject project, | private void buildProjectRef( final DefaultProject project, | ||||
final Configuration element, | final Configuration element, | ||||
final HashMap projects ) | final HashMap projects ) | ||||
throws Exception | |||||
throws ProjectException | |||||
{ | { | ||||
final String name = element.getAttribute( "name", null ); | final String name = element.getAttribute( "name", null ); | ||||
final String location = element.getAttribute( "location", null ); | final String location = element.getAttribute( "location", null ); | ||||
@@ -300,27 +354,31 @@ public class DefaultProjectBuilder | |||||
{ | { | ||||
final String message = | final String message = | ||||
REZ.getString( "ant.projectref-no-name.error", element.getLocation() ); | REZ.getString( "ant.projectref-no-name.error", element.getLocation() ); | ||||
throw new Exception( message ); | |||||
throw new ProjectException( message ); | |||||
} | } | ||||
if( !validName( name ) ) | |||||
try | |||||
{ | |||||
m_nameValidator.validate( name ); | |||||
} | |||||
catch( Exception e ) | |||||
{ | { | ||||
final String message = | final String message = | ||||
REZ.getString( "ant.projectref-bad-name.error", element.getLocation() ); | REZ.getString( "ant.projectref-bad-name.error", element.getLocation() ); | ||||
throw new Exception( message ); | |||||
throw new ProjectException( message, e ); | |||||
} | } | ||||
if( null == location ) | if( null == location ) | ||||
{ | { | ||||
final String message = | final String message = | ||||
REZ.getString( "ant.projectref-no-location.error", element.getLocation() ); | REZ.getString( "ant.projectref-no-location.error", element.getLocation() ); | ||||
throw new Exception( message ); | |||||
throw new ProjectException( message ); | |||||
} | } | ||||
// Build the URL of the referenced projects | // Build the URL of the referenced projects | ||||
final File baseDirectory = project.getBaseDirectory(); | final File baseDirectory = project.getBaseDirectory(); | ||||
final File file = FileUtil.resolveFile( baseDirectory, location ); | final File file = FileUtil.resolveFile( baseDirectory, location ); | ||||
final String systemID = file.toURL().toString(); | |||||
final String systemID = extractURL( file ).toString(); | |||||
// Locate the referenced project, building it if necessary | // Locate the referenced project, building it if necessary | ||||
Project other = (Project)projects.get( systemID ); | Project other = (Project)projects.get( systemID ); | ||||
@@ -333,9 +391,22 @@ public class DefaultProjectBuilder | |||||
project.addProject( name, other ); | project.addProject( name, other ); | ||||
} | } | ||||
private URL extractURL( final File file ) throws ProjectException | |||||
{ | |||||
try | |||||
{ | |||||
return file.toURL(); | |||||
} | |||||
catch( MalformedURLException e ) | |||||
{ | |||||
final String message = REZ.getString( "ant.project-unexpected.error" ); | |||||
throw new ProjectException( message, e ); | |||||
} | |||||
} | |||||
private void buildTypeLib( final DefaultProject project, | private void buildTypeLib( final DefaultProject project, | ||||
final Configuration element ) | final Configuration element ) | ||||
throws Exception | |||||
throws ProjectException | |||||
{ | { | ||||
final String library = element.getAttribute( "library", null ); | final String library = element.getAttribute( "library", null ); | ||||
final String name = element.getAttribute( "name", null ); | final String name = element.getAttribute( "name", null ); | ||||
@@ -345,7 +416,7 @@ public class DefaultProjectBuilder | |||||
{ | { | ||||
final String message = | final String message = | ||||
REZ.getString( "ant.import-no-library.error", element.getLocation() ); | REZ.getString( "ant.import-no-library.error", element.getLocation() ); | ||||
throw new Exception( message ); | |||||
throw new ProjectException( message ); | |||||
} | } | ||||
if( null == name || null == type ) | if( null == name || null == type ) | ||||
@@ -354,7 +425,7 @@ public class DefaultProjectBuilder | |||||
{ | { | ||||
final String message = | final String message = | ||||
REZ.getString( "ant.import-malformed.error", element.getLocation() ); | REZ.getString( "ant.import-malformed.error", element.getLocation() ); | ||||
throw new Exception( message ); | |||||
throw new ProjectException( message ); | |||||
} | } | ||||
} | } | ||||
@@ -368,14 +439,14 @@ public class DefaultProjectBuilder | |||||
* @param target the Configuration | * @param target the Configuration | ||||
*/ | */ | ||||
private void buildTarget( final DefaultProject project, final Configuration target ) | private void buildTarget( final DefaultProject project, final Configuration target ) | ||||
throws Exception | |||||
throws ProjectException | |||||
{ | { | ||||
final String name = target.getAttribute( "name", null ); | final String name = target.getAttribute( "name", null ); | ||||
final String depends = target.getAttribute( "depends", null ); | final String depends = target.getAttribute( "depends", null ); | ||||
final String ifCondition = target.getAttribute( "if", null ); | final String ifCondition = target.getAttribute( "if", null ); | ||||
final String unlessCondition = target.getAttribute( "unless", null ); | final String unlessCondition = target.getAttribute( "unless", null ); | ||||
verifyName( name, target ); | |||||
verifyTargetName( name, target ); | |||||
if( getLogger().isDebugEnabled() ) | if( getLogger().isDebugEnabled() ) | ||||
{ | { | ||||
@@ -392,24 +463,30 @@ public class DefaultProjectBuilder | |||||
project.addTarget( name, defaultTarget ); | project.addTarget( name, defaultTarget ); | ||||
} | } | ||||
private void verifyName( final String name, final Configuration target ) throws Exception | |||||
private void verifyTargetName( final String name, final Configuration target ) | |||||
throws ProjectException | |||||
{ | { | ||||
if( null == name ) | if( null == name ) | ||||
{ | { | ||||
final String message = | final String message = | ||||
REZ.getString( "ant.target-noname.error", target.getLocation() ); | REZ.getString( "ant.target-noname.error", target.getLocation() ); | ||||
throw new Exception( message ); | |||||
throw new ProjectException( message ); | |||||
} | } | ||||
if( !validName( name ) ) | |||||
try | |||||
{ | |||||
m_nameValidator.validate( name ); | |||||
} | |||||
catch( Exception e ) | |||||
{ | { | ||||
final String message = | final String message = | ||||
REZ.getString( "ant.target-bad-name.error", target.getLocation() ); | REZ.getString( "ant.target-bad-name.error", target.getLocation() ); | ||||
throw new Exception( message ); | |||||
throw new ProjectException( message, e ); | |||||
} | } | ||||
} | } | ||||
private String[] buildDependsList( final String depends, final Configuration target ) throws Exception | |||||
private String[] buildDependsList( final String depends, final Configuration target ) | |||||
throws ProjectException | |||||
{ | { | ||||
String[] dependencies = null; | String[] dependencies = null; | ||||
@@ -428,7 +505,7 @@ public class DefaultProjectBuilder | |||||
final String message = REZ.getString( "ant.target-bad-dependency.error", | final String message = REZ.getString( "ant.target-bad-dependency.error", | ||||
target.getName(), | target.getName(), | ||||
target.getLocation() ); | target.getLocation() ); | ||||
throw new Exception( message ); | |||||
throw new ProjectException( message ); | |||||
} | } | ||||
if( getLogger().isDebugEnabled() ) | if( getLogger().isDebugEnabled() ) | ||||
@@ -447,7 +524,6 @@ public class DefaultProjectBuilder | |||||
private Condition buildCondition( final String ifCondition, | private Condition buildCondition( final String ifCondition, | ||||
final String unlessCondition ) | final String unlessCondition ) | ||||
throws Exception | |||||
{ | { | ||||
final AndCondition condition = new AndCondition(); | final AndCondition condition = new AndCondition(); | ||||
@@ -475,16 +551,4 @@ public class DefaultProjectBuilder | |||||
return condition; | return condition; | ||||
} | } | ||||
protected boolean validName( final String name ) | |||||
{ | |||||
if( -1 != name.indexOf( "->" ) ) | |||||
{ | |||||
return false; | |||||
} | |||||
else | |||||
{ | |||||
return true; | |||||
} | |||||
} | |||||
} | } |
@@ -10,10 +10,14 @@ ati.attribue-unquoted.error=Expecting the value of attribute {0} to be enclosed | |||||
ant.project-banner.notice=Project {0} base directory: {1}. | ant.project-banner.notice=Project {0} base directory: {1}. | ||||
ant.target-parse.notice=Parsing target: {0}. | ant.target-parse.notice=Parsing target: {0}. | ||||
ant.target-if.notice=Target if condition: {0} | ant.target-if.notice=Target if condition: {0} | ||||
ant.target- unless.notice=Target unless condition: {0} | |||||
ant.target-unless.notice=Target unless condition: {0} | |||||
ant.target-dependency.notice=Target dependency: {0} | ant.target-dependency.notice=Target dependency: {0} | ||||
ant.project-unexpected.error=Unexpected error building project. | |||||
ant.project-parse.error=Error parsing project. | |||||
ant.no-project-element.error=Project file must be enclosed in project element. | ant.no-project-element.error=Project file must be enclosed in project element. | ||||
ant.unknown-toplevel-element.error=Unknown top-level element {0} at {1}. | ant.unknown-toplevel-element.error=Unknown top-level element {0} at {1}. | ||||
ant.project-bad-name.error=Invalid project name. | |||||
ant.project-create-name.error=Could not create a name for this project. | |||||
ant.projectref-no-name.error=Malformed projectref without a name attribute at {0}. | ant.projectref-no-name.error=Malformed projectref without a name attribute at {0}. | ||||
ant.projectref-bad-name.error=Projectref with an invalid name attribute at {0}. | ant.projectref-bad-name.error=Projectref with an invalid name attribute at {0}. | ||||
ant.projectref-no-location.error=Malformed projectref without a location attribute at {0}. | ant.projectref-no-location.error=Malformed projectref without a location attribute at {0}. | ||||
@@ -20,6 +20,7 @@ import org.apache.avalon.framework.service.ServiceException; | |||||
import org.apache.avalon.framework.service.ServiceManager; | import org.apache.avalon.framework.service.ServiceManager; | ||||
import org.apache.myrmidon.api.TaskContext; | import org.apache.myrmidon.api.TaskContext; | ||||
import org.apache.myrmidon.api.TaskException; | import org.apache.myrmidon.api.TaskException; | ||||
import org.apache.myrmidon.interfaces.model.DefaultNameValidator; | |||||
import org.apache.myrmidon.interfaces.workspace.PropertyResolver; | import org.apache.myrmidon.interfaces.workspace.PropertyResolver; | ||||
/** | /** | ||||
@@ -34,6 +35,12 @@ public class DefaultTaskContext | |||||
private final static Resources REZ = | private final static Resources REZ = | ||||
ResourceManager.getPackageResources( DefaultTaskContext.class ); | ResourceManager.getPackageResources( DefaultTaskContext.class ); | ||||
// Property name validator allows digits, but no internal whitespace. | |||||
private static DefaultNameValidator m_propertyNameValidator = new DefaultNameValidator(); | |||||
static { | |||||
m_propertyNameValidator.setAllowInternalWhitespace( false ); | |||||
} | |||||
private final Map m_contextData = new Hashtable(); | private final Map m_contextData = new Hashtable(); | ||||
private final TaskContext m_parent; | private final TaskContext m_parent; | ||||
private ServiceManager m_serviceManager; | private ServiceManager m_serviceManager; | ||||
@@ -199,6 +206,7 @@ public class DefaultTaskContext | |||||
public void setProperty( final String name, final Object value ) | public void setProperty( final String name, final Object value ) | ||||
throws TaskException | throws TaskException | ||||
{ | { | ||||
checkPropertyName( name ); | |||||
checkPropertyValid( name, value ); | checkPropertyValid( name, value ); | ||||
m_contextData.put( name, value ); | m_contextData.put( name, value ); | ||||
} | } | ||||
@@ -362,6 +370,22 @@ public class DefaultTaskContext | |||||
return value; | return value; | ||||
} | } | ||||
/** | |||||
* Checks that the supplied property name is valid. | |||||
*/ | |||||
private void checkPropertyName( final String name ) throws TaskException | |||||
{ | |||||
try | |||||
{ | |||||
m_propertyNameValidator.validate( name ); | |||||
} | |||||
catch( Exception e ) | |||||
{ | |||||
String message = REZ.getString( "bad-property-name.error" ); | |||||
throw new TaskException( message, e ); | |||||
} | |||||
} | |||||
/** | /** | ||||
* Make sure property is valid if it is one of the "magic" properties. | * Make sure property is valid if it is one of the "magic" properties. | ||||
* | * | ||||
@@ -13,6 +13,7 @@ exec-task.notice=Executing task {0}. | |||||
#DefaultTaskContext | #DefaultTaskContext | ||||
unknown-prop.error=Unknown property {0}. | unknown-prop.error=Unknown property {0}. | ||||
bad-property.error=Property {0} must have a value of type {1}. | bad-property.error=Property {0} must have a value of type {1}. | ||||
bad-property-name.error=Invalid property name. | |||||
null-resolved-value.error=Value "{0}" resolved to null. | null-resolved-value.error=Value "{0}" resolved to null. | ||||
bad-resolve.error=Unable to resolve value "{0}". | bad-resolve.error=Unable to resolve value "{0}". | ||||
bad-find-service.error=Could not find service "{0}". | bad-find-service.error=Could not find service "{0}". | ||||
@@ -25,8 +25,8 @@ public interface ProjectBuilder | |||||
* | * | ||||
* @param source the source | * @param source the source | ||||
* @return the constructed Project | * @return the constructed Project | ||||
* @exception Exception if an error occurs | |||||
* @exception ProjectException if an error occurs | |||||
*/ | */ | ||||
Project build( String source ) | Project build( String source ) | ||||
throws Exception; | |||||
throws ProjectException; | |||||
} | } |
@@ -0,0 +1,56 @@ | |||||
/* | |||||
* Copyright (C) The Apache Software Foundation. All rights reserved. | |||||
* | |||||
* This software is published under the terms of the Apache Software License | |||||
* version 1.1, a copy of which has been included with this distribution in | |||||
* the LICENSE.txt file. | |||||
*/ | |||||
package org.apache.myrmidon.interfaces.builder; | |||||
/** | |||||
* A cascading exception thrown on a problem constructing a Project model. | |||||
* | |||||
* @author <a href="mailto:darrell@apache.org">Darrell DeBoer</a> | |||||
* @version $Revision$ $Date$ | |||||
*/ | |||||
public class ProjectException | |||||
extends Exception | |||||
{ | |||||
/** | |||||
* If this exception is cascaded, the cause of this exception. | |||||
*/ | |||||
private final Throwable m_throwable; | |||||
/** | |||||
* Constructs an non-cascaded exception with a message | |||||
* | |||||
* @param message the message | |||||
*/ | |||||
public ProjectException( final String message ) | |||||
{ | |||||
this( message, null ); | |||||
} | |||||
/** | |||||
* Constructs a cascaded exception with the supplied message, which links the | |||||
* Throwable provided. | |||||
* | |||||
* @param message the message | |||||
* @param throwable the throwable that caused this exception | |||||
*/ | |||||
public ProjectException( final String message, final Throwable throwable ) | |||||
{ | |||||
super( message ); | |||||
m_throwable = throwable; | |||||
} | |||||
/** | |||||
* Retrieve root cause of the exception. | |||||
* | |||||
* @return the root cause | |||||
*/ | |||||
public final Throwable getCause() | |||||
{ | |||||
return m_throwable; | |||||
} | |||||
} |
@@ -0,0 +1,356 @@ | |||||
/* | |||||
* Copyright (C) The Apache Software Foundation. All rights reserved. | |||||
* | |||||
* This software is published under the terms of the Apache Software License | |||||
* version 1.1, a copy of which has been included with this distribution in | |||||
* the LICENSE.txt file. | |||||
*/ | |||||
package org.apache.myrmidon.interfaces.model; | |||||
import org.apache.avalon.excalibur.i18n.ResourceManager; | |||||
import org.apache.avalon.excalibur.i18n.Resources; | |||||
/** | |||||
* Simple helper class which determines the validity of names used | |||||
* in ant projects. | |||||
* | |||||
* @author <a href="mailto:darrell@apache.org">Darrell DeBoer</a> | |||||
* @version $Revision$ $Date$ | |||||
*/ | |||||
public class DefaultNameValidator | |||||
implements NameValidator | |||||
{ | |||||
private final static Resources REZ = | |||||
ResourceManager.getPackageResources( DefaultNameValidator.class ); | |||||
/** | |||||
* Determines whether the supplied name may include surrounding whitespace. | |||||
*/ | |||||
private boolean m_allowSurroundingWhitespace; | |||||
// Settings for initial characters. | |||||
private boolean m_allowInitialDigit; | |||||
private String m_additionalInitialCharacters; | |||||
// Settings for internal characters. | |||||
private boolean m_allowInternalDigits; | |||||
private boolean m_allowInternalWhitespace; | |||||
private String m_additionalInternalCharacters; | |||||
/** | |||||
* Construct a default name validator. | |||||
* Letters, digits and "_" are permitted as initial character. | |||||
* Letters, digits, whitespace and "_-." are permitted as internal characters. | |||||
* Surrounding whitespace is not permitted. | |||||
*/ | |||||
public DefaultNameValidator() | |||||
{ | |||||
this( false, true, "_", true, true, "_-." ); | |||||
} | |||||
/** | |||||
* Contstruct a NameValidator with the specified rules. | |||||
* @param allowSurroundingWhitespace | |||||
* specified if names are trimmed before checking | |||||
* @param allowInitialDigit | |||||
* specifies if digits are permitted as intial characters | |||||
* @param additionalInitialCharacters | |||||
* extra characters to allow as initial characters. | |||||
* @param allowInternalDigits | |||||
* specifies if digits are permitted as internal characters | |||||
* @param allowInternalWhitespace | |||||
* specifies if whitespace is permitted internally in names | |||||
* @param additionalInternalCharacters | |||||
* extra characters permitted in names | |||||
*/ | |||||
public DefaultNameValidator( final boolean allowSurroundingWhitespace, | |||||
final boolean allowInitialDigit, | |||||
final String additionalInitialCharacters, | |||||
final boolean allowInternalDigits, | |||||
final boolean allowInternalWhitespace, | |||||
final String additionalInternalCharacters ) | |||||
{ | |||||
setAllowSurroundingWhitespace( allowSurroundingWhitespace ); | |||||
setAllowInitialDigit( allowInitialDigit ); | |||||
setAdditionalInitialCharacters( additionalInitialCharacters ); | |||||
setAllowInternalDigits( allowInternalDigits ); | |||||
setAllowInternalWhitespace( allowInternalWhitespace ); | |||||
setAdditionalInternalCharacters( additionalInternalCharacters ); | |||||
} | |||||
/** | |||||
* Creates a valid name based on the supplied string value, removing invalid | |||||
* characters. If no valid characters are present, an exception is thrown. | |||||
* @param baseName the name used to construct the valid name | |||||
* @throws Exception if no valid name could be constructed. | |||||
*/ | |||||
public String makeValidName( final String baseName ) throws Exception | |||||
{ | |||||
final StringBuffer buffer = new StringBuffer( baseName ); | |||||
while( buffer.length() > 0 && !isValidInitialChar( buffer.charAt( 0 ) ) ) | |||||
{ | |||||
buffer.delete( 0, 1 ); | |||||
} | |||||
if( buffer.length() == 0 ) | |||||
{ | |||||
final String message = REZ.getString( "name.could-not-create.error", baseName ); | |||||
throw new Exception( message ); | |||||
} | |||||
for( int i = 1; i < buffer.length(); ) | |||||
{ | |||||
if( !isValidInternalChar( buffer.charAt( i ) ) ) | |||||
{ | |||||
buffer.delete( i, i + 1 ); | |||||
} | |||||
else | |||||
{ | |||||
i++; | |||||
} | |||||
} | |||||
return buffer.toString(); | |||||
} | |||||
/** | |||||
* Validates the supplied name, failing if it is not. | |||||
* @throws Exception is the supplied name is not valid. | |||||
*/ | |||||
public void validate( final String name ) throws Exception | |||||
{ | |||||
String testName = name; | |||||
// If surrounding whitespace is allowed, trim it. Otherwise, check. | |||||
if( m_allowSurroundingWhitespace ) | |||||
{ | |||||
testName = testName.trim(); | |||||
} | |||||
else | |||||
{ | |||||
checkSurroundingWhitespace( testName ); | |||||
} | |||||
// Zero-length name is invalid. | |||||
if( testName.length() == 0 ) | |||||
{ | |||||
final String message = REZ.getString( "name.zero-char-name.error" ); | |||||
throw new Exception( message ); | |||||
} | |||||
// Check first character. | |||||
final char initial = testName.charAt( 0 ); | |||||
checkInitialCharacter( initial, testName ); | |||||
// Check the rest of the characters. | |||||
for( int i = 1; i < testName.length(); i++ ) | |||||
{ | |||||
final char internal = testName.charAt( i ); | |||||
checkInternalCharacter( internal, testName ); | |||||
} | |||||
} | |||||
/** | |||||
* Checks if the supplied character is permitted as an internal character. | |||||
* @throws Exception if the character is not permitted | |||||
*/ | |||||
private void checkInternalCharacter( final char internal, final String name ) | |||||
throws Exception | |||||
{ | |||||
if( !isValidInternalChar( internal ) ) | |||||
{ | |||||
final String message = REZ.getString( "name.invalid-internal-char.error", | |||||
name, | |||||
describeValidInternalChars() ); | |||||
throw new Exception( message ); | |||||
} | |||||
} | |||||
/** | |||||
* Checks if the supplied character is permitted as an internal character. | |||||
* @throws Exception if the character is not permitted | |||||
*/ | |||||
private void checkInitialCharacter( final char initial, final String name ) | |||||
throws Exception | |||||
{ | |||||
if( !isValidInitialChar( initial ) ) | |||||
{ | |||||
final String message = REZ.getString( "name.invalid-initial-char.error", | |||||
name, | |||||
describeValidInitialChars() ); | |||||
throw new Exception( message ); | |||||
} | |||||
} | |||||
/** | |||||
* Checks the name for surrounding whitespace | |||||
* @throws Exception if surrounding whitespace is found | |||||
*/ | |||||
private void checkSurroundingWhitespace( final String testName ) | |||||
throws Exception | |||||
{ | |||||
if( testName.length() == 0 ) | |||||
{ | |||||
return; | |||||
} | |||||
if( Character.isWhitespace( testName.charAt( 0 ) ) || | |||||
Character.isWhitespace( testName.charAt( testName.length() - 1 ) ) ) | |||||
{ | |||||
final String message = | |||||
REZ.getString( "name.enclosing-whitespace.error", testName ); | |||||
throw new Exception( message ); | |||||
} | |||||
} | |||||
/** | |||||
* Determines if a character is allowed as the first character in a name. | |||||
* Valid characters are Letters, Digits, and defined initial characters ("_"). | |||||
* @param chr the character to be assessed | |||||
* @return <code>true</code> if the character can be the first character of a name | |||||
*/ | |||||
protected boolean isValidInitialChar( final char chr ) | |||||
{ | |||||
if( Character.isLetter( chr ) ) | |||||
{ | |||||
return true; | |||||
} | |||||
if( m_allowInitialDigit | |||||
&& Character.isDigit( chr ) ) | |||||
{ | |||||
return true; | |||||
} | |||||
if( m_additionalInitialCharacters.indexOf( chr ) != -1 ) | |||||
{ | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
/** | |||||
* Determines if a character is allowed as a non-initial character in a name. | |||||
* Valid characters are Letters, Digits, whitespace, and defined | |||||
* internal characters ("_-."). | |||||
* @param chr the character to be assessed | |||||
* @return <code>true</code> if the character can be included in a name | |||||
*/ | |||||
protected boolean isValidInternalChar( final char chr ) | |||||
{ | |||||
if( Character.isLetter( chr ) ) | |||||
{ | |||||
return true; | |||||
} | |||||
if( m_allowInternalDigits | |||||
&& Character.isDigit( chr ) ) | |||||
{ | |||||
return true; | |||||
} | |||||
if( m_allowInternalWhitespace | |||||
&& Character.isWhitespace( chr ) ) | |||||
{ | |||||
return true; | |||||
} | |||||
if( m_additionalInternalCharacters.indexOf( chr ) != -1 ) | |||||
{ | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
/** | |||||
* Builds a message detailing the valid initial characters. | |||||
*/ | |||||
protected String describeValidInitialChars() | |||||
{ | |||||
StringBuffer validChars = new StringBuffer( "letters" ); | |||||
if( m_allowInitialDigit ) | |||||
{ | |||||
validChars.append( ", digits" ); | |||||
} | |||||
validChars.append( ", and \"" ); | |||||
validChars.append( m_additionalInitialCharacters ); | |||||
validChars.append( "\"" ); | |||||
return validChars.toString(); | |||||
} | |||||
/** | |||||
* Builds a message detailing the valid internal characters. | |||||
*/ | |||||
protected String describeValidInternalChars() | |||||
{ | |||||
StringBuffer validChars = new StringBuffer( "letters" ); | |||||
if( m_allowInternalDigits ) | |||||
{ | |||||
validChars.append( ", digits" ); | |||||
} | |||||
if( m_allowInternalWhitespace ) | |||||
{ | |||||
validChars.append( ", whitespace" ); | |||||
} | |||||
validChars.append( ", and \"" ); | |||||
validChars.append( m_additionalInternalCharacters ); | |||||
validChars.append( "\"" ); | |||||
return validChars.toString(); | |||||
} | |||||
/** | |||||
* @param allowSurroundingWhitespace | |||||
* specified if names are trimmed before checking | |||||
*/ | |||||
public void setAllowSurroundingWhitespace( boolean allowSurroundingWhitespace ) | |||||
{ | |||||
m_allowSurroundingWhitespace = allowSurroundingWhitespace; | |||||
} | |||||
/** | |||||
* @param allowInitialDigit | |||||
* specifies if digits are permitted as intial characters | |||||
*/ | |||||
public void setAllowInitialDigit( boolean allowInitialDigit ) | |||||
{ | |||||
m_allowInitialDigit = allowInitialDigit; | |||||
} | |||||
/** | |||||
* @param additionalInitialCharacters | |||||
* extra characters to allow as initial characters. | |||||
*/ | |||||
public void setAdditionalInitialCharacters( String additionalInitialCharacters ) | |||||
{ | |||||
m_additionalInitialCharacters = additionalInitialCharacters; | |||||
} | |||||
/** | |||||
* @param allowInternalDigits | |||||
* specifies if digits are permitted as internal characters | |||||
*/ | |||||
public void setAllowInternalDigits( boolean allowInternalDigits ) | |||||
{ | |||||
m_allowInternalDigits = allowInternalDigits; | |||||
} | |||||
/** | |||||
* @param allowInternalWhitespace | |||||
* specifies if whitespace is permitted internally in names | |||||
*/ | |||||
public void setAllowInternalWhitespace( boolean allowInternalWhitespace ) | |||||
{ | |||||
m_allowInternalWhitespace = allowInternalWhitespace; | |||||
} | |||||
/** | |||||
* @param additionalInternalCharacters | |||||
* extra characters permitted in names | |||||
*/ | |||||
public void setAdditionalInternalCharacters( String additionalInternalCharacters ) | |||||
{ | |||||
m_additionalInternalCharacters = additionalInternalCharacters; | |||||
} | |||||
} |
@@ -0,0 +1,23 @@ | |||||
/* | |||||
* Copyright (C) The Apache Software Foundation. All rights reserved. | |||||
* | |||||
* This software is published under the terms of the Apache Software License | |||||
* version 1.1, a copy of which has been included with this distribution in | |||||
* the LICENSE.txt file. | |||||
*/ | |||||
package org.apache.myrmidon.interfaces.model; | |||||
/** | |||||
* Determines the validity of names used in projects. | |||||
* | |||||
* @author <a href="mailto:darrell@apache.org">Darrell DeBoer</a> | |||||
* @version $Revision$ $Date$ | |||||
*/ | |||||
public interface NameValidator | |||||
{ | |||||
/** | |||||
* Validates the supplied name, failing if it is not. | |||||
* @throws Exception is the supplied name is not valid. | |||||
*/ | |||||
void validate( String name ) throws Exception; | |||||
} |
@@ -0,0 +1,6 @@ | |||||
# Name validation | |||||
name.zero-char-name.error=Name "" is invalid, as it contains no characters. | |||||
name.enclosing-whitespace.error=Name "{0}" is invalid, as it contains enclosing whitespace. | |||||
name.invalid-initial-char.error=Name "{0}" is invalid, as it begins with an illegal character. Names can start with {1}. | |||||
name.invalid-internal-char.error=Name "{0}" is invalid, as it contains an illegal character. Permitted name characters are {1}. | |||||
name.could-not-create.error=Could not valid name from "{0}". |
@@ -12,6 +12,7 @@ import org.apache.avalon.excalibur.i18n.ResourceManager; | |||||
import org.apache.avalon.excalibur.i18n.Resources; | import org.apache.avalon.excalibur.i18n.Resources; | ||||
import org.apache.myrmidon.AbstractProjectTest; | import org.apache.myrmidon.AbstractProjectTest; | ||||
import org.apache.myrmidon.LogMessageTracker; | import org.apache.myrmidon.LogMessageTracker; | ||||
import org.apache.myrmidon.components.workspace.DefaultTaskContext; | |||||
/** | /** | ||||
* Test cases for <property> task. | * Test cases for <property> task. | ||||
@@ -91,4 +92,26 @@ public class PropertyTest | |||||
executeTargetExpectError( projectFile, "too-many-values3", messages ); | executeTargetExpectError( projectFile, "too-many-values3", messages ); | ||||
} | } | ||||
/** | |||||
* Tests basic validation of property names. | |||||
*/ | |||||
public void testNameValidation() throws Exception | |||||
{ | |||||
final File projectFile = getTestResource( "property.ant" ); | |||||
final Resources contextResources | |||||
= ResourceManager.getPackageResources( DefaultTaskContext.class ); | |||||
// Invalid names | |||||
String[] messages = new String[] | |||||
{ | |||||
null, | |||||
contextResources.getString( "bad-property-name.error" ), | |||||
null | |||||
}; | |||||
executeTargetExpectError( projectFile, "bad-prop-name1", messages ); | |||||
executeTargetExpectError( projectFile, "bad-prop-name2", messages ); | |||||
executeTargetExpectError( projectFile, "bad-prop-name3", messages ); | |||||
} | |||||
} | } |
@@ -0,0 +1,70 @@ | |||||
/* | |||||
* Copyright (C) The Apache Software Foundation. All rights reserved. | |||||
* | |||||
* This software is published under the terms of the Apache Software License | |||||
* version 1.1, a copy of which has been included with this distribution in | |||||
* the LICENSE.txt file. | |||||
*/ | |||||
package org.apache.myrmidon.components.builder; | |||||
import java.io.File; | |||||
import org.apache.avalon.excalibur.i18n.ResourceManager; | |||||
import org.apache.avalon.excalibur.i18n.Resources; | |||||
import org.apache.myrmidon.AbstractMyrmidonTest; | |||||
/** | |||||
* Test cases for {@link DefaultProjectBuilder}. | |||||
* | |||||
* @author <a href="mailto:darrell@apache.org">Darrell DeBoer</a> | |||||
* @version $Revision$ $Date$ | |||||
*/ | |||||
public class DefaultProjectBuilderTest | |||||
extends AbstractMyrmidonTest | |||||
{ | |||||
private final static Resources REZ | |||||
= ResourceManager.getPackageResources( DefaultProjectBuilder.class ); | |||||
private DefaultProjectBuilder m_builder; | |||||
public DefaultProjectBuilderTest( String name ) | |||||
{ | |||||
super( name ); | |||||
} | |||||
protected void setUp() throws Exception | |||||
{ | |||||
super.setUp(); | |||||
m_builder = new DefaultProjectBuilder(); | |||||
m_builder.enableLogging( createLogger() ); | |||||
} | |||||
/** | |||||
* Test validation of project and target names. | |||||
*/ | |||||
public void testNameValidation() throws Exception | |||||
{ | |||||
// Check bad project name | |||||
final File badProjectFile = getTestResource( "bad-project-name.ant" ); | |||||
try | |||||
{ | |||||
m_builder.build( badProjectFile.getAbsolutePath() ); | |||||
fail(); | |||||
} | |||||
catch( Exception e ) | |||||
{ | |||||
assertSameMessage( REZ.getString( "ant.project-bad-name.error" ), e ); | |||||
} | |||||
// Check bad target name | |||||
final File badTargetFile = getTestResource( "bad-target-name.ant" ); | |||||
try | |||||
{ | |||||
m_builder.build( badTargetFile.getAbsolutePath() ); | |||||
fail(); | |||||
} | |||||
catch( Exception e ) | |||||
{ | |||||
// TODO - check error message | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,122 @@ | |||||
/* | |||||
* Copyright (C) The Apache Software Foundation. All rights reserved. | |||||
* | |||||
* This software is published under the terms of the Apache Software License | |||||
* version 1.1, a copy of which has been included with this distribution in | |||||
* the LICENSE.txt file. | |||||
*/ | |||||
package org.apache.myrmidon.interfaces.model; | |||||
import org.apache.myrmidon.AbstractMyrmidonTest; | |||||
/** | |||||
* TestCases for {@link DefaultNameValidator}. | |||||
* | |||||
* @author <a href="mailto:darrell@apache.org">Darrell DeBoer</a> | |||||
* @version $Revision$ $Date$ | |||||
*/ | |||||
public class DefaultNameValidatorTest | |||||
extends AbstractMyrmidonTest | |||||
{ | |||||
private DefaultNameValidator m_validator = new DefaultNameValidator(); | |||||
public DefaultNameValidatorTest( String name ) | |||||
{ | |||||
super( name ); | |||||
} | |||||
/** | |||||
* Test valid names for the default validator. | |||||
*/ | |||||
public void testValidNames() throws Exception | |||||
{ | |||||
testValid( "aName" ); | |||||
testValid( "123456" ); | |||||
testValid( "s p a ce s" ); | |||||
testValid( "d-a-s-h-e-s-" ); | |||||
testValid( "d.o.t.s." ); | |||||
testValid( "_u_n_d_e_r_s_c_o_r_e_s" ); | |||||
testValid( "a" ); | |||||
testValid( "1" ); | |||||
testValid( "_" ); | |||||
} | |||||
/** | |||||
* Test invalid names for the default validator. | |||||
*/ | |||||
public void testInvalidNames() throws Exception | |||||
{ | |||||
testInvalid( "" ); | |||||
testInvalid( " " ); | |||||
testInvalid( " " ); | |||||
testInvalid( " bad" ); | |||||
testInvalid( "bad " ); | |||||
testInvalid( " bad " ); | |||||
testInvalid( "-dashfirst" ); | |||||
testInvalid( ".dotfirst" ); | |||||
testInvalid( "question?" ); | |||||
} | |||||
/** | |||||
* Test that certain characters are disallowed in the default validator. | |||||
*/ | |||||
public void testReservedChars() throws Exception | |||||
{ | |||||
String reserved = "!@#$%^&*()+=~`{}[]|\\/?<>,:;"; | |||||
for( int pos = 0; pos < reserved.length(); pos++ ) | |||||
{ | |||||
char chr = reserved.charAt( pos ); | |||||
testReservedChar( chr ); | |||||
} | |||||
} | |||||
private void testReservedChar( char chr ) throws Exception | |||||
{ | |||||
String test = "a" + String.valueOf( chr ); | |||||
testInvalid( test ); | |||||
} | |||||
/** | |||||
* Test validation using a restrictive set of validation rules. | |||||
*/ | |||||
public void testStrictNames() throws Exception | |||||
{ | |||||
m_validator = new DefaultNameValidator( false, false, "", false, false, "." ); | |||||
testValid( "name" ); | |||||
testValid( "a" ); | |||||
testValid( "yep.ok" ); | |||||
testInvalid( "_nogood" ); | |||||
testInvalid( "no_good" ); | |||||
testInvalid( "nope1" ); | |||||
testInvalid( "123" ); | |||||
testInvalid( "not ok" ); | |||||
} | |||||
private void testValid( String name ) | |||||
{ | |||||
try | |||||
{ | |||||
m_validator.validate( name ); | |||||
} | |||||
catch( Exception e ) | |||||
{ | |||||
fail( e.getMessage() ); | |||||
} | |||||
} | |||||
private void testInvalid( String name ) | |||||
{ | |||||
try | |||||
{ | |||||
m_validator.validate( name ); | |||||
fail( "Name \"" + name + "\" should be invalid." ); | |||||
} | |||||
catch( Exception e ) | |||||
{ | |||||
} | |||||
} | |||||
} |
@@ -12,6 +12,7 @@ import org.apache.avalon.excalibur.i18n.ResourceManager; | |||||
import org.apache.avalon.excalibur.i18n.Resources; | import org.apache.avalon.excalibur.i18n.Resources; | ||||
import org.apache.myrmidon.AbstractProjectTest; | import org.apache.myrmidon.AbstractProjectTest; | ||||
import org.apache.myrmidon.LogMessageTracker; | import org.apache.myrmidon.LogMessageTracker; | ||||
import org.apache.myrmidon.components.workspace.DefaultTaskContext; | |||||
/** | /** | ||||
* Test cases for <property> task. | * Test cases for <property> task. | ||||
@@ -91,4 +92,26 @@ public class PropertyTest | |||||
executeTargetExpectError( projectFile, "too-many-values3", messages ); | executeTargetExpectError( projectFile, "too-many-values3", messages ); | ||||
} | } | ||||
/** | |||||
* Tests basic validation of property names. | |||||
*/ | |||||
public void testNameValidation() throws Exception | |||||
{ | |||||
final File projectFile = getTestResource( "property.ant" ); | |||||
final Resources contextResources | |||||
= ResourceManager.getPackageResources( DefaultTaskContext.class ); | |||||
// Invalid names | |||||
String[] messages = new String[] | |||||
{ | |||||
null, | |||||
contextResources.getString( "bad-property-name.error" ), | |||||
null | |||||
}; | |||||
executeTargetExpectError( projectFile, "bad-prop-name1", messages ); | |||||
executeTargetExpectError( projectFile, "bad-prop-name2", messages ); | |||||
executeTargetExpectError( projectFile, "bad-prop-name3", messages ); | |||||
} | |||||
} | } |
@@ -0,0 +1,70 @@ | |||||
/* | |||||
* Copyright (C) The Apache Software Foundation. All rights reserved. | |||||
* | |||||
* This software is published under the terms of the Apache Software License | |||||
* version 1.1, a copy of which has been included with this distribution in | |||||
* the LICENSE.txt file. | |||||
*/ | |||||
package org.apache.myrmidon.components.builder; | |||||
import java.io.File; | |||||
import org.apache.avalon.excalibur.i18n.ResourceManager; | |||||
import org.apache.avalon.excalibur.i18n.Resources; | |||||
import org.apache.myrmidon.AbstractMyrmidonTest; | |||||
/** | |||||
* Test cases for {@link DefaultProjectBuilder}. | |||||
* | |||||
* @author <a href="mailto:darrell@apache.org">Darrell DeBoer</a> | |||||
* @version $Revision$ $Date$ | |||||
*/ | |||||
public class DefaultProjectBuilderTest | |||||
extends AbstractMyrmidonTest | |||||
{ | |||||
private final static Resources REZ | |||||
= ResourceManager.getPackageResources( DefaultProjectBuilder.class ); | |||||
private DefaultProjectBuilder m_builder; | |||||
public DefaultProjectBuilderTest( String name ) | |||||
{ | |||||
super( name ); | |||||
} | |||||
protected void setUp() throws Exception | |||||
{ | |||||
super.setUp(); | |||||
m_builder = new DefaultProjectBuilder(); | |||||
m_builder.enableLogging( createLogger() ); | |||||
} | |||||
/** | |||||
* Test validation of project and target names. | |||||
*/ | |||||
public void testNameValidation() throws Exception | |||||
{ | |||||
// Check bad project name | |||||
final File badProjectFile = getTestResource( "bad-project-name.ant" ); | |||||
try | |||||
{ | |||||
m_builder.build( badProjectFile.getAbsolutePath() ); | |||||
fail(); | |||||
} | |||||
catch( Exception e ) | |||||
{ | |||||
assertSameMessage( REZ.getString( "ant.project-bad-name.error" ), e ); | |||||
} | |||||
// Check bad target name | |||||
final File badTargetFile = getTestResource( "bad-target-name.ant" ); | |||||
try | |||||
{ | |||||
m_builder.build( badTargetFile.getAbsolutePath() ); | |||||
fail(); | |||||
} | |||||
catch( Exception e ) | |||||
{ | |||||
// TODO - check error message | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,122 @@ | |||||
/* | |||||
* Copyright (C) The Apache Software Foundation. All rights reserved. | |||||
* | |||||
* This software is published under the terms of the Apache Software License | |||||
* version 1.1, a copy of which has been included with this distribution in | |||||
* the LICENSE.txt file. | |||||
*/ | |||||
package org.apache.myrmidon.interfaces.model; | |||||
import org.apache.myrmidon.AbstractMyrmidonTest; | |||||
/** | |||||
* TestCases for {@link DefaultNameValidator}. | |||||
* | |||||
* @author <a href="mailto:darrell@apache.org">Darrell DeBoer</a> | |||||
* @version $Revision$ $Date$ | |||||
*/ | |||||
public class DefaultNameValidatorTest | |||||
extends AbstractMyrmidonTest | |||||
{ | |||||
private DefaultNameValidator m_validator = new DefaultNameValidator(); | |||||
public DefaultNameValidatorTest( String name ) | |||||
{ | |||||
super( name ); | |||||
} | |||||
/** | |||||
* Test valid names for the default validator. | |||||
*/ | |||||
public void testValidNames() throws Exception | |||||
{ | |||||
testValid( "aName" ); | |||||
testValid( "123456" ); | |||||
testValid( "s p a ce s" ); | |||||
testValid( "d-a-s-h-e-s-" ); | |||||
testValid( "d.o.t.s." ); | |||||
testValid( "_u_n_d_e_r_s_c_o_r_e_s" ); | |||||
testValid( "a" ); | |||||
testValid( "1" ); | |||||
testValid( "_" ); | |||||
} | |||||
/** | |||||
* Test invalid names for the default validator. | |||||
*/ | |||||
public void testInvalidNames() throws Exception | |||||
{ | |||||
testInvalid( "" ); | |||||
testInvalid( " " ); | |||||
testInvalid( " " ); | |||||
testInvalid( " bad" ); | |||||
testInvalid( "bad " ); | |||||
testInvalid( " bad " ); | |||||
testInvalid( "-dashfirst" ); | |||||
testInvalid( ".dotfirst" ); | |||||
testInvalid( "question?" ); | |||||
} | |||||
/** | |||||
* Test that certain characters are disallowed in the default validator. | |||||
*/ | |||||
public void testReservedChars() throws Exception | |||||
{ | |||||
String reserved = "!@#$%^&*()+=~`{}[]|\\/?<>,:;"; | |||||
for( int pos = 0; pos < reserved.length(); pos++ ) | |||||
{ | |||||
char chr = reserved.charAt( pos ); | |||||
testReservedChar( chr ); | |||||
} | |||||
} | |||||
private void testReservedChar( char chr ) throws Exception | |||||
{ | |||||
String test = "a" + String.valueOf( chr ); | |||||
testInvalid( test ); | |||||
} | |||||
/** | |||||
* Test validation using a restrictive set of validation rules. | |||||
*/ | |||||
public void testStrictNames() throws Exception | |||||
{ | |||||
m_validator = new DefaultNameValidator( false, false, "", false, false, "." ); | |||||
testValid( "name" ); | |||||
testValid( "a" ); | |||||
testValid( "yep.ok" ); | |||||
testInvalid( "_nogood" ); | |||||
testInvalid( "no_good" ); | |||||
testInvalid( "nope1" ); | |||||
testInvalid( "123" ); | |||||
testInvalid( "not ok" ); | |||||
} | |||||
private void testValid( String name ) | |||||
{ | |||||
try | |||||
{ | |||||
m_validator.validate( name ); | |||||
} | |||||
catch( Exception e ) | |||||
{ | |||||
fail( e.getMessage() ); | |||||
} | |||||
} | |||||
private void testInvalid( String name ) | |||||
{ | |||||
try | |||||
{ | |||||
m_validator.validate( name ); | |||||
fail( "Name \"" + name + "\" should be invalid." ); | |||||
} | |||||
catch( Exception e ) | |||||
{ | |||||
} | |||||
} | |||||
} |
@@ -209,12 +209,6 @@ | |||||
<li>Fire ProjectListener events projectStarted() and projectFinished() | <li>Fire ProjectListener events projectStarted() and projectFinished() | ||||
events on start and finish of referenced projects, adding indicator methods | events on start and finish of referenced projects, adding indicator methods | ||||
to ProjectEvent.</li> | to ProjectEvent.</li> | ||||
<li>Validate project and target names in DefaultProjectBuilder - reject dodgy | |||||
names like "," or "", or " ". Probably want to reject names that start or | |||||
end with white-space (though internal whitespace is probably fine). We also | |||||
want to reserve certain punctuation characters like , : ? $ [ ] { } < >, etc for | |||||
future use.</li> | |||||
<li>Similarly, validate property names, using the same rules.</li> | |||||
<li>Detect duplicate type names.</li> | <li>Detect duplicate type names.</li> | ||||
<li>Add fully qualified type names, based on antlib name and type shorthand name. | <li>Add fully qualified type names, based on antlib name and type shorthand name. | ||||
Allow these to be used in build files in addition to the shorthand names.</li> | Allow these to be used in build files in addition to the shorthand names.</li> | ||||