blob: 40ee2148a013bc21e9b0e2e10c7e00d8958844ae [file] [log] [blame]
// Copyright 2004 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.spec;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.tapestry.IResourceLocation;
import org.apache.tapestry.Tapestry;
/**
* A specification for a component, as read from an XML specification file.
*
* <p>A specification consists of
* <ul>
* <li>An implementing class
* <li>An optional template
* <li>An optional description
* <li>A set of contained components
* <li>Bindings for the properties of each contained component
* <li>A set of named assets
* <li>Definitions for helper beans
* <li>Any reserved names (used for HTML attributes)
* </ul>
*
* <p>From this information, an actual component may be instantiated and
* initialized. Instantiating a component is usually a recursive process, since
* to initialize a container component, it is necessary to instantiate and initialize
* its contained components as well.
*
* @see org.apache.tapestry.IComponent
* @see IContainedComponent
* @see org.apache.tapestry.engine.IPageLoader
*
* @author Howard Lewis Ship
* @version $Id$
*
**/
public class ComponentSpecification extends LocatablePropertyHolder implements IComponentSpecification
{
private String _componentClassName;
/** @since 1.0.9 **/
private String _description;
/**
* Keyed on component id, value is {@link IContainedComponent}.
*
**/
protected Map _components;
/**
* Keyed on asset name, value is {@link IAssetSpecification}.
*
**/
protected Map _assets;
/**
* Defines all formal parameters. Keyed on parameter name, value is
* {@link IParameterSpecification}.
*
**/
protected Map _parameters;
/**
* Defines all helper beans. Keyed on name, value is {@link IBeanSpecification}.
*
* @since 1.0.4
**/
protected Map _beans;
/**
* The names of all reserved informal parameter names (as lower-case). This
* allows the page loader to filter out any informal parameters during page load,
* rather than during render.
*
* @since 1.0.5
*
**/
protected Set _reservedParameterNames;
/**
* Is the component allowed to have a body (that is, wrap other elements?).
*
**/
private boolean _allowBody = true;
/**
* Is the component allow to have informal parameter specified.
*
**/
private boolean _allowInformalParameters = true;
/**
* The XML Public Id used when the page or component specification was read
* (if applicable).
*
* @since 2.2
*
**/
private String _publicId;
/**
* Indicates that the specification is for a page, not a component.
*
* @since 2.2
*
**/
private boolean _pageSpecification;
/**
* The location from which the specification was obtained.
*
* @since 3.0
*
**/
private IResourceLocation _specificationLocation;
/**
* A Map of {@link IPropertySpecification} keyed on the name
* of the property.
*
* @since 3.0
*
**/
private Map _propertySpecifications;
/**
* @throws IllegalArgumentException if the name already exists.
*
**/
public void addAsset(String name, IAssetSpecification asset)
{
if (_assets == null)
_assets = new HashMap();
if (_assets.containsKey(name))
throw new IllegalArgumentException(
Tapestry.format("ComponentSpecification.duplicate-asset", this, name));
_assets.put(name, asset);
}
/**
* @throws IllegalArgumentException if the id is already defined.
*
**/
public void addComponent(String id, IContainedComponent component)
{
if (_components == null)
_components = new HashMap();
if (_components.containsKey(id))
throw new IllegalArgumentException(
Tapestry.format("ComponentSpecification.duplicate-component", this, id));
_components.put(id, component);
}
/**
* Adds the parameter. The name is added as a reserved name.
*
* @throws IllegalArgumentException if the name already exists.
**/
public void addParameter(String name, IParameterSpecification spec)
{
if (_parameters == null)
_parameters = new HashMap();
if (_parameters.containsKey(name))
throw new IllegalArgumentException(
Tapestry.format("ComponentSpecification.duplicate-parameter", this, name));
_parameters.put(name, spec);
addReservedParameterName(name);
}
/**
* Returns true if the component is allowed to wrap other elements (static HTML
* or other components). The default is true.
*
* @see #setAllowBody(boolean)
*
**/
public boolean getAllowBody()
{
return _allowBody;
}
/**
* Returns true if the component allows informal parameters (parameters
* not formally defined). Informal parameters are generally used to create
* additional HTML attributes for an HTML tag rendered by the
* component. This is often used to specify JavaScript event handlers or the class
* of the component (for Cascarding Style Sheets).
*
* <p>The default value is true.
*
* @see #setAllowInformalParameters(boolean)
**/
public boolean getAllowInformalParameters()
{
return _allowInformalParameters;
}
/**
* Returns the {@link IAssetSpecification} with the given name, or null
* if no such specification exists.
*
* @see #addAsset(String,IAssetSpecification)
**/
public IAssetSpecification getAsset(String name)
{
return (IAssetSpecification) get(_assets, name);
}
/**
* Returns a <code>List</code>
* of the String names of all assets, in alphabetical
* order
*
**/
public List getAssetNames()
{
return sortedKeys(_assets);
}
/**
* Returns the specification of a contained component with the given id, or
* null if no such contained component exists.
*
* @see #addComponent(String, IContainedComponent)
*
**/
public IContainedComponent getComponent(String id)
{
return (IContainedComponent) get(_components, id);
}
public String getComponentClassName()
{
return _componentClassName;
}
/**
* Returns an <code>List</code>
* of the String names of the {@link IContainedComponent}s
* for this component.
*
* @see #addComponent(String, IContainedComponent)
*
**/
public List getComponentIds()
{
return sortedKeys(_components);
}
/**
* Returns the specification of a parameter with the given name, or
* null if no such parameter exists.
*
* @see #addParameter(String, IParameterSpecification)
*
**/
public IParameterSpecification getParameter(String name)
{
return (IParameterSpecification) get(_parameters, name);
}
/**
* Returns a List of
* of String names of all parameters. This list
* is in alphabetical order.
*
* @see #addParameter(String, IParameterSpecification)
*
**/
public List getParameterNames()
{
return sortedKeys(_parameters);
}
public void setAllowBody(boolean value)
{
_allowBody = value;
}
public void setAllowInformalParameters(boolean value)
{
_allowInformalParameters = value;
}
public void setComponentClassName(String value)
{
_componentClassName = value;
}
/**
* @since 1.0.4
*
* @throws IllegalArgumentException if the bean already has a specification.
**/
public void addBeanSpecification(String name, IBeanSpecification specification)
{
if (_beans == null)
_beans = new HashMap();
else
if (_beans.containsKey(name))
throw new IllegalArgumentException(
Tapestry.format("ComponentSpecification.duplicate-bean", this, name));
_beans.put(name, specification);
}
/**
* Returns the {@link IBeanSpecification} for the given name, or null
* if not such specification exists.
*
* @since 1.0.4
*
**/
public IBeanSpecification getBeanSpecification(String name)
{
if (_beans == null)
return null;
return (IBeanSpecification) _beans.get(name);
}
/**
* Returns an unmodifiable collection of the names of all beans.
*
**/
public Collection getBeanNames()
{
if (_beans == null)
return Collections.EMPTY_LIST;
return Collections.unmodifiableCollection(_beans.keySet());
}
/**
* Adds the value as a reserved name. Reserved names are not allowed
* as the names of informal parameters. Since the comparison is
* caseless, the value is converted to lowercase before being
* stored.
*
* @since 1.0.5
*
**/
public void addReservedParameterName(String value)
{
if (_reservedParameterNames == null)
_reservedParameterNames = new HashSet();
_reservedParameterNames.add(value.toLowerCase());
}
/**
* Returns true if the value specified is in the reserved name list.
* The comparison is caseless. All formal parameters are automatically
* in the reserved name list, as well as any additional
* reserved names specified in the component specification. The latter
* refer to HTML attributes generated directly by the component.
*
* @since 1.0.5
*
**/
public boolean isReservedParameterName(String value)
{
if (_reservedParameterNames == null)
return false;
return _reservedParameterNames.contains(value.toLowerCase());
}
public String toString()
{
ToStringBuilder builder = new ToStringBuilder(this);
builder.append("componentClassName", _componentClassName);
builder.append("pageSpecification", _pageSpecification);
builder.append("specificationLocation", _specificationLocation);
builder.append("allowBody", _allowBody);
builder.append("allowInformalParameter", _allowInformalParameters);
return builder.toString();
}
/**
* Returns the documentation for this component.
*
* @since 1.0.9
**/
public String getDescription()
{
return _description;
}
/**
* Sets the documentation for this component.
*
* @since 1.0.9
**/
public void setDescription(String description)
{
_description = description;
}
/**
* Returns the XML Public Id for the specification file, or null
* if not applicable.
*
* <p>
* This method exists as a convienience for the Spindle plugin.
* A previous method used an arbitrary version string, the
* public id is more useful and less ambiguous.
*
* @since 2.2
*
**/
public String getPublicId()
{
return _publicId;
}
/** @since 2.2 **/
public void setPublicId(String publicId)
{
_publicId = publicId;
}
/**
*
* Returns true if the specification is known to be a page
* specification and not a component specification. Earlier versions
* of the framework did not distinguish between the two, but starting
* in 2.2, there are seperate XML entities for pages and components.
* Pages omit several attributes and entities related
* to parameters, as parameters only make sense for components.
*
* @since 2.2
*
**/
public boolean isPageSpecification()
{
return _pageSpecification;
}
/** @since 2.2 **/
public void setPageSpecification(boolean pageSpecification)
{
_pageSpecification = pageSpecification;
}
/** @since 2.2 **/
private List sortedKeys(Map input)
{
if (input == null)
return Collections.EMPTY_LIST;
List result = new ArrayList(input.keySet());
Collections.sort(result);
return result;
}
/** @since 2.2 **/
private Object get(Map map, Object key)
{
if (map == null)
return null;
return map.get(key);
}
/** @since 3.0 **/
public IResourceLocation getSpecificationLocation()
{
return _specificationLocation;
}
/** @since 3.0 **/
public void setSpecificationLocation(IResourceLocation specificationLocation)
{
_specificationLocation = specificationLocation;
}
/**
* Adds a new property specification. The name of the property must
* not already be defined (and must not change after being added).
*
* @since 3.0
*
**/
public void addPropertySpecification(IPropertySpecification spec)
{
if (_propertySpecifications == null)
_propertySpecifications = new HashMap();
String name = spec.getName();
if (_propertySpecifications.containsKey(name))
throw new IllegalArgumentException(
Tapestry.format(
"ComponentSpecification.duplicate-property-specification",
this,
name));
_propertySpecifications.put(name, spec);
}
/**
* Returns a sorted, immutable list of the names of all
* {@link org.apache.tapestry.spec.IPropertySpecification}s.
*
* @since 3.0
*
**/
public List getPropertySpecificationNames()
{
return sortedKeys(_propertySpecifications);
}
/**
* Returns the named {@link org.apache.tapestry.spec.IPropertySpecification},
* or null if no such specification exist.
*
* @since 3.0
* @see #addPropertySpecification(IPropertySpecification)
*
**/
public IPropertySpecification getPropertySpecification(String name)
{
return (IPropertySpecification) get(_propertySpecifications, name);
}
}