blob: 4e7e5175f34eb051d9018337409242815e83d06f [file]
// 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.spec;
import org.apache.hivemind.ApplicationRuntimeException;
import org.apache.hivemind.Location;
import org.apache.hivemind.Resource;
import org.apache.hivemind.util.ToStringBuilder;
import org.apache.tapestry.Tapestry;
import java.util.*;
/**
* Specification for a library.
* {@link org.apache.tapestry.spec.ApplicationSpecification}is a specialized
* kind of library.
*
* @author Howard Lewis Ship
* @since 2.2bv
*/
public class LibrarySpecification extends LocatablePropertyHolder implements ILibrarySpecification
{
/**
* Map of page name to page specification path.
*/
private Map _pages;
/**
* Map of component alias to component specification path.
*/
private Map _components;
/**
* Map of library id to library specification path.
*/
private Map _libraries;
private String _description;
/**
* Map of extension name to {@link IExtensionSpecification}.
*/
private Map _extensions;
/**
* Map of extension name to Object for instantiated extensions.
*/
private Map _instantiatedExtensions;
/**
* The XML Public Id used when the library specification was read (if
* applicable).
*
* @since 2.2
*/
private String _publicId;
/**
* The location of the specification.
*/
private Resource _specificationLocation;
public String getLibrarySpecificationPath(String id)
{
return (String) get(_libraries, id);
}
/**
* Sets the specification path for an embedded library.
*
* @throws IllegalArgumentException
* if a library with the given id already exists
*/
public void setLibrarySpecificationPath(String id, String path)
{
if (_libraries == null) _libraries = new HashMap();
if (_libraries.containsKey(id))
throw new IllegalArgumentException(Tapestry.format("LibrarySpecification.duplicate-child-namespace-id", id));
_libraries.put(id, path);
}
public List getLibraryIds()
{
return sortedKeys(_libraries);
}
public String getPageSpecificationPath(String name)
{
return (String) get(_pages, name);
}
public void setPageSpecificationPath(String name, String path)
{
if (_pages == null)
_pages = new HashMap();
if (_pages.containsKey(name))
throw new IllegalArgumentException(Tapestry.format("LibrarySpecification.duplicate-page-name", name));
_pages.put(name, path);
}
public List getPageNames()
{
return sortedKeys(_pages);
}
public void setComponentSpecificationPath(String alias, String path)
{
if (_components == null)
_components = new HashMap();
if (_components.containsKey(alias))
throw new IllegalArgumentException(Tapestry.format("LibrarySpecification.duplicate-component-alias", alias));
_components.put(alias, path);
}
public String getComponentSpecificationPath(String alias)
{
return (String) get(_components, alias);
}
/**
* @since 3.0
*/
public List getComponentTypes()
{
return sortedKeys(_components);
}
private List sortedKeys(Map map)
{
if (map == null)
return Collections.EMPTY_LIST;
List result = new ArrayList(map.keySet());
Collections.sort(result);
return result;
}
private Object get(Map map, Object key)
{
if (map == null)
return null;
return map.get(key);
}
/**
* Returns the documentation for this library..
*/
public String getDescription()
{
return _description;
}
/**
* Sets the documentation for this library.
*/
public void setDescription(String description)
{
_description = description;
}
/**
* Returns a Map of extensions; key is extension name, value is
* {@link org.apache.tapestry.spec.IExtensionSpecification}. May return
* null. The returned Map is immutable.
*/
public Map getExtensionSpecifications()
{
if (_extensions == null)
return null;
return Collections.unmodifiableMap(_extensions);
}
/**
* Adds another extension specification.
*
* @throws IllegalArgumentException
* if an extension with the given name already exists.
*/
public void addExtensionSpecification(String name, IExtensionSpecification extension)
{
if (_extensions == null)
_extensions = new HashMap();
if (_extensions.containsKey(name))
throw new IllegalArgumentException(Tapestry.format("LibrarySpecification.duplicate-extension-name",
this, name));
_extensions.put(name, extension);
}
/**
* Returns a sorted List of the names of all extensions. May return the
* empty list, but won't return null.
*/
public synchronized List getExtensionNames()
{
return sortedKeys(_instantiatedExtensions);
}
/**
* Returns the named IExtensionSpecification, or null if it doesn't exist.
*/
public IExtensionSpecification getExtensionSpecification(String name)
{
if (_extensions == null)
return null;
return (IExtensionSpecification) _extensions.get(name);
}
/**
* Returns true if this library specification has a specification for the
* named extension.
*/
public boolean checkExtension(String name)
{
if (_extensions == null)
return false;
return _extensions.containsKey(name);
}
/**
* Returns an instantiated extension. Extensions are created as needed and
* cached for later use.
*
* @throws IllegalArgumentException
* if no extension specification exists for the given name.
*/
public synchronized Object getExtension(String name)
{
return getExtension(name, null);
}
/** @since 3.0 * */
public synchronized Object getExtension(String name, Class typeConstraint)
{
if (_instantiatedExtensions == null)
_instantiatedExtensions = new HashMap();
Object result = _instantiatedExtensions.get(name);
IExtensionSpecification spec = getExtensionSpecification(name);
if (spec == null)
throw new IllegalArgumentException(Tapestry.format("LibrarySpecification.no-such-extension", name));
if (result == null)
{
result = spec.instantiateExtension();
_instantiatedExtensions.put(name, result);
}
if (typeConstraint != null)
applyTypeConstraint(name, result, typeConstraint, spec.getLocation());
return result;
}
/**
* Checks that an extension conforms to the supplied type constraint.
*
* @param name
* Name of the extension to apply constraint check to.
* @param extension
* Object extension.
* @param typeConstraint
* Constraint to check.
* @param location
* Location of specified extension.
*
* @throws IllegalArgumentException
* if the extension fails the check.
* @since 3.0
*/
protected void applyTypeConstraint(String name, Object extension,
Class typeConstraint, Location location)
{
Class extensionClass = extension.getClass();
// Can you assign an instance of the extension to a variable
// of type typeContraint legally?
if (typeConstraint.isAssignableFrom(extensionClass))
return;
String key = typeConstraint.isInterface()
? "LibrarySpecification.extension-does-not-implement-interface"
: "LibrarySpecification.extension-not-a-subclass";
throw new ApplicationRuntimeException(
Tapestry.format(key, name, extensionClass.getName(), typeConstraint.getName()), location, null);
}
/**
* Invoked after the entire specification has been constructed to
* instantiate any extensions marked immediate.
*/
public synchronized void instantiateImmediateExtensions()
{
if (_extensions == null)
return;
Iterator i = _extensions.entrySet().iterator();
while(i.hasNext())
{
Map.Entry entry = (Map.Entry) i.next();
IExtensionSpecification spec = (IExtensionSpecification) entry.getValue();
if (!spec.isImmediate())
continue;
String name = (String) entry.getKey();
getExtension(name);
}
}
/**
* Returns the extensions map.
*
* @return Map of objects.
*/
protected Map getExtensions()
{
return _extensions;
}
/**
* Updates the extension map.
*
* @param extension
* A Map of extension specification paths keyed on extension id.
* <p>
* The map is retained, not copied.
*/
protected void setExtensions(Map extension)
{
_extensions = extension;
}
/**
* Returns the libraries map.
*
* @return Map of {@link LibrarySpecification}.
*/
protected Map getLibraries()
{
return _libraries;
}
/**
* Updates the library map.
*
* @param libraries
* A Map of library specification paths keyed on library id.
* <p>
* The map is retained, not copied.
*/
protected void setLibraries(Map libraries)
{
_libraries = libraries;
}
/**
* Returns the pages map.
*
* @return Map of {@link IComponentSpecification}.
*/
protected Map getPages()
{
return _pages;
}
/**
* Updates the page map.
*
* @param pages
* A Map of page specification paths keyed on page id.
* <p>
* The map is retained, not copied.
*/
protected void setPages(Map pages)
{
_pages = pages;
}
/**
* Returns the components map.
*
* @return Map of {@link IContainedComponent}.
*/
protected Map getComponents()
{
return _components;
}
/**
* Updates the components map.
*
* @param components
* A Map of {@link IContainedComponent}keyed on component id.
* The map is retained, not copied.
*/
protected void setComponents(Map components)
{
_components = components;
}
/**
* Returns the XML Public Id for the library 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.
*/
public String getPublicId()
{
return _publicId;
}
public void setPublicId(String publicId)
{
_publicId = publicId;
}
/** @since 3.0 * */
public Resource getSpecificationLocation()
{
return _specificationLocation;
}
/** @since 3.0 * */
public void setSpecificationLocation(Resource specificationLocation)
{
_specificationLocation = specificationLocation;
}
/** @since 3.0 * */
public synchronized String toString()
{
ToStringBuilder builder = new ToStringBuilder(this);
builder.append("components", _components);
builder.append("description", _description);
builder.append("instantiatedExtensions", _instantiatedExtensions);
builder.append("libraries", _libraries);
builder.append("pages", _pages);
builder.append("publicId", _publicId);
builder.append("specificationLocation", _specificationLocation);
extendDescription(builder);
return builder.toString();
}
/**
* Does nothing, subclasses may override to add additional description.
*
* @see #toString()
* @since 3.0
*/
protected void extendDescription(ToStringBuilder builder)
{
}
}