You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by jk...@apache.org on 2006/03/11 21:55:35 UTC

svn commit: r385164 [20/32] - in /jakarta/tapestry/trunk: ./ .settings/ annotations/src/java/org/apache/tapestry/annotations/ annotations/src/test/org/apache/tapestry/annotations/ config/ contrib/src/documentation/content/xdocs/tapestry-contrib/Compone...

Modified: jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TemplateParser.java
URL: http://svn.apache.org/viewcvs/jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TemplateParser.java?rev=385164&r1=385163&r2=385164&view=diff
==============================================================================
--- jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TemplateParser.java (original)
+++ jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TemplateParser.java Sat Mar 11 12:54:27 2006
@@ -1,4 +1,4 @@
-// Copyright 2004, 2005, 2006 The Apache Software Foundation
+// Copyright 2004, 2005 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -35,93 +35,89 @@
 
 /**
  * Parses Tapestry templates, breaking them into a series of
- * {@link org.apache.tapestry.parse.TemplateToken tokens}. Although often
- * referred to as an "HTML template", there is no real requirement that the
- * template be HTML. This parser can handle any reasonable SGML derived markup
- * (including XML), but specifically works around the ambiguities of HTML
- * reasonably.
+ * {@link org.apache.tapestry.parse.TemplateToken tokens}. Although often referred to as an "HTML
+ * template", there is no real requirement that the template be HTML. This parser can handle any
+ * reasonable SGML derived markup (including XML), but specifically works around the ambiguities of
+ * HTML reasonably.
  * <p>
- * Deployed as the tapestry.parse.TemplateParser service, using the threaded
- * model.
+ * Deployed as the tapestry.parse.TemplateParser service, using the threaded model.
  * <p>
- * Dynamic markup in Tapestry attempts to be invisible. Components are arbitrary
- * tags containing a <code>jwcid</code> attribute. Such components must be
- * well balanced (have a matching close tag, or end the tag with "<code>/&gt;</code>".
+ * Dynamic markup in Tapestry attempts to be invisible. Components are arbitrary tags containing a
+ * <code>jwcid</code> attribute. Such components must be well balanced (have a matching close tag,
+ * or end the tag with "<code>/&gt;</code>".
  * <p>
- * Generally, the id specified in the template is matched against an component
- * defined in the specification. However, implicit components are also possible.
- * The jwcid attribute uses the syntax "<code>@Type</code>" for implicit components. Type is the component type, and may
- *       include a library id prefix. Such a component is anonymous (but is
- *       given a unique id).
+ * Generally, the id specified in the template is matched against an component defined in the
+ * specification. However, implicit components are also possible. The jwcid attribute uses the
+ * syntax "<code>@Type</code>" for implicit components. Type is the component type, and may include a library id
+ *       prefix. Such a component is anonymous (but is given a unique id).
  *       <p>
- *       (The unique ids assigned start with a dollar sign, which is normally no
- *       allowed for component ids ... this helps to make them stand out and
- *       assures that they do not conflict with user-defined component ids.
- *       These ids tend to propagate into URLs and become HTML element names and
- *       even JavaScript variable names ... the dollar sign is acceptible in
- *       these contexts as well).
+ *       (The unique ids assigned start with a dollar sign, which is normally no allowed for
+ *       component ids ... this helps to make them stand out and assures that they do not conflict
+ *       with user-defined component ids. These ids tend to propagate into URLs and become HTML
+ *       element names and even JavaScript variable names ... the dollar sign is acceptible in these
+ *       contexts as well).
  *       <p>
  *       Implicit component may also be given a name using the syntax "
- *       <code>componentId:@Type</code>". Such a component should <b>not </b>
- *       be defined in the specification, but may still be accessed via
+ *       <code>componentId:@Type</code>". Such a component should <b>not </b> be defined in the
+ *       specification, but may still be accessed via
  *       {@link org.apache.tapestry.IComponent#getComponent(String)}.
  *       <p>
- *       Both defined and implicit components may have additional attributes
- *       defined, simply by including them in the template. They set formal or
- *       informal parameters of the component to static strings.
- *       {@link org.apache.tapestry.spec.IComponentSpecification#getAllowInformalParameters()},
- *       if false, will cause such attributes to be simply ignored. For defined
- *       components, conflicting values defined in the template are ignored.
+ *       Both defined and implicit components may have additional attributes defined, simply by
+ *       including them in the template. They set formal or informal parameters of the component to
+ *       static strings.
+ *       {@link org.apache.tapestry.spec.IComponentSpecification#getAllowInformalParameters()}, if
+ *       false, will cause such attributes to be simply ignored. For defined components, conflicting
+ *       values defined in the template are ignored.
  *       <p>
- *       Attributes in component tags will become formal and informal parameters
- *       of the corresponding component. Most attributes will be
+ *       Attributes in component tags will become formal and informal parameters of the
+ *       corresponding component. Most attributes will be
  *       <p>
- *       The parser removes the body of some tags (when the corresponding
- *       component doesn't
- *       {@link org.apache.tapestry.spec.IComponentSpecification#getAllowBody() allow a body},
- *       and allows portions of the template to be completely removed.
+ *       The parser removes the body of some tags (when the corresponding component doesn't
+ *       {@link org.apache.tapestry.spec.IComponentSpecification#getAllowBody() allow a body}, and
+ *       allows portions of the template to be completely removed.
  *       <p>
- *       The parser does a pretty thorough lexical analysis of the template, and
- *       reports a great number of errors, including improper nesting of tags.
+ *       The parser does a pretty thorough lexical analysis of the template, and reports a great
+ *       number of errors, including improper nesting of tags.
  *       <p>
- *       The parser supports <em>invisible localization</em>: The parser
- *       recognizes HTML of the form:
- *       <code>&lt;span key="<i>value</i>"&gt; ... &lt;/span&gt;</code> and
- *       converts them into a {@link TokenType#LOCALIZATION} token. You may also
- *       specifify a <code>raw</code> attribute ... if the value is
- *       <code>true</code>, then the localized value is sent to the client
- *       without filtering, which is appropriate if the value has any markup
- *       that should not be escaped.
+ *       The parser supports <em>invisible localization</em>: The parser recognizes HTML of the
+ *       form: <code>&lt;span key="<i>value</i>"&gt; ... &lt;/span&gt;</code> and converts them
+ *       into a {@link TokenType#LOCALIZATION} token. You may also specifify a <code>raw</code>
+ *       attribute ... if the value is <code>true</code>, then the localized value is sent to the
+ *       client without filtering, which is appropriate if the value has any markup that should not
+ *       be escaped.
  * @author Howard Lewis Ship, Geoff Longman
  */
 
 public class TemplateParser implements ITemplateParser
 {
-
     /**
-     * The attribute, checked for in &lt;span&gt; tags, that signfies that the
-     * span is being used as an invisible localization.
-     * 
-     * @since 2.0.4
+     * A "magic" component id that causes the tag with the id and its entire body to be ignored
+     * during parsing.
      */
 
-    public static final String LOCALIZATION_KEY_ATTRIBUTE_NAME = "key";
+    private static final String REMOVE_ID = "$remove$";
+
+    /**
+     * A "magic" component id that causes the tag to represent the true content of the template. Any
+     * content prior to the tag is discarded, and any content after the tag is ignored. The tag
+     * itself is not included.
+     */
 
-    public static final String PROPERTY_NAME_PATTERN = "_?[a-zA-Z]\\w*";
+    private static final String CONTENT_ID = "$content$";
 
     /**
-     * Pattern used to recognize ordinary components (defined in the
-     * specification).
+     * The attribute, checked for in &lt;span&gt; tags, that signfies that the span is being used as
+     * an invisible localization.
      * 
-     * @since 3.0
+     * @since 2.0.4
      */
 
-    public static final String SIMPLE_ID_PATTERN = "^(" + PROPERTY_NAME_PATTERN + ")$";
+    public static final String LOCALIZATION_KEY_ATTRIBUTE_NAME = "key";
 
     /**
-     * Used with {@link #LOCALIZATION_KEY_ATTRIBUTE_NAME} to indicate a string
-     * that should be rendered "raw" (without escaping HTML). If not specified,
-     * defaults to "false". The value must equal "true" (caselessly).
+     * Used with {@link #LOCALIZATION_KEY_ATTRIBUTE_NAME} to indicate a string that should be
+     * rendered "raw" (without escaping HTML). If not specified, defaults to "false". The value must
+     * equal "true" (caselessly).
      * 
      * @since 2.3
      */
@@ -129,94 +125,108 @@
     public static final String RAW_ATTRIBUTE_NAME = "raw";
 
     /**
-     * Pattern used to recognize implicit components (whose type is defined in
-     * the template). Subgroup 1 is the id (which may be null) and subgroup 2 is
-     * the type (which may be qualified with a library prefix). Subgroup 4 is
-     * the library id, Subgroup 5 is the simple component type, which may (as of
-     * 4.0) have slashes to delinate folders containing the component.
+     * Attribute name used to identify components.
      * 
-     * @since 3.0
+     * @since 4.0
      */
 
-    public static final String IMPLICIT_ID_PATTERN = "^(" + PROPERTY_NAME_PATTERN + ")?@(((" + PROPERTY_NAME_PATTERN
-            + "):)?((" + PROPERTY_NAME_PATTERN + "/)*" + PROPERTY_NAME_PATTERN + "))$";
+    private String _componentAttributeName;
 
-    private static final int WAIT_FOR_ATTRIBUTE_NAME = 0;
+    private static final String PROPERTY_NAME_PATTERN = "_?[a-zA-Z]\\w*";
 
-    private static final int WAIT_FOR_ATTRIBUTE_VALUE = 3;
+    /**
+     * Pattern used to recognize ordinary components (defined in the specification).
+     * 
+     * @since 3.0
+     */
+
+    public static final String SIMPLE_ID_PATTERN = "^(" + PROPERTY_NAME_PATTERN + ")$";
 
     /**
-     * A "magic" component id that causes the tag with the id and its entire
-     * body to be ignored during parsing.
+     * Pattern used to recognize implicit components (whose type is defined in the template).
+     * Subgroup 1 is the id (which may be null) and subgroup 2 is the type (which may be qualified
+     * with a library prefix). Subgroup 4 is the library id, Subgroup 5 is the simple component
+     * type, which may (as of 4.0) have slashes to delinate folders containing the component.
+     * 
+     * @since 3.0
      */
 
-    private static final String REMOVE_ID = "$remove$";
-    private static final int ADVANCE_PAST_EQUALS = 2;
+    public static final String IMPLICIT_ID_PATTERN = "^(" + PROPERTY_NAME_PATTERN + ")?@((("
+            + PROPERTY_NAME_PATTERN + "):)?((" + PROPERTY_NAME_PATTERN + "/)*"
+            + PROPERTY_NAME_PATTERN + "))$";
 
-    private static final char[] CLOSE_TAG = new char[] { '<', '/' };
+    private static final int IMPLICIT_ID_PATTERN_ID_GROUP = 1;
 
-    private static final int COLLECT_ATTRIBUTE_NAME = 1;
+    private static final int IMPLICIT_ID_PATTERN_TYPE_GROUP = 2;
 
-    private static final int COLLECT_QUOTED_VALUE = 4;
+    private static final int IMPLICIT_ID_PATTERN_LIBRARY_ID_GROUP = 4;
 
-    private static final int COLLECT_UNQUOTED_VALUE = 5;
+    private static final int IMPLICIT_ID_PATTERN_SIMPLE_TYPE_GROUP = 5;
+
+    private Pattern _simpleIdPattern;
+
+    private Pattern _implicitIdPattern;
 
-    private static final char[] COMMENT_END = new char[] { '-', '-', '>' };
+    private PatternMatcher _patternMatcher;
+
+    private IdAllocator _idAllocator = new IdAllocator();
 
-    private static final char[] COMMENT_START = new char[] { '<', '!', '-', '-' };
+    private ITemplateParserDelegate _delegate;
 
     /**
-     * A "magic" component id that causes the tag to represent the true content
-     * of the template. Any content prior to the tag is discarded, and any
-     * content after the tag is ignored. The tag itself is not included.
+     * Identifies the template being parsed; used with error messages.
      */
 
-    private static final String CONTENT_ID = "$content$";
+    private Resource _resourceLocation;
 
     /**
-     * Conversions needed by {@link #convertEntitiesToPlain(String)}.
+     * Shared instance of {@link Location} used by all {@link TextToken} instances in the template.
      */
 
-    private static final String[] CONVERSIONS = { "&lt;", "<", "&gt;", ">", "&quot;", "\"", "&amp;", "&" };
+    private Location _templateLocation;
 
-    private static final int IMPLICIT_ID_PATTERN_ID_GROUP = 1;
+    /**
+     * Location with in the resource for the current line.
+     */
 
-    private static final int IMPLICIT_ID_PATTERN_LIBRARY_ID_GROUP = 4;
+    private Location _currentLocation;
 
-    private static final int IMPLICIT_ID_PATTERN_SIMPLE_TYPE_GROUP = 5;
+    /**
+     * Local reference to the template data that is to be parsed.
+     */
 
-    private static final int IMPLICIT_ID_PATTERN_TYPE_GROUP = 2;
+    private char[] _templateData;
 
     /**
-     * Stores details about a parsed tag, used to match start and end tags and
-     * control other parser processing.
+     * List of Tag
      */
+
+    private List _stack = new ArrayList();
+
     private static class Tag
     {
+        // The element, i.e., <jwc> or virtually any other element (via jwcid attribute)
+        String _tagName;
 
         // If true, the tag is a placeholder for a dynamic element
         boolean _component;
 
-        // If true, then the parse ends when the closing tag is found.
-        boolean _content;
-
         // If true, the body of the tag is being ignored, and the
         // ignore flag is cleared when the close tag is reached
         boolean _ignoringBody;
 
-        // The line on which the start tag exists
-        int _line;
+        // If true, then the entire tag (and its body) is being ignored
+        boolean _removeTag;
 
         // If true, then the tag must have a balanced closing tag.
         // This is always true for components.
         boolean _mustBalance;
 
-        // If true, then the entire tag (and its body) is being ignored
-        boolean _removeTag;
+        // The line on which the start tag exists
+        int _line;
 
-        // The element, i.e., <jwc> or virtually any other element (via jwcid
-        // attribute)
-        String _tagName;
+        // If true, then the parse ends when the closing tag is found.
+        boolean _content;
 
         Tag(String tagName, int line)
         {
@@ -231,59 +241,23 @@
     }
 
     /**
-     * A {@link Map}of {@link String}s, used to store attributes collected
-     * while parsing a tag.
-     */
-
-    private Map _attributes = new HashMap();
-
-    /**
-     * The start of the current block of static text, or -1 if no block is
-     * active.
-     */
-
-    private int _blockStart;
-
-    /**
-     * Attribute name used to identify components.
-     * 
-     * @since 4.0
-     */
-
-    private String _componentAttributeName;
-
-    /**
-     * Location with in the resource for the current line.
+     * List of {@link TemplateToken}, this forms the ultimate response.
      */
 
-    private Location _currentLocation;
+    private List _tokens = new ArrayList();
 
     /**
-     * The location of the 'cursor' within the template data. The advance()
-     * method moves this forward.
+     * The location of the 'cursor' within the template data. The advance() method moves this
+     * forward.
      */
 
     private int _cursor;
 
-    private ITemplateParserDelegate _delegate;
-
-    /**
-     * A factory used to create template tokens.
-     */
-
-    private TemplateTokenFactory _factory;
-
-    private IdAllocator _idAllocator = new IdAllocator();
-
     /**
-     * Set to true when the body of a tag is being ignored. This is typically
-     * used to skip over the body of a tag when its corresponding component
-     * doesn't allow a body, or whe the special jwcid of $remove$ is used.
+     * The start of the current block of static text, or -1 if no block is active.
      */
 
-    private boolean _ignoring;
-
-    private Pattern _implicitIdPattern;
+    private int _blockStart;
 
     /**
      * The current line number; tracked by advance(). Starts at 1.
@@ -291,40 +265,25 @@
 
     private int _line;
 
-    private PatternMatcher _patternMatcher;
-
-    /**
-     * Identifies the template being parsed; used with error messages.
-     */
-
-    private Resource _resourceLocation;
-
-    private Pattern _simpleIdPattern;
-
-    /**
-     * List of Tag.
-     */
-
-    private List _stack = new ArrayList();
-
     /**
-     * Local reference to the template data that is to be parsed.
+     * Set to true when the body of a tag is being ignored. This is typically used to skip over the
+     * body of a tag when its corresponding component doesn't allow a body, or whe the special jwcid
+     * of $remove$ is used.
      */
 
-    private char[] _templateData;
+    private boolean _ignoring;
 
     /**
-     * Shared instance of {@link Location} used by all {@link TextToken}
-     * instances in the template.
+     * A {@link Map}of {@link String}s, used to store attributes collected while parsing a tag.
      */
 
-    private Location _templateLocation;
+    private Map _attributes = new HashMap();
 
     /**
-     * List of {@link TemplateToken}, this forms the ultimate response.
+     * A factory used to create template tokens.
      */
 
-    private List _tokens = new ArrayList();
+    private TemplateTokenFactory _factory;
 
     public TemplateParser()
     {
@@ -344,124 +303,50 @@
     }
 
     /**
-     * @throws TemplateParseException
-     * @since 4.0
-     */
-
-    private void addAttributeIfUnique(String tagName, String attributeName, String attributeValue)
-        throws TemplateParseException
-    {
-
-        if (_attributes.containsKey(attributeName))
-            templateParseProblem(ParseMessages.duplicateTagAttribute(tagName, _line, attributeName),
-                    getCurrentLocation(), _line, _cursor);
-
-        _attributes.put(attributeName, attributeValue);
-    }
-
-    /**
-     * Adds the attribute to the token (identifying prefixes and whatnot is now
-     * done downstream).
+     * Parses the template data into an array of {@link TemplateToken}s.
+     * <p>
+     * The parser is <i>decidedly </i> not threadsafe, so care should be taken that only a single
+     * thread accesses it.
      * 
-     * @since 3.0
+     * @param templateData
+     *            the HTML template to parse. Some tokens will hold a reference to this array.
+     * @param delegate
+     *            object that "knows" about defined components
+     * @param resourceLocation
+     *            a description of where the template originated from, used with error messages.
      */
 
-    private void addAttributeToToken(OpenToken token, String name, String attributeValue)
-    {
-        token.addAttribute(name, convertEntitiesToPlain(attributeValue));
-    }
-
-    private void addOpenToken(String tagName, String jwcId, String type, Location location)
+    public TemplateToken[] parse(char[] templateData, ITemplateParserDelegate delegate,
+            Resource resourceLocation) throws TemplateParseException
     {
-        OpenToken token = _factory.createOpenToken(tagName, jwcId, type, location);
-        _tokens.add(token);
-
-        if (_attributes.isEmpty()) return;
-
-        Iterator i = _attributes.entrySet().iterator();
-        while(i.hasNext())
+        try
         {
-            Map.Entry entry = (Map.Entry)i.next();
-
-            String key = (String)entry.getKey();
-
-            if (key.equalsIgnoreCase(_componentAttributeName)) continue;
+            beforeParse(templateData, delegate, resourceLocation);
 
-            String value = (String)entry.getValue();
+            parse();
 
-            addAttributeToToken(token, key, value);
+            return (TemplateToken[]) _tokens.toArray(new TemplateToken[_tokens.size()]);
         }
-    }
-
-    private void addTextToken(int end)
-    {
-        // No active block to add to.
-
-        if (_blockStart < 0) return;
-
-        if (_blockStart <= end)
+        finally
         {
-            // This seems odd, shouldn't the location be the current location? I
-            // guess
-            // no errors are ever reported for a text token.
-
-            TemplateToken token = _factory.createTextToken(_templateData, _blockStart, end, _templateLocation);
-
-            _tokens.add(token);
+            afterParse();
         }
-
-        _blockStart = -1;
     }
 
     /**
-     * Advances the cursor to the next character. If the end-of-line is reached,
-     * then increments the line counter.
+     * perform default initialization of the parser.
      */
 
-    private void advance()
-    {
-        int length = _templateData.length;
-
-        if (_cursor >= length) return;
-
-        char ch = _templateData[_cursor];
-
-        _cursor++;
-
-        if (ch == '\n')
-        {
-            _line++;
-            _currentLocation = null;
-            return;
-        }
-
-        // A \r, or a \r\n also counts as a new line.
-
-        if (ch == '\r')
-        {
-            _line++;
-            _currentLocation = null;
-
-            if (_cursor < length && _templateData[_cursor] == '\n') _cursor++;
-
-            return;
-        }
-
-        // Not an end-of-line character.
-
-    }
-
-    private void advanceOverWhitespace()
+    protected void beforeParse(char[] templateData, ITemplateParserDelegate delegate,
+            Resource resourceLocation)
     {
-        int length = _templateData.length;
-
-        while(_cursor < length)
-        {
-            char ch = _templateData[_cursor];
-            if (!Character.isWhitespace(ch)) return;
-
-            advance();
-        }
+        _templateData = templateData;
+        _resourceLocation = resourceLocation;
+        _templateLocation = new LocationImpl(resourceLocation);
+        _delegate = delegate;
+        _ignoring = false;
+        _line = 1;
+        _componentAttributeName = delegate.getComponentAttributeName();
     }
 
     /**
@@ -482,281 +367,51 @@
     }
 
     /**
-     * Notify that the beginning of an attribute value has been detected.
+     * Used by the parser to report problems in the parse. Parsing <b>must </b> stop when a problem
+     * is reported.
      * <p>
-     * Default implementation does nothing.
+     * The default implementation simply throws an exception that contains the message and location
+     * parameters.
+     * <p>
+     * Subclasses may override but <b>must </b> ensure they throw the required exception.
+     * 
+     * @param message
+     * @param location
+     * @param line
+     *            ignored by the default impl
+     * @param cursor
+     *            ignored by the default impl
+     * @throws TemplateParseException
+     *             always thrown in order to terminate the parse.
      */
-    protected void attributeBeginEvent(String attributeName, int startLine, int cursorPosition)
+
+    protected void templateParseProblem(String message, Location location, int line, int cursor)
+            throws TemplateParseException
     {
+        throw new TemplateParseException(message, location);
     }
 
     /**
-     * Notify that the end of the current attribute value has been detected.
+     * Used by the parser to report tapestry runtime specific problems in the parse. Parsing <b>must
+     * </b> stop when a problem is reported.
      * <p>
-     * Default implementation does nothing.
-     */
-    protected void attributeEndEvent(int cursorPosition)
-    {
-    }
-
-    /**
-     * perform default initialization of the parser.
-     */
-
-    protected void beforeParse(char[] templateData, ITemplateParserDelegate delegate, Resource resourceLocation)
-    {
-        _templateData = templateData;
-        _resourceLocation = resourceLocation;
-        _templateLocation = new LocationImpl(resourceLocation);
-        _delegate = delegate;
-        _ignoring = false;
-        _line = 1;
-        _componentAttributeName = delegate.getComponentAttributeName();
-    }
-
-    /**
-     * Returns true if the map contains the given key (caseless search) and the
-     * value is "true" (caseless comparison).
-     */
-
-    private boolean checkBoolean(String key, Map map)
-    {
-        String value = findValueCaselessly(key, map);
-
-        if (value == null) return false;
-
-        return value.equalsIgnoreCase("true");
-    }
-
-    /**
-     * Invoked to handle a closing tag, i.e., &lt;/foo&gt;. When a tag closes,
-     * it will match against a tag on the open tag start. Preferably the top tag
-     * on the stack (if everything is well balanced), but this is HTML, not XML,
-     * so many tags won't balance.
-     * <p>
-     * Once the matching tag is located, the question is ... is the tag dynamic
-     * or static? If static, then the current text block is extended to include
-     * this close tag. If dynamic, then the current text block is ended (before
-     * the '&lt;' that starts the tag) and a close token is added.
+     * The default implementation simply rethrows the exception.
      * <p>
-     * In either case, the matching static element and anything above it is
-     * removed, and the cursor is left on the character following the '&gt;'.
-     */
-
-    private void closeTag()
-        throws TemplateParseException
-    {
-        int cursorStart = _cursor;
-        int length = _templateData.length;
-        int startLine = _line;
-
-        Location startLocation = getCurrentLocation();
-
-        _cursor += CLOSE_TAG.length;
-
-        int tagStart = _cursor;
-
-        while(true)
-        {
-            if (_cursor >= length)
-                templateParseProblem(ParseMessages.incompleteCloseTag(startLine), startLocation, startLine, cursorStart);
-
-            char ch = _templateData[_cursor];
-
-            if (ch == '>') break;
-
-            advance();
-        }
-
-        String tagName = new String(_templateData, tagStart, _cursor - tagStart);
-
-        int stackPos = _stack.size() - 1;
-        Tag tag = null;
-
-        while(stackPos >= 0)
-        {
-            tag = (Tag)_stack.get(stackPos);
-
-            if (tag.match(tagName)) break;
-
-            if (tag._mustBalance)
-                templateParseProblem(ParseMessages
-                        .improperlyNestedCloseTag(tagName, startLine, tag._tagName, tag._line), startLocation,
-                        startLine, cursorStart);
-
-            stackPos--;
-        }
-
-        if (stackPos < 0)
-            templateParseProblem(ParseMessages.unmatchedCloseTag(tagName, startLine), startLocation, startLine,
-                    cursorStart);
-
-        // Special case for the content tag
-
-        if (tag._content)
-        {
-            addTextToken(cursorStart - 1);
-
-            // Advance the cursor right to the end.
-
-            _cursor = length;
-            _stack.clear();
-            return;
-        }
-
-        // When a component closes, add a CLOSE tag.
-        if (tag._component)
-        {
-            addTextToken(cursorStart - 1);
-
-            _tokens.add(_factory.createCloseToken(tagName, getCurrentLocation()));
-        }
-        else
-        {
-            // The close of a static tag. Unless removing the tag
-            // entirely, make sure the block tag is part of a text block.
-
-            if (_blockStart < 0 && !tag._removeTag && !_ignoring) _blockStart = cursorStart;
-        }
-
-        // Remove all elements at stackPos or above.
-
-        for(int i = _stack.size() - 1; i >= stackPos; i--)
-            _stack.remove(i);
-
-        // Advance cursor past '>'
-
-        advance();
-
-        // If editting out the tag (i.e., $remove$) then kill any whitespace.
-        // For components that simply don't contain a body, removeTag will
-        // be false.
-
-        if (tag._removeTag) advanceOverWhitespace();
-
-        // If we were ignoring the body of the tag, then clear the ignoring
-        // flag, since we're out of the body.
-
-        if (tag._ignoringBody) _ignoring = false;
-    }
-
-    /**
-     * Processes a tag that is the open tag for a component (but also handles
-     * the $remove$ and $content$ tags).
-     */
-
-    /**
-     * Provided a raw input string that has been recognized to be an expression,
-     * this removes excess white space and converts &amp;amp;;, &amp;quot;;
-     * &amp;lt;; and &amp;gt;; to their normal character values (otherwise its
-     * impossible to specify those values in expressions in the template).
-     */
-
-    private String convertEntitiesToPlain(String input)
-    {
-        int inputLength = input.length();
-
-        StringBuffer buffer = new StringBuffer(inputLength);
-
-        int cursor = 0;
-
-        outer: while(cursor < inputLength)
-        {
-            for(int i = 0; i < CONVERSIONS.length; i += 2)
-            {
-                String entity = CONVERSIONS[i];
-                int entityLength = entity.length();
-                String value = CONVERSIONS[i + 1];
-
-                if (cursor + entityLength > inputLength) continue;
-
-                if (input.substring(cursor, cursor + entityLength).equals(entity))
-                {
-                    buffer.append(value);
-                    cursor += entityLength;
-                    continue outer;
-                }
-            }
-
-            buffer.append(input.charAt(cursor));
-            cursor++;
-        }
-
-        return buffer.toString().trim();
-    }
-
-    /**
-     * Returns a new Map that is a copy of the input Map with some key/value
-     * pairs removed. A list of keys is passed in and matching keys (caseless
-     * comparison) from the input Map are excluded from the output map. May
-     * return null (rather than return an empty Map).
-     */
-
-    private Map filter(Map input, String[] removeKeys)
-    {
-        if (input == null || input.isEmpty()) return null;
-
-        Map result = null;
-
-        Iterator i = input.entrySet().iterator();
-
-        nextkey: while(i.hasNext())
-        {
-            Map.Entry entry = (Map.Entry)i.next();
-
-            String key = (String)entry.getKey();
-
-            for(int j = 0; j < removeKeys.length; j++)
-            {
-                if (key.equalsIgnoreCase(removeKeys[j])) continue nextkey;
-            }
-
-            if (result == null) result = new HashMap(input.size());
-
-            result.put(key, entry.getValue());
-        }
-
-        return result;
-    }
-
-    /**
-     * Searches a Map for given key, caselessly. The Map is expected to consist
-     * of Strings for keys and values. Returns the value for the first key found
-     * that matches (caselessly) the input key. Returns null if no value found.
-     */
-
-    protected String findValueCaselessly(String key, Map map)
-    {
-        String result = (String)map.get(key);
-
-        if (result != null) return result;
-
-        Iterator i = map.entrySet().iterator();
-        while(i.hasNext())
-        {
-            Map.Entry entry = (Map.Entry)i.next();
-
-            String entryKey = (String)entry.getKey();
-
-            if (entryKey.equalsIgnoreCase(key)) return (String)entry.getValue();
-        }
-
-        return null;
-    }
-
-    /**
-     * Gets the current location within the file. This allows the location to be
-     * created only as needed, and multiple objects on the same line can share
-     * the same Location instance.
+     * Subclasses may override but <b>must </b> ensure they rethrow the exception.
      * 
-     * @since 3.0
+     * @param exception
+     * @param line
+     *            ignored by the default impl
+     * @param cursor
+     *            ignored by the default impl
+     * @throws ApplicationRuntimeException
+     *             always rethrown in order to terminate the parse.
      */
 
-    protected Location getCurrentLocation()
+    protected void templateParseProblem(ApplicationRuntimeException exception, int line, int cursor)
+            throws ApplicationRuntimeException
     {
-        if (_currentLocation == null) _currentLocation = new LocationImpl(_resourceLocation, _line);
-
-        return _currentLocation;
+        throw exception;
     }
 
     /**
@@ -764,7 +419,8 @@
      */
     protected List getTokens()
     {
-        if (_tokens == null) return Collections.EMPTY_LIST;
+        if (_tokens == null)
+            return Collections.EMPTY_LIST;
 
         return _tokens;
     }
@@ -777,9 +433,10 @@
     {
         try
         {
-            for(int i = 0; i < match.length; i++)
+            for (int i = 0; i < match.length; i++)
             {
-                if (_templateData[_cursor + i] != match[i]) return false;
+                if (_templateData[_cursor + i] != match[i])
+                    return false;
             }
 
             // Every character matched.
@@ -792,18 +449,27 @@
         }
     }
 
-    protected void parse()
-        throws TemplateParseException
+    private static final char[] COMMENT_START = new char[]
+    { '<', '!', '-', '-' };
+
+    private static final char[] COMMENT_END = new char[]
+    { '-', '-', '>' };
+
+    private static final char[] CLOSE_TAG = new char[]
+    { '<', '/' };
+
+    protected void parse() throws TemplateParseException
     {
         _cursor = 0;
         _blockStart = -1;
         int length = _templateData.length;
 
-        while(_cursor < length)
+        while (_cursor < length)
         {
             if (_templateData[_cursor] != '<')
             {
-                if (_blockStart < 0 && !_ignoring) _blockStart = _cursor;
+                if (_blockStart < 0 && !_ignoring)
+                    _blockStart = _cursor;
 
                 advance();
                 continue;
@@ -828,8 +494,7 @@
             startTag();
         }
 
-        // Usually there's some text at the end of the template (after the last
-        // closing tag) that
+        // Usually there's some text at the end of the template (after the last closing tag) that
         // should
         // be added. Often the last few tags are static tags so we definately
         // need to end the text block.
@@ -838,220 +503,73 @@
     }
 
     /**
-     * Parses the template data into an array of {@link TemplateToken}s.
-     * <p>
-     * The parser is <i>decidedly </i> not threadsafe, so care should be taken
-     * that only a single thread accesses it.
-     * 
-     * @param templateData
-     *            the HTML template to parse. Some tokens will hold a reference
-     *            to this array.
-     * @param delegate
-     *            object that "knows" about defined components
-     * @param resourceLocation
-     *            a description of where the template originated from, used with
-     *            error messages.
+     * Advance forward in the document until the end of the comment is reached. In addition, skip
+     * any whitespace following the comment.
      */
 
-    public TemplateToken[] parse(char[] templateData, ITemplateParserDelegate delegate, Resource resourceLocation)
-        throws TemplateParseException
+    private void skipComment() throws TemplateParseException
     {
-        try
-        {
-            beforeParse(templateData, delegate, resourceLocation);
+        int length = _templateData.length;
+        int startLine = _line;
 
-            parse();
+        if (_blockStart < 0 && !_ignoring)
+            _blockStart = _cursor;
 
-            return (TemplateToken[])_tokens.toArray(new TemplateToken[_tokens.size()]);
-        }
-        finally
+        while (true)
         {
-            afterParse();
+            if (_cursor >= length)
+                templateParseProblem(ParseMessages.commentNotEnded(startLine), new LocationImpl(
+                        _resourceLocation, startLine), startLine, _cursor);
+
+            if (lookahead(COMMENT_END))
+                break;
+
+            // Not the end of the comment, advance over it.
+
+            advance();
         }
+
+        _cursor += COMMENT_END.length;
+        advanceOverWhitespace();
     }
 
-    private void processComponentStart(String tagName, String jwcId, boolean emptyTag, int startLine, int cursorStart,
-            Location startLocation)
-        throws TemplateParseException
+    private void addTextToken(int end)
     {
-        if (jwcId.equalsIgnoreCase(CONTENT_ID))
-        {
-            processContentTag(tagName, startLine, cursorStart, emptyTag);
+        // No active block to add to.
 
+        if (_blockStart < 0)
             return;
-        }
 
-        boolean isRemoveId = jwcId.equalsIgnoreCase(REMOVE_ID);
+        if (_blockStart <= end)
+        {
+            // This seems odd, shouldn't the location be the current location? I guess
+            // no errors are ever reported for a text token.
 
-        if (_ignoring && !isRemoveId)
-            templateParseProblem(ParseMessages.componentMayNotBeIgnored(tagName, startLine), startLocation, startLine,
-                    cursorStart);
+            TemplateToken token = _factory.createTextToken(
+                    _templateData,
+                    _blockStart,
+                    end,
+                    _templateLocation);
 
-        String type = null;
-        boolean allowBody = false;
+            _tokens.add(token);
+        }
 
-        if (_patternMatcher.matches(jwcId, _implicitIdPattern))
-        {
-            MatchResult match = _patternMatcher.getMatch();
+        _blockStart = -1;
+    }
 
-            jwcId = match.group(IMPLICIT_ID_PATTERN_ID_GROUP);
-            type = match.group(IMPLICIT_ID_PATTERN_TYPE_GROUP);
-
-            String libraryId = match.group(IMPLICIT_ID_PATTERN_LIBRARY_ID_GROUP);
-            String simpleType = match.group(IMPLICIT_ID_PATTERN_SIMPLE_TYPE_GROUP);
-
-            // If (and this is typical) no actual component id was specified,
-            // then generate one on the fly.
-            // The allocated id for anonymous components is
-            // based on the simple (unprefixed) type, but starts
-            // with a leading dollar sign to ensure no conflicts
-            // with user defined component ids (which don't allow dollar signs
-            // in the id).
-            // New for 4.0: the component type may included slashes ('/'), but
-            // these
-            // are not valid identifiers, so we convert them to '$'.
-
-            if (jwcId == null) jwcId = _idAllocator.allocateId("$" + simpleType.replace('/', '$'));
-
-            try
-            {
-                allowBody = _delegate.getAllowBody(libraryId, simpleType, startLocation);
-            }
-            catch (ApplicationRuntimeException e)
-            {
-                // give subclasses a chance to handle and rethrow
-                templateParseProblem(e, startLine, cursorStart);
-            }
-
-        }
-        else
-        {
-            if (!isRemoveId)
-            {
-                if (!_patternMatcher.matches(jwcId, _simpleIdPattern))
-                    templateParseProblem(ParseMessages.componentIdInvalid(tagName, startLine, jwcId), startLocation,
-                            startLine, cursorStart);
-
-                if (!_delegate.getKnownComponent(jwcId))
-                    templateParseProblem(ParseMessages.unknownComponentId(tagName, startLine, jwcId), startLocation,
-                            startLine, cursorStart);
-
-                try
-                {
-                    allowBody = _delegate.getAllowBody(jwcId, startLocation);
-                }
-                catch (ApplicationRuntimeException e)
-                {
-                    // give subclasses a chance to handle and rethrow
-                    templateParseProblem(e, startLine, cursorStart);
-                }
-            }
-        }
-
-        // Ignore the body if we're removing the entire tag,
-        // of if the corresponding component doesn't allow
-        // a body.
-
-        boolean ignoreBody = !emptyTag && (isRemoveId || !allowBody);
-
-        if (_ignoring && ignoreBody)
-            templateParseProblem(ParseMessages.nestedIgnore(tagName, startLine), new LocationImpl(_resourceLocation,
-                    startLine), startLine, cursorStart);
-
-        if (!emptyTag) pushNewTag(tagName, startLine, isRemoveId, ignoreBody);
-
-        // End any open block.
-
-        addTextToken(cursorStart - 1);
-
-        if (!isRemoveId)
-        {
-            addOpenToken(tagName, jwcId, type, startLocation);
-
-            if (emptyTag) _tokens.add(_factory.createCloseToken(tagName, getCurrentLocation()));
-        }
-
-        advance();
-    }
-
-    private void processContentTag(String tagName, int startLine, int cursorStart, boolean emptyTag)
-        throws TemplateParseException
-    {
-        if (_ignoring)
-            templateParseProblem(ParseMessages.contentBlockMayNotBeIgnored(tagName, startLine), new LocationImpl(
-                    _resourceLocation, startLine), startLine, cursorStart);
-
-        if (emptyTag)
-            templateParseProblem(ParseMessages.contentBlockMayNotBeEmpty(tagName, startLine), new LocationImpl(
-                    _resourceLocation, startLine), startLine, cursorStart);
-
-        _tokens.clear();
-        _blockStart = -1;
-
-        Tag tag = new Tag(tagName, startLine);
-
-        tag._mustBalance = true;
-        tag._content = true;
-
-        _stack.clear();
-        _stack.add(tag);
-
-        advance();
-    }
-
-    private void pushNewTag(String tagName, int startLine, boolean isRemoveId, boolean ignoreBody)
-    {
-        Tag tag = new Tag(tagName, startLine);
-
-        tag._component = !isRemoveId;
-        tag._removeTag = isRemoveId;
-
-        tag._ignoringBody = ignoreBody;
-
-        _ignoring = tag._ignoringBody;
-
-        tag._mustBalance = true;
-
-        _stack.add(tag);
-    }
-
-    public void setFactory(TemplateTokenFactory factory)
-    {
-        _factory = factory;
-    }
-
-    /**
-     * Advance forward in the document until the end of the comment is reached.
-     * In addition, skip any whitespace following the comment.
-     */
-
-    private void skipComment()
-        throws TemplateParseException
-    {
-        int length = _templateData.length;
-        int startLine = _line;
-
-        if (_blockStart < 0 && !_ignoring) _blockStart = _cursor;
+    private static final int WAIT_FOR_ATTRIBUTE_NAME = 0;
 
-        while(true)
-        {
-            if (_cursor >= length)
-                templateParseProblem(ParseMessages.commentNotEnded(startLine), new LocationImpl(_resourceLocation,
-                        startLine), startLine, _cursor);
+    private static final int COLLECT_ATTRIBUTE_NAME = 1;
 
-            if (lookahead(COMMENT_END)) break;
+    private static final int ADVANCE_PAST_EQUALS = 2;
 
-            // Not the end of the comment, advance over it.
+    private static final int WAIT_FOR_ATTRIBUTE_VALUE = 3;
 
-            advance();
-        }
+    private static final int COLLECT_QUOTED_VALUE = 4;
 
-        _cursor += COMMENT_END.length;
-        advanceOverWhitespace();
-    }
+    private static final int COLLECT_UNQUOTED_VALUE = 5;
 
-    private void startTag()
-        throws TemplateParseException
+    private void startTag() throws TemplateParseException
     {
         int cursorStart = _cursor;
         int length = _templateData.length;
@@ -1067,7 +585,7 @@
 
         // Collect the element type
 
-        while(_cursor < length)
+        while (_cursor < length)
         {
             char ch = _templateData[_cursor];
 
@@ -1091,189 +609,185 @@
 
         // Collect each attribute
 
-        while(!endOfTag)
+        while (!endOfTag)
         {
             if (_cursor >= length)
             {
-                String message = (tagName == null) ? ParseMessages.unclosedUnknownTag(startLine) : ParseMessages
-                        .unclosedTag(tagName, startLine);
+                String message = (tagName == null) ? ParseMessages.unclosedUnknownTag(startLine)
+                        : ParseMessages.unclosedTag(tagName, startLine);
 
                 templateParseProblem(message, startLocation, startLine, cursorStart);
             }
 
             char ch = _templateData[_cursor];
 
-            switch(state)
+            switch (state)
             {
-            case WAIT_FOR_ATTRIBUTE_NAME:
+                case WAIT_FOR_ATTRIBUTE_NAME:
 
-                // Ignore whitespace before the next attribute name, while
-                // looking for the end of the current tag.
+                    // Ignore whitespace before the next attribute name, while
+                    // looking for the end of the current tag.
 
-                if (ch == '/')
-                {
-                    emptyTag = true;
-                    advance();
-                    break;
-                }
+                    if (ch == '/')
+                    {
+                        emptyTag = true;
+                        advance();
+                        break;
+                    }
+
+                    if (ch == '>')
+                    {
+                        endOfTag = true;
+                        break;
+                    }
+
+                    if (Character.isWhitespace(ch))
+                    {
+                        advance();
+                        break;
+                    }
 
-                if (ch == '>')
-                {
-                    endOfTag = true;
-                    break;
-                }
+                    // Found non-whitespace, assume its the attribute name.
+                    // Note: could use a check here for non-alpha.
 
-                if (Character.isWhitespace(ch))
-                {
+                    attributeNameStart = _cursor;
+                    state = COLLECT_ATTRIBUTE_NAME;
                     advance();
                     break;
-                }
 
-                // Found non-whitespace, assume its the attribute name.
-                // Note: could use a check here for non-alpha.
+                case COLLECT_ATTRIBUTE_NAME:
 
-                attributeNameStart = _cursor;
-                state = COLLECT_ATTRIBUTE_NAME;
-                advance();
-                break;
+                    // Looking for end of attribute name.
 
-            case COLLECT_ATTRIBUTE_NAME:
+                    if (ch == '=' || ch == '/' || ch == '>' || Character.isWhitespace(ch))
+                    {
+                        attributeName = new String(_templateData, attributeNameStart, _cursor
+                                - attributeNameStart);
 
-                // Looking for end of attribute name.
+                        state = ADVANCE_PAST_EQUALS;
+                        break;
+                    }
 
-                if (ch == '=' || ch == '/' || ch == '>' || Character.isWhitespace(ch))
-                {
-                    attributeName = new String(_templateData, attributeNameStart, _cursor - attributeNameStart);
+                    // Part of the attribute name
 
-                    state = ADVANCE_PAST_EQUALS;
+                    advance();
                     break;
-                }
-
-                // Part of the attribute name
-
-                advance();
-                break;
 
-            case ADVANCE_PAST_EQUALS:
+                case ADVANCE_PAST_EQUALS:
 
-                // Looking for the '=' sign. May hit the end of the tag, or (for
-                // bare
-                // attributes),
-                // the next attribute name.
-
-                if (ch == '/' || ch == '>')
-                {
-                    // A bare attribute, which is not interesting to
-                    // us.
+                    // Looking for the '=' sign. May hit the end of the tag, or (for bare
+                    // attributes),
+                    // the next attribute name.
+
+                    if (ch == '/' || ch == '>')
+                    {
+                        // A bare attribute, which is not interesting to
+                        // us.
+
+                        state = WAIT_FOR_ATTRIBUTE_NAME;
+                        break;
+                    }
+
+                    if (Character.isWhitespace(ch))
+                    {
+                        advance();
+                        break;
+                    }
+
+                    if (ch == '=')
+                    {
+                        state = WAIT_FOR_ATTRIBUTE_VALUE;
+                        quoteChar = 0;
+                        attributeValueStart = -1;
+                        advance();
+                        break;
+                    }
+
+                    // Otherwise, an HTML style "bare" attribute (such as <select multiple>).
+                    // We aren't interested in those (we're just looking for the id or jwcid
+                    // attribute).
 
                     state = WAIT_FOR_ATTRIBUTE_NAME;
                     break;
-                }
-
-                if (Character.isWhitespace(ch))
-                {
-                    advance();
-                    break;
-                }
-
-                if (ch == '=')
-                {
-                    state = WAIT_FOR_ATTRIBUTE_VALUE;
-                    quoteChar = 0;
-                    attributeValueStart = -1;
-                    advance();
-                    break;
-                }
-
-                // Otherwise, an HTML style "bare" attribute (such as <select
-                // multiple>).
-                // We aren't interested in those (we're just looking for the id
-                // or jwcid
-                // attribute).
-
-                state = WAIT_FOR_ATTRIBUTE_NAME;
-                break;
-
-            case WAIT_FOR_ATTRIBUTE_VALUE:
-
-                if (ch == '/' || ch == '>')
-                    templateParseProblem(ParseMessages.missingAttributeValue(tagName, _line, attributeName),
-                            getCurrentLocation(), _line, _cursor);
 
-                // Ignore whitespace between '=' and the attribute value. Also,
-                // look
-                // for initial quote.
+                case WAIT_FOR_ATTRIBUTE_VALUE:
 
-                if (Character.isWhitespace(ch))
-                {
-                    advance();
-                    break;
-                }
+                    if (ch == '/' || ch == '>')
+                        templateParseProblem(ParseMessages.missingAttributeValue(
+                                tagName,
+                                _line,
+                                attributeName), getCurrentLocation(), _line, _cursor);
+
+                    // Ignore whitespace between '=' and the attribute value. Also, look
+                    // for initial quote.
+
+                    if (Character.isWhitespace(ch))
+                    {
+                        advance();
+                        break;
+                    }
+
+                    if (ch == '\'' || ch == '"')
+                    {
+                        quoteChar = ch;
+
+                        state = COLLECT_QUOTED_VALUE;
+                        advance();
+                        attributeValueStart = _cursor;
+                        attributeBeginEvent(attributeName, _line, attributeValueStart);
+                        break;
+                    }
 
-                if (ch == '\'' || ch == '"')
-                {
-                    quoteChar = ch;
+                    // Not whitespace or quote, must be start of unquoted attribute.
 
-                    state = COLLECT_QUOTED_VALUE;
-                    advance();
+                    state = COLLECT_UNQUOTED_VALUE;
                     attributeValueStart = _cursor;
                     attributeBeginEvent(attributeName, _line, attributeValueStart);
                     break;
-                }
-
-                // Not whitespace or quote, must be start of unquoted attribute.
 
-                state = COLLECT_UNQUOTED_VALUE;
-                attributeValueStart = _cursor;
-                attributeBeginEvent(attributeName, _line, attributeValueStart);
-                break;
-
-            case COLLECT_QUOTED_VALUE:
-
-                // Start collecting the quoted attribute value. Stop at the
-                // matching quote
-                // character,
-                // unless bare, in which case, stop at the next whitespace.
-
-                if (ch == quoteChar)
-                {
-                    String attributeValue = new String(_templateData, attributeValueStart, _cursor
-                            - attributeValueStart);
-
-                    attributeEndEvent(_cursor);
+                case COLLECT_QUOTED_VALUE:
 
-                    addAttributeIfUnique(tagName, attributeName, attributeValue);
+                    // Start collecting the quoted attribute value. Stop at the matching quote
+                    // character,
+                    // unless bare, in which case, stop at the next whitespace.
+
+                    if (ch == quoteChar)
+                    {
+                        String attributeValue = new String(_templateData, attributeValueStart,
+                                _cursor - attributeValueStart);
+
+                        attributeEndEvent(_cursor);
+
+                        addAttributeIfUnique(tagName, attributeName, attributeValue);
+
+                        // Advance over the quote.
+                        advance();
+                        state = WAIT_FOR_ATTRIBUTE_NAME;
+                        break;
+                    }
 
-                    // Advance over the quote.
                     advance();
-                    state = WAIT_FOR_ATTRIBUTE_NAME;
                     break;
-                }
 
-                advance();
-                break;
-
-            case COLLECT_UNQUOTED_VALUE:
-
-                // An unquoted attribute value ends with whitespace
-                // or the end of the enclosing tag.
+                case COLLECT_UNQUOTED_VALUE:
 
-                if (ch == '/' || ch == '>' || Character.isWhitespace(ch))
-                {
-                    String attributeValue = new String(_templateData, attributeValueStart, _cursor
-                            - attributeValueStart);
+                    // An unquoted attribute value ends with whitespace
+                    // or the end of the enclosing tag.
 
-                    attributeEndEvent(_cursor);
-                    addAttributeIfUnique(tagName, attributeName, attributeValue);
+                    if (ch == '/' || ch == '>' || Character.isWhitespace(ch))
+                    {
+                        String attributeValue = new String(_templateData, attributeValueStart,
+                                _cursor - attributeValueStart);
+
+                        attributeEndEvent(_cursor);
+                        addAttributeIfUnique(tagName, attributeName, attributeValue);
+
+                        state = WAIT_FOR_ATTRIBUTE_NAME;
+                        break;
+                    }
 
-                    state = WAIT_FOR_ATTRIBUTE_NAME;
+                    advance();
                     break;
-                }
-
-                advance();
-
-            default:
-                break;
             }
         }
 
@@ -1287,8 +801,11 @@
         if (localizationKey != null && tagName.equalsIgnoreCase("span") && jwcId == null)
         {
             if (_ignoring)
-                templateParseProblem(ParseMessages.componentMayNotBeIgnored(tagName, startLine), startLocation,
-                        startLine, cursorStart);
+                templateParseProblem(
+                        ParseMessages.componentMayNotBeIgnored(tagName, startLine),
+                        startLocation,
+                        startLine,
+                        cursorStart);
 
             // If the tag isn't empty, then create a Tag instance to ignore the
             // body of the tag.
@@ -1321,9 +838,14 @@
 
             boolean raw = checkBoolean(RAW_ATTRIBUTE_NAME, _attributes);
 
-            Map attributes = filter(_attributes, new String[] { LOCALIZATION_KEY_ATTRIBUTE_NAME, RAW_ATTRIBUTE_NAME });
+            Map attributes = filter(_attributes, new String[]
+            { LOCALIZATION_KEY_ATTRIBUTE_NAME, RAW_ATTRIBUTE_NAME });
 
-            TemplateToken token = _factory.createLocalizationToken(tagName, localizationKey, raw, attributes,
+            TemplateToken token = _factory.createLocalizationToken(
+                    tagName,
+                    localizationKey,
+                    raw,
+                    attributes,
                     startLocation);
 
             _tokens.add(token);
@@ -1348,12 +870,37 @@
 
         // If there wasn't an active block, then start one.
 
-        if (_blockStart < 0 && !_ignoring) _blockStart = cursorStart;
+        if (_blockStart < 0 && !_ignoring)
+            _blockStart = cursorStart;
 
         advance();
     }
 
     /**
+     * @throws TemplateParseException
+     * @since 4.0
+     */
+
+    private void addAttributeIfUnique(String tagName, String attributeName, String attributeValue)
+            throws TemplateParseException
+    {
+
+        if (_attributes.containsKey(attributeName))
+            templateParseProblem(
+                    ParseMessages.duplicateTagAttribute(tagName, _line, attributeName),
+                    getCurrentLocation(),
+                    _line,
+                    _cursor);
+
+        _attributes.put(attributeName, attributeValue);
+    }
+
+    /**
+     * Processes a tag that is the open tag for a component (but also handles the $remove$ and
+     * $content$ tags).
+     */
+
+    /**
      * Notify that the beginning of a tag has been detected.
      * <p>
      * Default implementation does nothing.
@@ -1372,52 +919,541 @@
     }
 
     /**
-     * Used by the parser to report tapestry runtime specific problems in the
-     * parse. Parsing <b>must </b> stop when a problem is reported.
-     * <p>
-     * The default implementation simply rethrows the exception.
+     * Notify that the beginning of an attribute value has been detected.
      * <p>
-     * Subclasses may override but <b>must </b> ensure they rethrow the
-     * exception.
-     * 
-     * @param exception
-     * @param line
-     *            ignored by the default impl
-     * @param cursor
-     *            ignored by the default impl
-     * @throws ApplicationRuntimeException
-     *             always rethrown in order to terminate the parse.
+     * Default implementation does nothing.
      */
-
-    protected void templateParseProblem(ApplicationRuntimeException exception, int line, int cursor)
+    protected void attributeBeginEvent(String attributeName, int startLine, int cursorPosition)
     {
-        throw exception;
     }
 
     /**
-     * Used by the parser to report problems in the parse. Parsing <b>must </b>
-     * stop when a problem is reported.
-     * <p>
-     * The default implementation simply throws an exception that contains the
-     * message and location parameters.
+     * Notify that the end of the current attribute value has been detected.
      * <p>
-     * Subclasses may override but <b>must </b> ensure they throw the required
-     * exception.
-     * 
-     * @param message
-     * @param location
-     * @param line
-     *            ignored by the default impl
-     * @param cursor
-     *            ignored by the default impl
-     * @throws TemplateParseException
-     *             always thrown in order to terminate the parse.
+     * Default implementation does nothing.
      */
+    protected void attributeEndEvent(int cursorPosition)
+    {
+    }
 
-    protected void templateParseProblem(String message, Location location, int line, int cursor)
-        throws TemplateParseException
+    private void processComponentStart(String tagName, String jwcId, boolean emptyTag,
+            int startLine, int cursorStart, Location startLocation) throws TemplateParseException
     {
-        throw new TemplateParseException(message, location);
+        if (jwcId.equalsIgnoreCase(CONTENT_ID))
+        {
+            processContentTag(tagName, startLine, cursorStart, emptyTag);
+
+            return;
+        }
+
+        boolean isRemoveId = jwcId.equalsIgnoreCase(REMOVE_ID);
+
+        if (_ignoring && !isRemoveId)
+            templateParseProblem(
+                    ParseMessages.componentMayNotBeIgnored(tagName, startLine),
+                    startLocation,
+                    startLine,
+                    cursorStart);
+
+        String type = null;
+        boolean allowBody = false;
+
+        if (_patternMatcher.matches(jwcId, _implicitIdPattern))
+        {
+            MatchResult match = _patternMatcher.getMatch();
+
+            jwcId = match.group(IMPLICIT_ID_PATTERN_ID_GROUP);
+            type = match.group(IMPLICIT_ID_PATTERN_TYPE_GROUP);
+
+            String libraryId = match.group(IMPLICIT_ID_PATTERN_LIBRARY_ID_GROUP);
+            String simpleType = match.group(IMPLICIT_ID_PATTERN_SIMPLE_TYPE_GROUP);
+
+            // If (and this is typical) no actual component id was specified,
+            // then generate one on the fly.
+            // The allocated id for anonymous components is
+            // based on the simple (unprefixed) type, but starts
+            // with a leading dollar sign to ensure no conflicts
+            // with user defined component ids (which don't allow dollar signs
+            // in the id).
+            // New for 4.0: the component type may included slashes ('/'), but these
+            // are not valid identifiers, so we convert them to '$'.
+
+            if (jwcId == null)
+                jwcId = _idAllocator.allocateId("$" + simpleType.replace('/', '$'));
+
+            try
+            {
+                allowBody = _delegate.getAllowBody(libraryId, simpleType, startLocation);
+            }
+            catch (ApplicationRuntimeException e)
+            {
+                // give subclasses a chance to handle and rethrow
+                templateParseProblem(e, startLine, cursorStart);
+            }
+
+        }
+        else
+        {
+            if (!isRemoveId)
+            {
+                if (!_patternMatcher.matches(jwcId, _simpleIdPattern))
+                    templateParseProblem(
+                            ParseMessages.componentIdInvalid(tagName, startLine, jwcId),
+                            startLocation,
+                            startLine,
+                            cursorStart);
+
+                if (!_delegate.getKnownComponent(jwcId))
+                    templateParseProblem(
+                            ParseMessages.unknownComponentId(tagName, startLine, jwcId),
+                            startLocation,
+                            startLine,
+                            cursorStart);
+
+                try
+                {
+                    allowBody = _delegate.getAllowBody(jwcId, startLocation);
+                }
+                catch (ApplicationRuntimeException e)
+                {
+                    // give subclasses a chance to handle and rethrow
+                    templateParseProblem(e, startLine, cursorStart);
+                }
+            }
+        }
+
+        // Ignore the body if we're removing the entire tag,
+        // of if the corresponding component doesn't allow
+        // a body.
+
+        boolean ignoreBody = !emptyTag && (isRemoveId || !allowBody);
+
+        if (_ignoring && ignoreBody)
+            templateParseProblem(ParseMessages.nestedIgnore(tagName, startLine), new LocationImpl(
+                    _resourceLocation, startLine), startLine, cursorStart);
+
+        if (!emptyTag)
+            pushNewTag(tagName, startLine, isRemoveId, ignoreBody);
+
+        // End any open block.
+
+        addTextToken(cursorStart - 1);
+
+        if (!isRemoveId)
+        {
+            addOpenToken(tagName, jwcId, type, startLocation);
+
+            if (emptyTag)
+                _tokens.add(_factory.createCloseToken(tagName, getCurrentLocation()));
+        }
+
+        advance();
+    }
+
+    private void pushNewTag(String tagName, int startLine, boolean isRemoveId, boolean ignoreBody)
+    {
+        Tag tag = new Tag(tagName, startLine);
+
+        tag._component = !isRemoveId;
+        tag._removeTag = isRemoveId;
+
+        tag._ignoringBody = ignoreBody;
+
+        _ignoring = tag._ignoringBody;
+
+        tag._mustBalance = true;
+
+        _stack.add(tag);
+    }
+
+    private void processContentTag(String tagName, int startLine, int cursorStart, boolean emptyTag)
+            throws TemplateParseException
+    {
+        if (_ignoring)
+            templateParseProblem(
+                    ParseMessages.contentBlockMayNotBeIgnored(tagName, startLine),
+                    new LocationImpl(_resourceLocation, startLine),
+                    startLine,
+                    cursorStart);
+
+        if (emptyTag)
+            templateParseProblem(
+                    ParseMessages.contentBlockMayNotBeEmpty(tagName, startLine),
+                    new LocationImpl(_resourceLocation, startLine),
+                    startLine,
+                    cursorStart);
+
+        _tokens.clear();
+        _blockStart = -1;
+
+        Tag tag = new Tag(tagName, startLine);
+
+        tag._mustBalance = true;
+        tag._content = true;
+
+        _stack.clear();
+        _stack.add(tag);
+
+        advance();
+    }
+
+    private void addOpenToken(String tagName, String jwcId, String type, Location location)
+    {
+        OpenToken token = _factory.createOpenToken(tagName, jwcId, type, location);
+        _tokens.add(token);
+
+        if (_attributes.isEmpty())
+            return;
+
+        Iterator i = _attributes.entrySet().iterator();
+        while (i.hasNext())
+        {
+            Map.Entry entry = (Map.Entry) i.next();
+
+            String key = (String) entry.getKey();
+
+            if (key.equalsIgnoreCase(_componentAttributeName))
+                continue;
+
+            String value = (String) entry.getValue();
+
+            addAttributeToToken(token, key, value);
+        }
+    }
+
+    /**
+     * Adds the attribute to the token (identifying prefixes and whatnot is now done downstream).
+     * 
+     * @since 3.0
+     */
+
+    private void addAttributeToToken(OpenToken token, String name, String attributeValue)
+    {
+        token.addAttribute(name, convertEntitiesToPlain(attributeValue));
+    }
+
+    /**
+     * Invoked to handle a closing tag, i.e., &lt;/foo&gt;. When a tag closes, it will match against
+     * a tag on the open tag start. Preferably the top tag on the stack (if everything is well
+     * balanced), but this is HTML, not XML, so many tags won't balance.
+     * <p>
+     * Once the matching tag is located, the question is ... is the tag dynamic or static? If
+     * static, then the current text block is extended to include this close tag. If dynamic, then
+     * the current text block is ended (before the '&lt;' that starts the tag) and a close token is
+     * added.
+     * <p>
+     * In either case, the matching static element and anything above it is removed, and the cursor
+     * is left on the character following the '&gt;'.
+     */
+
+    private void closeTag() throws TemplateParseException
+    {
+        int cursorStart = _cursor;
+        int length = _templateData.length;
+        int startLine = _line;
+
+        Location startLocation = getCurrentLocation();
+
+        _cursor += CLOSE_TAG.length;
+
+        int tagStart = _cursor;
+
+        while (true)
+        {
+            if (_cursor >= length)
+                templateParseProblem(
+                        ParseMessages.incompleteCloseTag(startLine),
+                        startLocation,
+                        startLine,
+                        cursorStart);
+
+            char ch = _templateData[_cursor];
+
+            if (ch == '>')
+                break;
+
+            advance();
+        }
+
+        String tagName = new String(_templateData, tagStart, _cursor - tagStart);
+
+        int stackPos = _stack.size() - 1;
+        Tag tag = null;
+
+        while (stackPos >= 0)
+        {
+            tag = (Tag) _stack.get(stackPos);
+
+            if (tag.match(tagName))
+                break;
+
+            if (tag._mustBalance)
+                templateParseProblem(ParseMessages.improperlyNestedCloseTag(
+                        tagName,
+                        startLine,
+                        tag._tagName,
+                        tag._line), startLocation, startLine, cursorStart);
+
+            stackPos--;
+        }
+
+        if (stackPos < 0)
+            templateParseProblem(
+                    ParseMessages.unmatchedCloseTag(tagName, startLine),
+                    startLocation,
+                    startLine,
+                    cursorStart);
+
+        // Special case for the content tag
+
+        if (tag._content)
+        {
+            addTextToken(cursorStart - 1);
+
+            // Advance the cursor right to the end.
+
+            _cursor = length;
+            _stack.clear();
+            return;
+        }
+
+        // When a component closes, add a CLOSE tag.
+        if (tag._component)
+        {
+            addTextToken(cursorStart - 1);
+
+            _tokens.add(_factory.createCloseToken(tagName, getCurrentLocation()));
+        }
+        else
+        {
+            // The close of a static tag. Unless removing the tag
+            // entirely, make sure the block tag is part of a text block.
+
+            if (_blockStart < 0 && !tag._removeTag && !_ignoring)
+                _blockStart = cursorStart;
+        }
+
+        // Remove all elements at stackPos or above.
+
+        for (int i = _stack.size() - 1; i >= stackPos; i--)
+            _stack.remove(i);
+
+        // Advance cursor past '>'
+
+        advance();
+
+        // If editting out the tag (i.e., $remove$) then kill any whitespace.
+        // For components that simply don't contain a body, removeTag will
+        // be false.
+
+        if (tag._removeTag)
+            advanceOverWhitespace();
+
+        // If we were ignoring the body of the tag, then clear the ignoring
+        // flag, since we're out of the body.
+
+        if (tag._ignoringBody)
+            _ignoring = false;
+    }
+
+    /**
+     * Advances the cursor to the next character. If the end-of-line is reached, then increments the
+     * line counter.
+     */
+
+    private void advance()
+    {
+        int length = _templateData.length;
+
+        if (_cursor >= length)
+            return;
+
+        char ch = _templateData[_cursor];
+
+        _cursor++;
+
+        if (ch == '\n')
+        {
+            _line++;
+            _currentLocation = null;
+            return;
+        }
+
+        // A \r, or a \r\n also counts as a new line.
+
+        if (ch == '\r')
+        {
+            _line++;
+            _currentLocation = null;
+
+            if (_cursor < length && _templateData[_cursor] == '\n')
+                _cursor++;
+
+            return;
+        }
+
+        // Not an end-of-line character.
+
+    }
+
+    private void advanceOverWhitespace()
+    {
+        int length = _templateData.length;
+
+        while (_cursor < length)
+        {
+            char ch = _templateData[_cursor];
+            if (!Character.isWhitespace(ch))
+                return;
+
+            advance();
+        }
+    }
+
+    /**
+     * Returns a new Map that is a copy of the input Map with some key/value pairs removed. A list
+     * of keys is passed in and matching keys (caseless comparison) from the input Map are excluded
+     * from the output map. May return null (rather than return an empty Map).
+     */
+
+    private Map filter(Map input, String[] removeKeys)
+    {
+        if (input == null || input.isEmpty())
+            return null;
+
+        Map result = null;
+
+        Iterator i = input.entrySet().iterator();
+
+        nextkey: while (i.hasNext())
+        {
+            Map.Entry entry = (Map.Entry) i.next();
+
+            String key = (String) entry.getKey();
+
+            for (int j = 0; j < removeKeys.length; j++)
+            {
+                if (key.equalsIgnoreCase(removeKeys[j]))
+                    continue nextkey;
+            }
+
+            if (result == null)
+                result = new HashMap(input.size());
+
+            result.put(key, entry.getValue());
+        }
+
+        return result;
+    }
+
+    /**
+     * Searches a Map for given key, caselessly. The Map is expected to consist of Strings for keys
+     * and values. Returns the value for the first key found that matches (caselessly) the input
+     * key. Returns null if no value found.
+     */
+
+    protected String findValueCaselessly(String key, Map map)
+    {
+        String result = (String) map.get(key);
+
+        if (result != null)
+            return result;
+
+        Iterator i = map.entrySet().iterator();
+        while (i.hasNext())
+        {
+            Map.Entry entry = (Map.Entry) i.next();
+
+            String entryKey = (String) entry.getKey();
+
+            if (entryKey.equalsIgnoreCase(key))
+                return (String) entry.getValue();
+        }
+
+        return null;
+    }
+
+    /**
+     * Conversions needed by {@link #convertEntitiesToPlain(String)}
+     */
+
+    private static final String[] CONVERSIONS =
+    { "&lt;", "<", "&gt;", ">", "&quot;", "\"", "&amp;", "&" };
+
+    /**
+     * Provided a raw input string that has been recognized to be an expression, this removes excess
+     * white space and converts &amp;amp;;, &amp;quot;; &amp;lt;; and &amp;gt;; to their normal
+     * character values (otherwise its impossible to specify those values in expressions in the
+     * template).
+     */
+
+    private String convertEntitiesToPlain(String input)
+    {
+        int inputLength = input.length();
+
+        StringBuffer buffer = new StringBuffer(inputLength);
+
+        int cursor = 0;
+
+        outer: while (cursor < inputLength)
+        {
+            for (int i = 0; i < CONVERSIONS.length; i += 2)
+            {
+                String entity = CONVERSIONS[i];
+                int entityLength = entity.length();
+                String value = CONVERSIONS[i + 1];
+
+                if (cursor + entityLength > inputLength)
+                    continue;
+
+                if (input.substring(cursor, cursor + entityLength).equals(entity))
+                {
+                    buffer.append(value);
+                    cursor += entityLength;
+                    continue outer;
+                }
+            }
+
+            buffer.append(input.charAt(cursor));
+            cursor++;
+        }
+
+        return buffer.toString().trim();
+    }
+
+    /**
+     * Returns true if the map contains the given key (caseless search) and the value is "true"
+     * (caseless comparison).
+     */
+
+    private boolean checkBoolean(String key, Map map)
+    {
+        String value = findValueCaselessly(key, map);
+
+        if (value == null)
+            return false;
+
+        return value.equalsIgnoreCase("true");
+    }
+
+    /**
+     * Gets the current location within the file. This allows the location to be created only as
+     * needed, and multiple objects on the same line can share the same Location instance.
+     * 
+     * @since 3.0
+     */
+
+    protected Location getCurrentLocation()
+    {
+        if (_currentLocation == null)
+            _currentLocation = new LocationImpl(_resourceLocation, _line);
+
+        return _currentLocation;
+    }
+
+    public void setFactory(TemplateTokenFactory factory)
+    {
+        _factory = factory;
     }
 
-}
+}
\ No newline at end of file

Modified: jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TextToken.java
URL: http://svn.apache.org/viewcvs/jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TextToken.java?rev=385164&r1=385163&r2=385164&view=diff
==============================================================================
--- jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TextToken.java (original)
+++ jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TextToken.java Sat Mar 11 12:54:27 2006
@@ -1,4 +1,4 @@
-// Copyright 2004, 2005, 2006 The Apache Software Foundation
+// Copyright 2004, 2005 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -22,8 +22,8 @@
 import org.apache.tapestry.IRequestCycle;
 
 /**
- * Represents static text in the template that may be passed through to the
- * client unchanged (except, perhaps, for the removal of some whitespace).
+ * Represents static text in the template that may be passed through to the client unchanged
+ * (except, perhaps, for the removal of some whitespace).
  * 
  * @see TokenType#TEXT
  * @author Howard Lewis Ship
@@ -32,7 +32,6 @@
 
 public class TextToken extends TemplateToken implements IRender
 {
-
     private char[] _templateData;
 
     private int _offset;
@@ -43,9 +42,11 @@
     {
         super(TokenType.TEXT, location);
 
-        if (startIndex < 0 || endIndex < 0 || startIndex > templateData.length || endIndex > templateData.length)
-            throw new ApplicationRuntimeException(ParseMessages.rangeError(this, templateData.length), this,
-                    getLocation(), null);
+        if (startIndex < 0 || endIndex < 0 || startIndex > templateData.length
+                || endIndex > templateData.length)
+            throw new ApplicationRuntimeException(ParseMessages.rangeError(
+                    this,
+                    templateData.length), this, getLocation(), null);
 
         _templateData = templateData;
 
@@ -55,14 +56,14 @@
 
     public void render(IMarkupWriter writer, IRequestCycle cycle)
     {
-        if (_length == 0) return;
+        if (_length == 0)
+            return;
 
         // At one time, we would check to see if the cycle was rewinding and
         // only invoke printRaw() if it was. However, that slows down
         // normal rendering (microscopically) and, with the new
         // NullResponseWriter class, the "cost" of invoking cycle.isRewinding()
-        // is approximately the same as the "cost" of invoking
-        // writer.printRaw().
+        // is approximately the same as the "cost" of invoking writer.printRaw().
 
         writer.printRaw(_templateData, _offset, _length);
     }
@@ -87,4 +88,4 @@
     {
         return _offset;
     }
-}
+}
\ No newline at end of file

Modified: jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TokenType.java
URL: http://svn.apache.org/viewcvs/jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TokenType.java?rev=385164&r1=385163&r2=385164&view=diff
==============================================================================
--- jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TokenType.java (original)
+++ jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TokenType.java Sat Mar 11 12:54:27 2006
@@ -1,4 +1,4 @@
-// Copyright 2004, 2005, 2006 The Apache Software Foundation
+// Copyright 2004, 2005 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -21,9 +21,8 @@
  * @author Howard Lewis Ship
  */
 
-public final class TokenType
+public class TokenType
 {
-
     /**
      * Raw HTML text.
      * 
@@ -72,4 +71,4 @@
     {
         return _name;
     }
-}
+}
\ No newline at end of file

Modified: jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/record/PersistentPropertyDataEncoderImpl.java
URL: http://svn.apache.org/viewcvs/jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/record/PersistentPropertyDataEncoderImpl.java?rev=385164&r1=385163&r2=385164&view=diff
==============================================================================
--- jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/record/PersistentPropertyDataEncoderImpl.java (original)
+++ jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/record/PersistentPropertyDataEncoderImpl.java Sat Mar 11 12:54:27 2006
@@ -50,6 +50,8 @@
  */
 public class PersistentPropertyDataEncoderImpl implements PersistentPropertyDataEncoder
 {
+    private ClassResolver _classResolver;
+
     /**
      * Prefix on the MIME encoding that indicates that the encoded data is not encoded.
      */
@@ -62,8 +64,6 @@
 
     public static final String GZIP_BYTESTREAM_PREFIX = "Z";
 
-    private ClassResolver _classResolver;
-    
     public String encodePageChanges(List changes)
     {
         Defense.notNull(changes, "changes");
@@ -193,4 +193,4 @@
     {
         _classResolver = resolver;
     }
-}
+}
\ No newline at end of file

Modified: jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/record/RecordMessages.java
URL: http://svn.apache.org/viewcvs/jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/record/RecordMessages.java?rev=385164&r1=385163&r2=385164&view=diff
==============================================================================
--- jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/record/RecordMessages.java (original)
+++ jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/record/RecordMessages.java Sat Mar 11 12:54:27 2006
@@ -1,4 +1,4 @@
-// Copyright 2005, 2006 The Apache Software Foundation
+// Copyright 2005 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -14,7 +14,6 @@
 
 package org.apache.tapestry.record;
 
-import org.apache.hivemind.Messages;
 import org.apache.hivemind.impl.MessageFormatter;
 import org.apache.tapestry.IComponent;
 
@@ -22,44 +21,38 @@
  * @author Howard M. Lewis Ship
  * @since 4.0
  */
-final class RecordMessages
+class RecordMessages
 {
-
-    private final static Messages MESSAGES = new MessageFormatter(RecordMessages.class);
-
-    /** @since 4.1 */
-    private RecordMessages()
-    {
-    }
+    private final static MessageFormatter _formatter = new MessageFormatter(RecordMessages.class);
 
     static String unknownPersistenceStrategy(String name)
     {
-        return MESSAGES.format("unknown-persistence-strategy", name);
+        return _formatter.format("unknown-persistence-strategy", name);
     }
 
     static String missingPropertySpecification(String propertyName, IComponent component)
     {
-        return MESSAGES.format("missing-property-specification", propertyName, component.getExtendedId(), component
-                .getSpecification().getSpecificationLocation());
+        return _formatter.format("missing-property-specification", propertyName, component
+                .getExtendedId(), component.getSpecification().getSpecificationLocation());
     }
 
     static String recorderLocked(String propertyName, IComponent component)
     {
-        return MESSAGES.format("recorder-locked", propertyName, component.getExtendedId());
+        return _formatter.format("recorder-locked", propertyName, component.getExtendedId());
     }
 
     static String decodeFailure(Throwable cause)
     {
-        return MESSAGES.format("decode-failure", cause);
+        return _formatter.format("decode-failure", cause);
     }
 
     static String encodeFailure(Throwable cause)
     {
-        return MESSAGES.format("encode-failure", cause);
+        return _formatter.format("encode-failure", cause);
     }
 
     static String unknownPrefix(String prefix)
     {
-        return MESSAGES.format("unknown-prefix", prefix);
+        return _formatter.format("unknown-prefix", prefix);
     }
-}
+}
\ No newline at end of file



---------------------------------------------------------------------
To unsubscribe, e-mail: tapestry-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: tapestry-dev-help@jakarta.apache.org