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> | |||
| </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> | |||
| @@ -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; | |||
| import java.io.File; | |||
| import java.net.MalformedURLException; | |||
| import java.net.URL; | |||
| import java.util.ArrayList; | |||
| 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.SAXConfigurationHandler; | |||
| 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.Condition; | |||
| import org.apache.myrmidon.framework.conditions.IsSetCondition; | |||
| import org.apache.myrmidon.framework.conditions.NotCondition; | |||
| 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.Target; | |||
| 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> | |||
| * @version $Revision$ $Date$ | |||
| * | |||
| * @ant:type type="project-builder" name="xml" | |||
| * @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 TARGETS = 3; | |||
| // Use a name validator with the default rules. | |||
| private DefaultNameValidator m_nameValidator = new DefaultNameValidator(); | |||
| /** | |||
| * build a project from file. | |||
| * | |||
| * @param source the source | |||
| * @return the constructed Project | |||
| * @exception Exception if an error occurs | |||
| * @exception ProjectException if an error occurs | |||
| */ | |||
| public Project build( final String source ) | |||
| throws Exception | |||
| throws ProjectException | |||
| { | |||
| final File file = new File( source ); | |||
| return build( file, new HashMap() ); | |||
| } | |||
| 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() ); | |||
| if( null != 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 ); | |||
| projects.put( systemID.toString(), project ); | |||
| @@ -94,20 +99,33 @@ public class DefaultProjectBuilder | |||
| 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 configuration the configuration loaded | |||
| * @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, | |||
| final Configuration configuration ) | |||
| throws Exception | |||
| throws ProjectException | |||
| { | |||
| if( !configuration.getName().equals( "project" ) ) | |||
| { | |||
| final String message = REZ.getString( "ant.no-project-element.error" ); | |||
| throw new Exception( message ); | |||
| throw new ProjectException( message ); | |||
| } | |||
| //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 defaultTarget = configuration.getAttribute( "default", "main" ); | |||
| final Version version = getVersion( configuration ); | |||
| @@ -141,7 +156,7 @@ public class DefaultProjectBuilder | |||
| { | |||
| final String message = | |||
| 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 | |||
| @@ -168,12 +183,52 @@ public class DefaultProjectBuilder | |||
| 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. | |||
| * Throw exceptions with meaningful errors if malformed or missing. | |||
| */ | |||
| private Version getVersion( final Configuration configuration ) | |||
| throws Exception | |||
| throws ProjectException | |||
| { | |||
| try | |||
| { | |||
| @@ -183,7 +238,7 @@ public class DefaultProjectBuilder | |||
| catch( final ConfigurationException ce ) | |||
| { | |||
| 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 | |||
| */ | |||
| private Version parseVersion( final String versionString ) | |||
| throws Exception | |||
| throws ProjectException | |||
| { | |||
| try | |||
| @@ -202,8 +257,7 @@ public class DefaultProjectBuilder | |||
| { | |||
| final String message = | |||
| 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 configuration the Configuration | |||
| * @exception Exception if an error occurs | |||
| * @exception ProjectException if an error occurs | |||
| */ | |||
| private void buildTopLevelProject( final DefaultProject project, | |||
| final Configuration configuration, | |||
| final HashMap projects ) | |||
| throws Exception | |||
| throws ProjectException | |||
| { | |||
| final ArrayList implicitTaskList = new ArrayList(); | |||
| final Configuration[] children = configuration.getChildren(); | |||
| @@ -277,7 +331,7 @@ public class DefaultProjectBuilder | |||
| { | |||
| final String message = | |||
| 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, | |||
| final Configuration element, | |||
| final HashMap projects ) | |||
| throws Exception | |||
| throws ProjectException | |||
| { | |||
| final String name = element.getAttribute( "name", null ); | |||
| final String location = element.getAttribute( "location", null ); | |||
| @@ -300,27 +354,31 @@ public class DefaultProjectBuilder | |||
| { | |||
| final String message = | |||
| 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 = | |||
| REZ.getString( "ant.projectref-bad-name.error", element.getLocation() ); | |||
| throw new Exception( message ); | |||
| throw new ProjectException( message, e ); | |||
| } | |||
| if( null == location ) | |||
| { | |||
| final String message = | |||
| REZ.getString( "ant.projectref-no-location.error", element.getLocation() ); | |||
| throw new Exception( message ); | |||
| throw new ProjectException( message ); | |||
| } | |||
| // Build the URL of the referenced projects | |||
| final File baseDirectory = project.getBaseDirectory(); | |||
| 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 | |||
| Project other = (Project)projects.get( systemID ); | |||
| @@ -333,9 +391,22 @@ public class DefaultProjectBuilder | |||
| 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, | |||
| final Configuration element ) | |||
| throws Exception | |||
| throws ProjectException | |||
| { | |||
| final String library = element.getAttribute( "library", null ); | |||
| final String name = element.getAttribute( "name", null ); | |||
| @@ -345,7 +416,7 @@ public class DefaultProjectBuilder | |||
| { | |||
| final String message = | |||
| REZ.getString( "ant.import-no-library.error", element.getLocation() ); | |||
| throw new Exception( message ); | |||
| throw new ProjectException( message ); | |||
| } | |||
| if( null == name || null == type ) | |||
| @@ -354,7 +425,7 @@ public class DefaultProjectBuilder | |||
| { | |||
| final String message = | |||
| 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 | |||
| */ | |||
| private void buildTarget( final DefaultProject project, final Configuration target ) | |||
| throws Exception | |||
| throws ProjectException | |||
| { | |||
| final String name = target.getAttribute( "name", null ); | |||
| final String depends = target.getAttribute( "depends", null ); | |||
| final String ifCondition = target.getAttribute( "if", null ); | |||
| final String unlessCondition = target.getAttribute( "unless", null ); | |||
| verifyName( name, target ); | |||
| verifyTargetName( name, target ); | |||
| if( getLogger().isDebugEnabled() ) | |||
| { | |||
| @@ -392,24 +463,30 @@ public class DefaultProjectBuilder | |||
| 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 ) | |||
| { | |||
| final String message = | |||
| 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 = | |||
| 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; | |||
| @@ -428,7 +505,7 @@ public class DefaultProjectBuilder | |||
| final String message = REZ.getString( "ant.target-bad-dependency.error", | |||
| target.getName(), | |||
| target.getLocation() ); | |||
| throw new Exception( message ); | |||
| throw new ProjectException( message ); | |||
| } | |||
| if( getLogger().isDebugEnabled() ) | |||
| @@ -447,7 +524,6 @@ public class DefaultProjectBuilder | |||
| private Condition buildCondition( final String ifCondition, | |||
| final String unlessCondition ) | |||
| throws Exception | |||
| { | |||
| final AndCondition condition = new AndCondition(); | |||
| @@ -475,16 +551,4 @@ public class DefaultProjectBuilder | |||
| 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.target-parse.notice=Parsing target: {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.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.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-bad-name.error=Projectref with an invalid name 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.myrmidon.api.TaskContext; | |||
| import org.apache.myrmidon.api.TaskException; | |||
| import org.apache.myrmidon.interfaces.model.DefaultNameValidator; | |||
| import org.apache.myrmidon.interfaces.workspace.PropertyResolver; | |||
| /** | |||
| @@ -34,6 +35,12 @@ public class DefaultTaskContext | |||
| private final static Resources REZ = | |||
| 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 TaskContext m_parent; | |||
| private ServiceManager m_serviceManager; | |||
| @@ -199,6 +206,7 @@ public class DefaultTaskContext | |||
| public void setProperty( final String name, final Object value ) | |||
| throws TaskException | |||
| { | |||
| checkPropertyName( name ); | |||
| checkPropertyValid( name, value ); | |||
| m_contextData.put( name, value ); | |||
| } | |||
| @@ -362,6 +370,22 @@ public class DefaultTaskContext | |||
| 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. | |||
| * | |||
| @@ -13,6 +13,7 @@ exec-task.notice=Executing task {0}. | |||
| #DefaultTaskContext | |||
| unknown-prop.error=Unknown property {0}. | |||
| 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. | |||
| bad-resolve.error=Unable to resolve value "{0}". | |||
| bad-find-service.error=Could not find service "{0}". | |||
| @@ -25,8 +25,8 @@ public interface ProjectBuilder | |||
| * | |||
| * @param source the source | |||
| * @return the constructed Project | |||
| * @exception Exception if an error occurs | |||
| * @exception ProjectException if an error occurs | |||
| */ | |||
| 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.myrmidon.AbstractProjectTest; | |||
| import org.apache.myrmidon.LogMessageTracker; | |||
| import org.apache.myrmidon.components.workspace.DefaultTaskContext; | |||
| /** | |||
| * Test cases for <property> task. | |||
| @@ -91,4 +92,26 @@ public class PropertyTest | |||
| 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.myrmidon.AbstractProjectTest; | |||
| import org.apache.myrmidon.LogMessageTracker; | |||
| import org.apache.myrmidon.components.workspace.DefaultTaskContext; | |||
| /** | |||
| * Test cases for <property> task. | |||
| @@ -91,4 +92,26 @@ public class PropertyTest | |||
| 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() | |||
| events on start and finish of referenced projects, adding indicator methods | |||
| 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>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> | |||