Package org.apache.tapestry.parse

Source Code of org.apache.tapestry.parse.SpecificationParser

// 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.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package org.apache.tapestry.parse;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hivemind.ClassResolver;
import org.apache.hivemind.ErrorHandler;
import org.apache.hivemind.HiveMind;
import org.apache.hivemind.Location;
import org.apache.hivemind.Resource;
import org.apache.hivemind.impl.DefaultErrorHandler;
import org.apache.hivemind.impl.LocationImpl;
import org.apache.hivemind.parse.AbstractParser;
import org.apache.tapestry.INamespace;
import org.apache.tapestry.Tapestry;
import org.apache.tapestry.bean.BindingBeanInitializer;
import org.apache.tapestry.bean.LightweightBeanInitializer;
import org.apache.tapestry.binding.BindingConstants;
import org.apache.tapestry.binding.BindingSource;
import org.apache.tapestry.coerce.ValueConverter;
import org.apache.tapestry.spec.BeanLifecycle;
import org.apache.tapestry.spec.BindingType;
import org.apache.tapestry.spec.IApplicationSpecification;
import org.apache.tapestry.spec.IAssetSpecification;
import org.apache.tapestry.spec.IBeanSpecification;
import org.apache.tapestry.spec.IBindingSpecification;
import org.apache.tapestry.spec.IComponentSpecification;
import org.apache.tapestry.spec.IContainedComponent;
import org.apache.tapestry.spec.IExtensionSpecification;
import org.apache.tapestry.spec.ILibrarySpecification;
import org.apache.tapestry.spec.IParameterSpecification;
import org.apache.tapestry.spec.IPropertySpecification;
import org.apache.tapestry.spec.InjectSpecification;
import org.apache.tapestry.spec.SpecFactory;
import org.apache.tapestry.util.IPropertyHolder;
import org.apache.tapestry.util.RegexpMatcher;
import org.apache.tapestry.util.xml.DocumentParseException;
import org.apache.tapestry.util.xml.InvalidStringException;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

/**
* Parses the different types of Tapestry specifications.
* <p>
* Not threadsafe; it is the callers responsibility to ensure thread safety.
*
* @author Howard Lewis Ship
*/
public class SpecificationParser extends AbstractParser implements ISpecificationParser
{
    /**
     * Perl5 pattern for asset names. Letter, followed by letter, number or underscore. Also allows
     * the special "$template" value.
     *
     * @since 2.2
     */

    public static final String ASSET_NAME_PATTERN = "(\\$template)|("
            + Tapestry.SIMPLE_PROPERTY_NAME_PATTERN + ")";

    /**
     * Perl5 pattern for helper bean names. Letter, followed by letter, number or underscore.
     *
     * @since 2.2
     */

    public static final String BEAN_NAME_PATTERN = Tapestry.SIMPLE_PROPERTY_NAME_PATTERN;

    public static final String IDENTIFIER_PATTERN = "_?[a-zA-Z]\\w*";
   
    public static final String EXTENDED_IDENTIFIER_PATTERN = "_?[a-zA-Z](\\w|-)*";
   
    /**
     * Perl5 pattern for component type (which was known as an "alias" in earlier versions of
     * Tapestry). This is either a simple property name, or a series of property names seperated by
     * slashes (the latter being new in Tapestry 4.0). This defines a literal that can appear in a
     * library or application specification.
     *
     * @since 2.2
     */

    public static final String COMPONENT_ALIAS_PATTERN = "^(" + IDENTIFIER_PATTERN + "/)*"
            + IDENTIFIER_PATTERN + "$";

    /**
     * Perl5 pattern for component ids. Letter, followed by letter, number or underscore.
     *
     * @since 2.2
     */

    public static final String COMPONENT_ID_PATTERN = Tapestry.SIMPLE_PROPERTY_NAME_PATTERN;

    /**
     * Perl5 pattern for component types (i.e., the type attribute of the &lt;component&gt;
     * element). Component types are an optional namespace prefix followed by a component type
     * (within the library defined by the namespace). Starting in 4.0, the type portion is actually
     * a series of identifiers seperated by slashes.
     *
     * @since 2.2
     */

    public static final String COMPONENT_TYPE_PATTERN = "^(" + IDENTIFIER_PATTERN + ":)?" + "("
            + IDENTIFIER_PATTERN + "/)*" + IDENTIFIER_PATTERN + "$";

    /**
     * Extended version of {@link Tapestry.SIMPLE_PROPERTY_NAME_PATTERN}, but allows a series of
     * individual property names, seperated by periods. In addition, each name within the dotted
     * sequence is allowed to contain dashes.
     *
     * @since 2.2
     */

    public static final String EXTENDED_PROPERTY_NAME_PATTERN = "^" + EXTENDED_IDENTIFIER_PATTERN
            + "(\\." + EXTENDED_IDENTIFIER_PATTERN + ")*$";

    /**
     * Per5 pattern for extension names. Letter followed by letter, number, dash, period or
     * underscore.
     *
     * @since 2.2
     */

    public static final String EXTENSION_NAME_PATTERN = EXTENDED_PROPERTY_NAME_PATTERN;

    /**
     * Perl5 pattern for library ids. Letter followed by letter, number or underscore.
     *
     * @since 2.2
     */

    public static final String LIBRARY_ID_PATTERN = Tapestry.SIMPLE_PROPERTY_NAME_PATTERN;
   
    /**
     * Perl5 pattern for page names. Page names appear in library and application specifications, in
     * the &lt;page&gt; element. Starting with 4.0, the page name may look more like a path name,
     * consisting of a number of ids seperated by slashes. This is used to determine the folder
     * which contains the page specification or the page's template.
     *
     * @since 2.2
     */

    public static final String PAGE_NAME_PATTERN = "^" + IDENTIFIER_PATTERN + "(/"
            + IDENTIFIER_PATTERN + ")*$";

    /**
     * Perl5 pattern that parameter names must conform to. Letter, followed by letter, number or
     * underscore.
     *
     * @since 2.2
     */

    public static final String PARAMETER_NAME_PATTERN = Tapestry.SIMPLE_PROPERTY_NAME_PATTERN;

    /**
     * Perl5 pattern that property names (that can be connected to parameters) must conform to.
     * Letter, followed by letter, number or underscore.
     *
     * @since 2.2
     */

    public static final String PROPERTY_NAME_PATTERN = Tapestry.SIMPLE_PROPERTY_NAME_PATTERN;

    /**
     * Perl5 pattern for service names. Letter followed by letter, number, dash, underscore or
     * period.
     *
     * @since 2.2
     * @deprecated As of release 4.0, the &lt;service&gt; element (in 3.0 DTDs) is no longer
     *             supported.
     */

    public static final String SERVICE_NAME_PATTERN = EXTENDED_PROPERTY_NAME_PATTERN;
   
    /** @since 3.0 */

    public static final String TAPESTRY_DTD_3_0_PUBLIC_ID = "-//Apache Software Foundation//Tapestry Specification 3.0//EN";

    /** @since 4.0 */

    public static final String TAPESTRY_DTD_4_0_PUBLIC_ID = "-//Apache Software Foundation//Tapestry Specification 4.0//EN";

    /** @since 4.1 */
   
    public static final String TAPESTRY_DTD_4_1_PUBLIC_ID = "-//Apache Software Foundation//Tapestry Specification 4.1//EN";
   
    private static final int STATE_ALLOW_DESCRIPTION = 2000;

    private static final int STATE_ALLOW_PROPERTY = 2001;

    private static final int STATE_APPLICATION_SPECIFICATION_INITIAL = 1002;

    private static final int STATE_BEAN = 4;

    /** Very different between 3.0 and 4.0 DTD. */

    private static final int STATE_BINDING_3_0 = 7;

    /** @since 4.0 */

    private static final int STATE_BINDING = 100;

    private static final int STATE_COMPONENT = 6;

    private static final int STATE_COMPONENT_SPECIFICATION = 1;

    private static final int STATE_COMPONENT_SPECIFICATION_INITIAL = 1000;

    private static final int STATE_CONFIGURE = 14;

    private static final int STATE_DESCRIPTION = 2;

    private static final int STATE_EXTENSION = 13;

    private static final int STATE_LIBRARY_SPECIFICATION = 12;

    private static final int STATE_LIBRARY_SPECIFICATION_INITIAL = 1003;

    private static final int STATE_LISTENER_BINDING = 8;

    private static final int STATE_NO_CONTENT = 3000;

    private static final int STATE_PAGE_SPECIFICATION = 11;

    private static final int STATE_PAGE_SPECIFICATION_INITIAL = 1001;

    private static final int STATE_META = 3;

    private static final int STATE_PROPERTY = 10;

    private static final int STATE_SET = 5;

    /** 3.0 DTD only. */
    private static final int STATE_STATIC_BINDING = 9;
   
    /**
     * We can share a single map for all the XML attribute to object conversions, since the keys are
     * unique.
     */

    private final Map _conversionMap = new HashMap();

    /** @since 4.0 */
    private final Log _log;

    /** @since 4.0 */
    private final ErrorHandler _errorHandler;

    /**
     * Set to true if parsing the 4.0 DTD.
     *
     * @since 4.0
     */

    private boolean _dtd40;

    /**
     * The attributes of the current element, as a map (string keyed on string).
     */

    private Map _attributes;

    /**
     * The name of the current element.
     */

    private String _elementName;

    /** @since 1.0.9 */

    private final SpecFactory _factory;

    private RegexpMatcher _matcher = new RegexpMatcher();

    private SAXParser _parser;

    private SAXParserFactory _parserFactory = SAXParserFactory.newInstance();

    /**
     * @since 3.0
     */

    private final ClassResolver _resolver;

    /** @since 4.0 */

    private BindingSource _bindingSource;

    /**
     * The root object parsed: a component or page specification, a library specification, or an
     * application specification.
     */
    private Object _rootObject;

    /** @since 4.0 */

    private ValueConverter _valueConverter;

    // Identify all the different acceptible values.
    // We continue to sneak by with a single map because
    // there aren't conflicts; when we have 'foo' meaning
    // different things in different places in the DTD, we'll
    // need multiple maps.

    {

        _conversionMap.put("true", Boolean.TRUE);
        _conversionMap.put("t", Boolean.TRUE);
        _conversionMap.put("1", Boolean.TRUE);
        _conversionMap.put("y", Boolean.TRUE);
        _conversionMap.put("yes", Boolean.TRUE);
        _conversionMap.put("on", Boolean.TRUE);
        _conversionMap.put("aye", Boolean.TRUE);

        _conversionMap.put("false", Boolean.FALSE);
        _conversionMap.put("f", Boolean.FALSE);
        _conversionMap.put("0", Boolean.FALSE);
        _conversionMap.put("off", Boolean.FALSE);
        _conversionMap.put("no", Boolean.FALSE);
        _conversionMap.put("n", Boolean.FALSE);
        _conversionMap.put("nay", Boolean.FALSE);

        _conversionMap.put("none", BeanLifecycle.NONE);
        _conversionMap.put("request", BeanLifecycle.REQUEST);
        _conversionMap.put("page", BeanLifecycle.PAGE);
        _conversionMap.put("render", BeanLifecycle.RENDER);

        _parserFactory.setNamespaceAware(false);
        _parserFactory.setValidating(true);
    }

    /**
     * This constructor is a convienience used by some tests.
     */
    public SpecificationParser(ClassResolver resolver)
    {
        this(resolver, new SpecFactory());
    }

    /**
     * Create a new instance with resolver and a provided SpecFactory (used by Spindle).
     *
     * @deprecated to be removed in release 4.1
     */
    public SpecificationParser(ClassResolver resolver, SpecFactory factory)
    {
        this(new DefaultErrorHandler(), LogFactory.getLog(SpecificationParser.class), resolver,
                factory);
    }

    /**
     * The full constructor, used within Tapestry.
     */
    public SpecificationParser(ErrorHandler errorHandler, Log log, ClassResolver resolver,
            SpecFactory factory)
    {
        _errorHandler = errorHandler;
        _log = log;
        _resolver = resolver;
        _factory = factory;
    }

    protected void begin(String elementName, Map attributes)
    {
        _elementName = elementName;
        _attributes = attributes;

        switch (getState())
        {
            case STATE_COMPONENT_SPECIFICATION_INITIAL:

                beginComponentSpecificationInitial();
                break;

            case STATE_PAGE_SPECIFICATION_INITIAL:

                beginPageSpecificationInitial();
                break;

            case STATE_APPLICATION_SPECIFICATION_INITIAL:

                beginApplicationSpecificationInitial();
                break;

            case STATE_LIBRARY_SPECIFICATION_INITIAL:

                beginLibrarySpecificationInitial();
                break;

            case STATE_COMPONENT_SPECIFICATION:

                beginComponentSpecification();
                break;

            case STATE_PAGE_SPECIFICATION:

                beginPageSpecification();
                break;

            case STATE_ALLOW_DESCRIPTION:

                beginAllowDescription();
                break;

            case STATE_ALLOW_PROPERTY:

                allowMetaData();
                break;

            case STATE_BEAN:

                beginBean();
                break;

            case STATE_COMPONENT:

                beginComponent();
                break;

            case STATE_LIBRARY_SPECIFICATION:

                beginLibrarySpecification();
                break;

            case STATE_EXTENSION:

                beginExtension();
                break;

            default:

                unexpectedElement(_elementName);
        }
    }

    /**
     * Special state for a number of specification types that can support the &lt;description&gt;
     * element.
     */

    private void beginAllowDescription()
    {
        if (_elementName.equals("description"))
        {
            enterDescription();
            return;
        }

        unexpectedElement(_elementName);
    }

    /**
     * Special state for a number of elements that can support the nested &lt;meta&gt; meta data
     * element (&lt;property&gt; in 3.0 DTD).
     */

    private void allowMetaData()
    {
        if (_dtd40)
        {
            if (_elementName.equals("meta"))
            {
                enterMeta();
                return;
            }
        }
        else if (_elementName.equals("property"))
        {
            enterProperty30();
            return;
        }

        unexpectedElement(_elementName);
    }

    private void beginApplicationSpecificationInitial()
    {
        expectElement("application");

        String name = getAttribute("name");
        String engineClassName = getAttribute("engine-class");

        IApplicationSpecification as = _factory.createApplicationSpecification();

        as.setName(name);

        if (HiveMind.isNonBlank(engineClassName))
            as.setEngineClassName(engineClassName);

        _rootObject = as;

        push(_elementName, as, STATE_LIBRARY_SPECIFICATION);
    }

    private void beginBean()
    {
        if (_elementName.equals("set"))
        {
            enterSet();
            return;
        }

        if (_elementName.equals("set-property"))
        {
            enterSetProperty30();
            return;
        }

        if (_elementName.equals("set-message-property"))
        {
            enterSetMessage30();
            return;
        }

        if (_elementName.equals("description"))
        {
            enterDescription();
            return;
        }

        allowMetaData();
    }

    private void beginComponent()
    {
        // <binding> has changed between 3.0 and 4.0

        if (_elementName.equals("binding"))
        {
            enterBinding();
            return;
        }

        if (_elementName.equals("static-binding"))
        {
            enterStaticBinding30();
            return;
        }

        if (_elementName.equals("message-binding"))
        {
            enterMessageBinding30();
            return;
        }

        if (_elementName.equals("inherited-binding"))
        {
            enterInheritedBinding30();
            return;
        }

        if (_elementName.equals("listener-binding"))
        {
            enterListenerBinding();
            return;
        }

        allowMetaData();
    }

    private void beginComponentSpecification()
    {
        if (_elementName.equals("reserved-parameter"))
        {
            enterReservedParameter();
            return;
        }

        if (_elementName.equals("parameter"))
        {
            enterParameter();
            return;
        }

        // The remainder are common to both <component-specification> and
        // <page-specification>

        beginPageSpecification();
    }

    private void beginComponentSpecificationInitial()
    {
        expectElement("component-specification");

        IComponentSpecification cs = _factory.createComponentSpecification();

        cs.setAllowBody(getBooleanAttribute("allow-body", true));
        cs.setAllowInformalParameters(getBooleanAttribute("allow-informal-parameters", true));
        cs.setDeprecated(getBooleanAttribute("deprecated", false));

        String className = getAttribute("class");

        if (className != null)
            cs.setComponentClassName(className);

        cs.setSpecificationLocation(getResource());

        _rootObject = cs;

        push(_elementName, cs, STATE_COMPONENT_SPECIFICATION);
    }

    private void beginExtension()
    {
        if (_elementName.equals("configure"))
        {
            enterConfigure();
            return;
        }

        allowMetaData();
    }

    private void beginLibrarySpecification()
    {
        if (_elementName.equals("description"))
        {
            enterDescription();
            return;
        }

        if (_elementName.equals("page"))
        {
            enterPage();
            return;
        }

        if (_elementName.equals("component-type"))
        {
            enterComponentType();
            return;
        }

        // Holdover from the 3.0 DTD, now ignored.

        if (_elementName.equals("service"))
        {
            enterService30();
            return;
        }

        if (_elementName.equals("library"))
        {
            enterLibrary();
            return;
        }

        if (_elementName.equals("extension"))
        {
            enterExtension();
            return;
        }

        allowMetaData();
    }

    private void beginLibrarySpecificationInitial()
    {
        expectElement("library-specification");

        ILibrarySpecification ls = _factory.createLibrarySpecification();

        _rootObject = ls;

        push(_elementName, ls, STATE_LIBRARY_SPECIFICATION);
    }

    private void beginPageSpecification()
    {
        if (_elementName.equals("component"))
        {
            enterComponent();
            return;
        }

        if (_elementName.equals("bean"))
        {
            enterBean();
            return;
        }

        // <property-specification> in 3.0, <property> in 4.0
        // Have to be careful, because <meta> in 4.0 was <property> in 3.0

        if (_elementName.equals("property-specification")
                || (_dtd40 && _elementName.equals("property")))
        {
            enterProperty();
            return;
        }

        if (_elementName.equals("inject"))
        {
            enterInject();
            return;
        }

        // <asset> is new in 4.0

        if (_elementName.equals("asset"))
        {
            enterAsset();
            return;
        }

        // <context-asset>, <external-asset>, and <private-asset>
        // are all throwbacks to the 3.0 DTD and don't exist
        // in the 4.0 DTD.

        if (_elementName.equals("context-asset"))
        {
            enterContextAsset30();
            return;
        }

        if (_elementName.equals("private-asset"))
        {
            enterPrivateAsset30();
            return;
        }

        if (_elementName.equals("external-asset"))
        {
            enterExternalAsset30();
            return;

        }

        if (_elementName.equals("description"))
        {
            enterDescription();
            return;
        }

        allowMetaData();
    }

    private void beginPageSpecificationInitial()
    {
        expectElement("page-specification");

        IComponentSpecification cs = _factory.createComponentSpecification();

        String className = getAttribute("class");

        if (className != null)
            cs.setComponentClassName(className);

        cs.setSpecificationLocation(getResource());
        cs.setPageSpecification(true);

        _rootObject = cs;

        push(_elementName, cs, STATE_PAGE_SPECIFICATION);
    }

    /**
     * Close a stream (if not null), ignoring any errors.
     */
    private void close(InputStream stream)
    {
        try
        {
            if (stream != null)
                stream.close();
        }
        catch (IOException ex)
        {
            // ignore
        }
    }

    private void copyBindings(String sourceComponentId, IComponentSpecification cs,
            IContainedComponent target)
    {
        IContainedComponent source = cs.getComponent(sourceComponentId);
        if (source == null)
            throw new DocumentParseException(ParseMessages.unableToCopy(sourceComponentId),
                    getLocation());

        Iterator i = source.getBindingNames().iterator();
        while (i.hasNext())
        {
            String bindingName = (String) i.next();
            IBindingSpecification binding = source.getBinding(bindingName);
            target.setBinding(bindingName, binding);
        }

        target.setType(source.getType());
    }

    protected void end(String elementName)
    {
        _elementName = elementName;

        switch (getState())
        {
            case STATE_DESCRIPTION:

                endDescription();
                break;

            case STATE_META:

                endProperty();
                break;

            case STATE_SET:

                endSetProperty();
                break;

            case STATE_BINDING_3_0:

                endBinding30();
                break;

            case STATE_BINDING:

                endBinding();
                break;

            case STATE_STATIC_BINDING:

                endStaticBinding();
                break;

            case STATE_PROPERTY:

                endPropertySpecification();
                break;

            case STATE_LIBRARY_SPECIFICATION:

                endLibrarySpecification();
                break;

            case STATE_CONFIGURE:

                endConfigure();
                break;

            default:
                break;
        }

        // Pop the top element of the stack and continue processing from there.

        pop();
    }

    private void endBinding30()
    {
        BindingSetter bs = (BindingSetter) peekObject();

        String expression = getExtendedValue(bs.getValue(), "expression", true);

        IBindingSpecification spec = _factory.createBindingSpecification();

        spec.setType(BindingType.PREFIXED);
        spec.setValue(BindingConstants.OGNL_PREFIX + ":" + expression);

        bs.apply(spec);
    }

    private void endConfigure()
    {
        ExtensionConfigurationSetter setter = (ExtensionConfigurationSetter) peekObject();

        String finalValue = getExtendedValue(setter.getValue(), "value", true);

        setter.apply(finalValue);
    }

    private void endDescription()
    {
        DescriptionSetter setter = (DescriptionSetter) peekObject();

        String description = peekContent();

        setter.apply(description);
    }

    private void endLibrarySpecification()
    {
        ILibrarySpecification spec = (ILibrarySpecification) peekObject();

        spec.setSpecificationLocation(getResource());

        spec.instantiateImmediateExtensions();
    }

    private void endProperty()
    {
        PropertyValueSetter pvs = (PropertyValueSetter) peekObject();

        String finalValue = getExtendedValue(pvs.getPropertyValue(), "value", true);

        pvs.applyValue(finalValue);
    }

    private void endPropertySpecification()
    {
        IPropertySpecification ps = (IPropertySpecification) peekObject();

        String initialValue = getExtendedValue(ps.getInitialValue(), "initial-value", false);

        // In the 3.0 DTD, the initial value was always an OGNL expression.
        // In the 4.0 DTD, it is a binding reference, qualified with a prefix.

        if (initialValue != null && !_dtd40)
            initialValue = BindingConstants.OGNL_PREFIX + ":" + initialValue;

        ps.setInitialValue(initialValue);
    }

    private void endSetProperty()
    {
        BeanSetPropertySetter bs = (BeanSetPropertySetter) peekObject();

        String finalValue = getExtendedValue(bs.getBindingReference(), "expression", true);

        bs.applyBindingReference(finalValue);
    }

    private void endStaticBinding()
    {
        BindingSetter bs = (BindingSetter) peekObject();

        String literalValue = getExtendedValue(bs.getValue(), "value", true);

        IBindingSpecification spec = _factory.createBindingSpecification();

        spec.setType(BindingType.PREFIXED);
        spec.setValue(BindingConstants.LITERAL_PREFIX + ":" + literalValue);

        bs.apply(spec);
    }

    private void enterAsset(String pathAttributeName, String prefix)
    {
        String name = getValidatedAttribute("name", ASSET_NAME_PATTERN, "invalid-asset-name");
        String path = getAttribute(pathAttributeName);
        String propertyName = getValidatedAttribute(
                "property",
                PROPERTY_NAME_PATTERN,
                "invalid-property-name");

        IAssetSpecification ia = _factory.createAssetSpecification();

        ia.setPath(prefix == null ? path : prefix + path);
        ia.setPropertyName(propertyName);

        IComponentSpecification cs = (IComponentSpecification) peekObject();

        cs.addAsset(name, ia);

        push(_elementName, ia, STATE_ALLOW_PROPERTY);
    }

    private void enterBean()
    {
        String name = getValidatedAttribute("name", BEAN_NAME_PATTERN, "invalid-bean-name");

        String classAttribute = getAttribute("class");

        // Look for the lightweight initialization

        int commax = classAttribute.indexOf(',');

        String className = commax < 0 ? classAttribute : classAttribute.substring(0, commax);

        BeanLifecycle lifecycle = (BeanLifecycle) getConvertedAttribute(
                "lifecycle",
                BeanLifecycle.REQUEST);
        String propertyName = getValidatedAttribute(
                "property",
                PROPERTY_NAME_PATTERN,
                "invalid-property-name");

        IBeanSpecification bs = _factory.createBeanSpecification();

        bs.setClassName(className);
        bs.setLifecycle(lifecycle);
        bs.setPropertyName(propertyName);

        if (commax > 0)
        {
            String initializer = classAttribute.substring(commax + 1);
            bs.addInitializer(new LightweightBeanInitializer(initializer));
        }

        IComponentSpecification cs = (IComponentSpecification) peekObject();

        cs.addBeanSpecification(name, bs);

        push(_elementName, bs, STATE_BEAN);
    }

    private void enterBinding()
    {
        if (!_dtd40)
        {
            enterBinding30();
            return;
        }

        // 4.0 stuff

        String name = getValidatedAttribute(
                "name",
                PARAMETER_NAME_PATTERN,
                "invalid-parameter-name");
        String value = getAttribute("value");

        IContainedComponent cc = (IContainedComponent) peekObject();

        BindingSetter bs = new BindingSetter(cc, name, value);

        push(_elementName, bs, STATE_BINDING, false);
    }

    private void endBinding()
    {
        BindingSetter bs = (BindingSetter) peekObject();

        String value = getExtendedValue(bs.getValue(), "value", true);

        IBindingSpecification spec = _factory.createBindingSpecification();

        spec.setType(BindingType.PREFIXED);
        spec.setValue(value);

        bs.apply(spec);
    }

    /**
     * Handles a binding in a 3.0 DTD.
     */

    private void enterBinding30()
    {
        String name = getAttribute("name");
        String expression = getAttribute("expression");

        IContainedComponent cc = (IContainedComponent) peekObject();

        BindingSetter bs = new BindingSetter(cc, name, expression);

        push(_elementName, bs, STATE_BINDING_3_0, false);
    }

    private void enterComponent()
    {
        String id = getValidatedAttribute("id", COMPONENT_ID_PATTERN, "invalid-component-id");

        String type = getValidatedAttribute(
                "type",
                COMPONENT_TYPE_PATTERN,
                "invalid-component-type");
        String copyOf = getAttribute("copy-of");
        boolean inherit = getBooleanAttribute("inherit-informal-parameters", false);
        String propertyName = getValidatedAttribute(
                "property",
                PROPERTY_NAME_PATTERN,
                "invalid-property-name");

        // Check that either copy-of or type, but not both

        boolean hasCopyOf = HiveMind.isNonBlank(copyOf);

        if (hasCopyOf)
        {
            if (HiveMind.isNonBlank(type))
                throw new DocumentParseException(ParseMessages.bothTypeAndCopyOf(id), getLocation());
        }
        else
        {
            if (HiveMind.isBlank(type))
                throw new DocumentParseException(ParseMessages.missingTypeOrCopyOf(id),
                        getLocation());
        }

        IContainedComponent cc = _factory.createContainedComponent();
        cc.setType(type);
        cc.setCopyOf(copyOf);
        cc.setInheritInformalParameters(inherit);
        cc.setPropertyName(propertyName);

        IComponentSpecification cs = (IComponentSpecification) peekObject();

        cs.addComponent(id, cc);

        if (hasCopyOf)
            copyBindings(copyOf, cs, cc);

        push(_elementName, cc, STATE_COMPONENT);
    }

    private void enterComponentType()
    {
        String type = getValidatedAttribute(
                "type",
                COMPONENT_ALIAS_PATTERN,
                "invalid-component-type");
        String path = getAttribute("specification-path");

        ILibrarySpecification ls = (ILibrarySpecification) peekObject();

        ls.setComponentSpecificationPath(type, path);

        push(_elementName, null, STATE_NO_CONTENT);
    }

    private void enterConfigure()
    {
        String attributeName = _dtd40 ? "property" : "property-name";

        String propertyName = getValidatedAttribute(
                attributeName,
                PROPERTY_NAME_PATTERN,
                "invalid-property-name");

        String value = getAttribute("value");

        IExtensionSpecification es = (IExtensionSpecification) peekObject();

        ExtensionConfigurationSetter setter = new ExtensionConfigurationSetter(es, propertyName,
                value);

        push(_elementName, setter, STATE_CONFIGURE, false);
    }

    private void enterContextAsset30()
    {
        enterAsset("path", "context:");
    }

    /**
     * New in the 4.0 DTD. When using the 4.0 DTD, you must explicitly specify prefix if the asset
     * is not stored in the same domain as the specification file.
     *
     * @since 4.0
     */

    private void enterAsset()
    {
        enterAsset("path", null);
    }

    private void enterDescription()
    {
        push(_elementName, new DescriptionSetter(peekObject()), STATE_DESCRIPTION, false);
    }

    private void enterExtension()
    {
        String name = getValidatedAttribute(
                "name",
                EXTENSION_NAME_PATTERN,
                "invalid-extension-name");

        boolean immediate = getBooleanAttribute("immediate", false);
        String className = getAttribute("class");

        IExtensionSpecification es = _factory.createExtensionSpecification(
                _resolver,
                _valueConverter);

        es.setClassName(className);
        es.setImmediate(immediate);

        ILibrarySpecification ls = (ILibrarySpecification) peekObject();

        ls.addExtensionSpecification(name, es);

        push(_elementName, es, STATE_EXTENSION);
    }

    private void enterExternalAsset30()
    {
        // External URLs get no prefix, but will have a scheme (i.e., "http:") that
        // fulfils much the same purpose.

        enterAsset("URL", null);
    }

    /** A throwback to the 3.0 DTD. */

    private void enterInheritedBinding30()
    {
        String name = getAttribute("name");
        String parameterName = getAttribute("parameter-name");

        IBindingSpecification bs = _factory.createBindingSpecification();
        bs.setType(BindingType.INHERITED);
        bs.setValue(parameterName);

        IContainedComponent cc = (IContainedComponent) peekObject();

        cc.setBinding(name, bs);

        push(_elementName, null, STATE_NO_CONTENT);
    }

    private void enterLibrary()
    {
        String libraryId = getValidatedAttribute("id", LIBRARY_ID_PATTERN, "invalid-library-id");
        String path = getAttribute("specification-path");

        if (libraryId.equals(INamespace.FRAMEWORK_NAMESPACE)
                || libraryId.equals(INamespace.APPLICATION_NAMESPACE))
            throw new DocumentParseException(ParseMessages
                    .frameworkLibraryIdIsReserved(INamespace.FRAMEWORK_NAMESPACE), getLocation());

        ILibrarySpecification ls = (ILibrarySpecification) peekObject();

        ls.setLibrarySpecificationPath(libraryId, path);

        push(_elementName, null, STATE_NO_CONTENT);
    }

    private void enterListenerBinding()
    {
        _log.warn(ParseMessages.listenerBindingUnsupported(getLocation()));

        push(_elementName, null, STATE_LISTENER_BINDING, false);
    }

    private void enterMessageBinding30()
    {
        String name = getAttribute("name");
        String key = getAttribute("key");

        IBindingSpecification bs = _factory.createBindingSpecification();
        bs.setType(BindingType.PREFIXED);
        bs.setValue(BindingConstants.MESSAGE_PREFIX + ":" + key);
        bs.setLocation(getLocation());

        IContainedComponent cc = (IContainedComponent) peekObject();

        cc.setBinding(name, bs);

        push(_elementName, null, STATE_NO_CONTENT);
    }

    private void enterPage()
    {
        String name = getValidatedAttribute("name", PAGE_NAME_PATTERN, "invalid-page-name");
        String path = getAttribute("specification-path");

        ILibrarySpecification ls = (ILibrarySpecification) peekObject();

        ls.setPageSpecificationPath(name, path);

        push(_elementName, null, STATE_NO_CONTENT);
    }

    private void enterParameter()
    {
        IParameterSpecification ps = _factory.createParameterSpecification();

        String name = getValidatedAttribute(
                "name",
                PARAMETER_NAME_PATTERN,
                "invalid-parameter-name");

        String attributeName = _dtd40 ? "property" : "property-name";

        String propertyName = getValidatedAttribute(
                attributeName,
                PROPERTY_NAME_PATTERN,
                "invalid-property-name");

        if (propertyName == null)
            propertyName = name;

        ps.setParameterName(name);
        ps.setPropertyName(propertyName);

        ps.setRequired(getBooleanAttribute("required", false));

        // In the 3.0 DTD, default-value was always an OGNL expression.
        // Starting with 4.0, it's like a binding (prefixed). For a 3.0
        // DTD, we supply the "ognl:" prefix.

        String defaultValue = getAttribute("default-value");

        if (defaultValue != null && !_dtd40)
            defaultValue = BindingConstants.OGNL_PREFIX + ":" + defaultValue;

        ps.setDefaultValue(defaultValue);

        if (!_dtd40)
        {
            // When direction=auto (in a 3.0 DTD), turn caching off

            String direction = getAttribute("direction");
            ps.setCache(!"auto".equals(direction));
        }
        else
        {
            boolean cache = getBooleanAttribute("cache", true);
            ps.setCache(cache);
        }

        // type will only be specified in a 3.0 DTD.

        String type = getAttribute("type");

        if (type != null)
            ps.setType(type);

        // aliases is new in the 4.0 DTD

        String aliases = getAttribute("aliases");

        ps.setAliases(aliases);
        ps.setDeprecated(getBooleanAttribute("deprecated", false));

        IComponentSpecification cs = (IComponentSpecification) peekObject();

        cs.addParameter(ps);

        push(_elementName, ps, STATE_ALLOW_DESCRIPTION);
    }

    private void enterPrivateAsset30()
    {
        enterAsset("resource-path", "classpath:");
    }

    /** @since 4.0 */
    private void enterMeta()
    {
        String key = getAttribute("key");
        String value = getAttribute("value");

        // Value may be null, in which case the value is set from the element content

        IPropertyHolder ph = (IPropertyHolder) peekObject();

        push(_elementName, new PropertyValueSetter(ph, key, value), STATE_META, false);
    }

    private void enterProperty30()
    {
        String name = getAttribute("name");
        String value = getAttribute("value");

        // Value may be null, in which case the value is set from the element content

        IPropertyHolder ph = (IPropertyHolder) peekObject();

        push(_elementName, new PropertyValueSetter(ph, name, value), STATE_META, false);
    }

    /**
     * &tl;property&gt; in 4.0, or &lt;property-specification&gt; in 3.0.
     */

    private void enterProperty()
    {
        String name = getValidatedAttribute("name", PROPERTY_NAME_PATTERN, "invalid-property-name");
        String type = getAttribute("type");

        String persistence = null;

        if (_dtd40)
            persistence = getAttribute("persist");
        else
            persistence = getBooleanAttribute("persistent", false) ? "session" : null;

        String initialValue = getAttribute("initial-value");

        IPropertySpecification ps = _factory.createPropertySpecification();
        ps.setName(name);

        if (HiveMind.isNonBlank(type))
            ps.setType(type);

        ps.setPersistence(persistence);
        ps.setInitialValue(initialValue);

        IComponentSpecification cs = (IComponentSpecification) peekObject();
        cs.addPropertySpecification(ps);

        push(_elementName, ps, STATE_PROPERTY, false);
    }

    /**
     * @since 4.0
     */

    private void enterInject()
    {
        String property = getValidatedAttribute(
                "property",
                PROPERTY_NAME_PATTERN,
                "invalid-property-name");
        String type = getAttribute("type");
        String objectReference = getAttribute("object");

        InjectSpecification spec = _factory.createInjectSpecification();

        spec.setProperty(property);
        spec.setType(type);
        spec.setObject(objectReference);
        IComponentSpecification cs = (IComponentSpecification) peekObject();

        cs.addInjectSpecification(spec);

        push(_elementName, spec, STATE_NO_CONTENT);
    }

    private void enterReservedParameter()
    {
        String name = getAttribute("name");
        IComponentSpecification cs = (IComponentSpecification) peekObject();

        cs.addReservedParameterName(name);

        push(_elementName, null, STATE_NO_CONTENT);
    }

    private void enterService30()
    {
        _errorHandler.error(_log, ParseMessages.serviceElementNotSupported(), getLocation(), null);

        push(_elementName, null, STATE_NO_CONTENT);
    }

    private void enterSetMessage30()
    {
        String name = getAttribute("name");
        String key = getAttribute("key");

        BindingBeanInitializer bi = _factory.createBindingBeanInitializer(_bindingSource);

        bi.setPropertyName(name);
        bi.setBindingReference(BindingConstants.MESSAGE_PREFIX + ":" + key);
        bi.setLocation(getLocation());

        IBeanSpecification bs = (IBeanSpecification) peekObject();

        bs.addInitializer(bi);

        push(_elementName, null, STATE_NO_CONTENT);
    }

    private void enterSet()
    {
        String name = getAttribute("name");
        String reference = getAttribute("value");

        BindingBeanInitializer bi = _factory.createBindingBeanInitializer(_bindingSource);

        bi.setPropertyName(name);

        IBeanSpecification bs = (IBeanSpecification) peekObject();

        push(_elementName, new BeanSetPropertySetter(bs, bi, null, reference), STATE_SET, false);
    }

    private void enterSetProperty30()
    {
        String name = getAttribute("name");
        String expression = getAttribute("expression");

        BindingBeanInitializer bi = _factory.createBindingBeanInitializer(_bindingSource);

        bi.setPropertyName(name);

        IBeanSpecification bs = (IBeanSpecification) peekObject();

        push(_elementName, new BeanSetPropertySetter(bs, bi, BindingConstants.OGNL_PREFIX + ":",
                expression), STATE_SET, false);
    }

    private void enterStaticBinding30()
    {
        String name = getAttribute("name");
        String expression = getAttribute("value");

        IContainedComponent cc = (IContainedComponent) peekObject();

        BindingSetter bs = new BindingSetter(cc, name, expression);

        push(_elementName, bs, STATE_STATIC_BINDING, false);
    }

    private void expectElement(String elementName)
    {
        if (_elementName.equals(elementName))
            return;

        throw new DocumentParseException(ParseMessages.incorrectDocumentType(
                _elementName,
                elementName), getLocation(), null);

    }

    private String getAttribute(String name)
    {
        return (String) _attributes.get(name);
    }

    private boolean getBooleanAttribute(String name, boolean defaultValue)
    {
        String value = getAttribute(name);

        if (value == null)
            return defaultValue;

        Boolean b = (Boolean) _conversionMap.get(value);

        return b.booleanValue();
    }

    private Object getConvertedAttribute(String name, Object defaultValue)
    {
        String key = getAttribute(name);

        if (key == null)
            return defaultValue;

        return _conversionMap.get(key);
    }

    private InputSource getDTDInputSource(String name)
    {
        InputStream stream = getClass().getResourceAsStream(name);

        return new InputSource(stream);
    }

    private String getExtendedValue(String attributeValue, String attributeName, boolean required)
    {
        String contentValue = peekContent();

        boolean asAttribute = HiveMind.isNonBlank(attributeValue);
        boolean asContent = HiveMind.isNonBlank(contentValue);

        if (asAttribute && asContent)
        {
            throw new DocumentParseException(ParseMessages.noAttributeAndBody(
                    attributeName,
                    _elementName), getLocation(), null);
        }

        if (required && !(asAttribute || asContent))
        {
            throw new DocumentParseException(ParseMessages.requiredExtendedAttribute(
                    _elementName,
                    attributeName), getLocation(), null);
        }

        if (asAttribute)
            return attributeValue;

        return contentValue;
    }

    private String getValidatedAttribute(String name, String pattern, String errorKey)
    {
        String value = getAttribute(name);

        if (value == null)
            return null;

        if (_matcher.matches(pattern, value))
            return value;

        throw new InvalidStringException(ParseMessages.invalidAttribute(errorKey, value), value,
                getLocation());
    }

    protected void initializeParser(Resource resource, int startState)
    {
        super.initializeParser(resource, startState);

        _rootObject = null;
        _attributes = new HashMap();
    }

    public IApplicationSpecification parseApplicationSpecification(Resource resource)
    {
        initializeParser(resource, STATE_APPLICATION_SPECIFICATION_INITIAL);

        try
        {
            parseDocument();

            return (IApplicationSpecification) _rootObject;
        }
        finally
        {
            resetParser();
        }
    }

    public IComponentSpecification parseComponentSpecification(Resource resource)
    {
        initializeParser(resource, STATE_COMPONENT_SPECIFICATION_INITIAL);

        try
        {
            parseDocument();

            return (IComponentSpecification) _rootObject;
        }
        finally
        {
            resetParser();
        }
    }

    private void parseDocument()
    {
        InputStream stream = null;

        Resource resource = getResource();

        boolean success = false;

        try
        {
            if (_parser == null)
                _parser = _parserFactory.newSAXParser();

            URL resourceURL = resource.getResourceURL();

            if (resourceURL == null)
                throw new DocumentParseException(ParseMessages.missingResource(resource), resource);

            InputStream rawStream = resourceURL.openStream();
            stream = new BufferedInputStream(rawStream);

            _parser.parse(stream, this, resourceURL.toExternalForm());

            stream.close();
            stream = null;

            success = true;
        }
        catch (SAXParseException ex)
        {
            _parser = null;

            Location location = new LocationImpl(resource, ex.getLineNumber(), ex.getColumnNumber());

            throw new DocumentParseException(ParseMessages.errorReadingResource(resource, ex),
                    location, ex);
        }
        catch (Exception ex)
        {
            _parser = null;

            throw new DocumentParseException(ParseMessages.errorReadingResource(resource, ex),
                    resource, ex);
        }
        finally
        {
            if (!success)
                _parser = null;

            close(stream);
        }
    }

    public ILibrarySpecification parseLibrarySpecification(Resource resource)
    {
        initializeParser(resource, STATE_LIBRARY_SPECIFICATION_INITIAL);

        try
        {
            parseDocument();

            return (ILibrarySpecification) _rootObject;
        }
        finally
        {
            resetParser();
        }
    }

    public IComponentSpecification parsePageSpecification(Resource resource)
    {
        initializeParser(resource, STATE_PAGE_SPECIFICATION_INITIAL);

        try
        {
            parseDocument();

            return (IComponentSpecification) _rootObject;
        }
        finally
        {
            resetParser();
        }
    }

    protected String peekContent()
    {
        String content = super.peekContent();

        if (content == null)
            return null;

        return content.trim();
    }

    protected void resetParser()
    {
        _rootObject = null;
        _dtd40 = false;

        _attributes.clear();
    }

    /**
     * Resolved an external entity, which is assumed to be the doctype. Might need a check to ensure
     * that specs without a doctype fail.
     */
    public InputSource resolveEntity(String publicId, String systemId) throws SAXException
    {
        if (TAPESTRY_DTD_4_0_PUBLIC_ID.equals(publicId))
        {
            _dtd40 = true;
            return getDTDInputSource("Tapestry_4_0.dtd");
        }

        if (TAPESTRY_DTD_4_1_PUBLIC_ID.equals(publicId))
        {
            _dtd40 = true;
            return getDTDInputSource("Tapestry_4_1.dtd");
        }
       
        if (TAPESTRY_DTD_3_0_PUBLIC_ID.equals(publicId))
            return getDTDInputSource("Tapestry_3_0.dtd");

        throw new DocumentParseException(ParseMessages.unknownPublicId(getResource(), publicId),
                new LocationImpl(getResource()), null);
    }

    /** @since 4.0 */
    public void setBindingSource(BindingSource bindingSource)
    {
        _bindingSource = bindingSource;
    }

    /** @since 4.0 */
    public void setValueConverter(ValueConverter valueConverter)
    {
        _valueConverter = valueConverter;
    }
}
TOP

Related Classes of org.apache.tapestry.parse.SpecificationParser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.