blob: 84904f88510b0c726b2bd8c42f0545562327c2a9 [file] [log] [blame]
/*
* $Id$
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.tiles.definition;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.tiles.Attribute;
import org.apache.tiles.Definition;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
/**
* @version $Rev$ $Date$
*/
public class DefinitionsImpl implements Definitions {
/**
* Commons Logging instance.
*/
private static Log log = LogFactory.getLog(DefinitionsImpl.class);
/**
* The base set of Definition objects not discriminated by locale.
*/
private Map<String, Definition> baseDefinitions;
/**
* The locale-specific set of definitions objects.
*/
private Map<Locale, Map<String, Definition>> localeSpecificDefinitions;
/**
* Creates a new instance of DefinitionsImpl.
*/
public DefinitionsImpl() {
baseDefinitions = new HashMap<String, Definition>();
localeSpecificDefinitions =
new HashMap<Locale, Map<String, Definition>>();
}
/**
* Returns a Definition object that matches the given name.
*
* @param name The name of the Definition to return.
* @return the Definition matching the given name or null if none
* is found.
*/
public Definition getDefinition(String name) {
return baseDefinitions.get(name);
}
/**
* Adds new Definition objects to the internal collection and
* resolves inheritance attraibutes.
*
* @param defsMap The new definitions to add.
* @throws NoSuchDefinitionException If something goes wrong during
* addition.
*/
public void addDefinitions(Map<String, Definition> defsMap)
throws NoSuchDefinitionException {
this.baseDefinitions.putAll(defsMap);
resolveInheritances();
}
/**
* Adds new locale-specific Definition objects to the internal
* collection and resolves inheritance attraibutes.
*
* @param defsMap The new definitions to add.
* @param locale The locale to add the definitions to.
* @throws NoSuchDefinitionException If something goes wrong during
* inheritance resolution.
*/
public void addDefinitions(Map<String, Definition> defsMap,
Locale locale) throws NoSuchDefinitionException {
localeSpecificDefinitions.put(locale, defsMap);
resolveInheritances(locale);
}
/**
* Returns a Definition object that matches the given name and locale.
*
* @param name The name of the Definition to return.
* @param locale The locale to use to resolve the definition.
* @return the Definition matching the given name or null if none
* is found.
*/
public Definition getDefinition(String name, Locale locale) {
Definition definition = null;
if (locale != null) {
Map<String, Definition> localeSpecificMap =
localeSpecificDefinitions.get(locale);
if (localeSpecificMap != null) {
definition = localeSpecificMap.get(name);
}
}
if (definition == null) {
definition = getDefinition(name);
}
return definition;
}
/**
* Resolve extended instances.
*
* @throws NoSuchDefinitionException If a parent definition is not found.
*/
public void resolveInheritances() throws NoSuchDefinitionException {
Set<String> alreadyResolvedDefinitions = new HashSet<String>();
for (Definition definition : baseDefinitions.values()) {
resolveInheritance(definition, null, alreadyResolvedDefinitions);
} // end loop
}
/**
* Resolve locale-specific extended instances.
*
* @param locale The locale to use.
* @throws NoSuchDefinitionException If a parent definition is not found.
*/
public void resolveInheritances(Locale locale) throws NoSuchDefinitionException {
resolveInheritances();
Map<String, Definition> map = localeSpecificDefinitions.get(locale);
if (map != null) {
Set<String> alreadyResolvedDefinitions = new HashSet<String>();
for (Definition definition : map.values()) {
resolveInheritance(definition, locale,
alreadyResolvedDefinitions);
} // end loop
}
}
/**
* Clears definitions.
*/
public void reset() {
this.baseDefinitions = new HashMap<String, Definition>();
this.localeSpecificDefinitions =
new HashMap<Locale, Map<String, Definition>>();
}
/**
* Returns base definitions collection.
*
* @return The base (i.e. not depending on any locale) definitions map.
*/
public Map<String, Definition> getBaseDefinitions() {
return baseDefinitions;
}
/**
* Searches for a definition specified as an attribute.
*
* @param attr The attribute to use.
* @param locale The locale to search into.
* @return The required definition if found, otherwise it returns
* <code>null</code>.
*/
protected Definition getDefinitionByAttribute(
Attribute attr, Locale locale) {
Definition retValue = null;
Object attrValue = attr.getValue();
if (attrValue instanceof String) {
retValue = this.getDefinition((String) attr
.getValue(), locale);
}
return retValue;
}
/**
* Resolve locale-specific inheritance.
* First, resolve parent's inheritance, then set template to the parent's
* template.
* Also copy attributes setted in parent, and not set in child
* If instance doesn't extend anything, do nothing.
*
* @param definition The definition to resolve
* @param locale The locale to use.
* @param alreadyResolvedDefinitions The set of the definitions that have
* been already resolved.
* @throws NoSuchDefinitionException If an inheritance can not be solved.
*/
protected void resolveInheritance(Definition definition, Locale locale,
Set<String> alreadyResolvedDefinitions)
throws NoSuchDefinitionException {
// Already done, or not needed ?
if (!definition.isExtending()
|| alreadyResolvedDefinitions.contains(definition.getName())) {
return;
}
if (log.isDebugEnabled()) {
log.debug("Resolve definition for child name='"
+ definition.getName()
+ "' extends='" + definition.getExtends() + "'.");
}
// Set as visited to avoid endless recurisvity.
alreadyResolvedDefinitions.add(definition.getName());
// Resolve parent before itself.
Definition parent = getDefinition(definition.getExtends(),
locale);
if (parent == null) { // error
String msg = "Error while resolving definition inheritance: child '"
+ definition.getName()
+ "' can't find its ancestor '"
+ definition.getExtends()
+ "'. Please check your description file.";
log.error(msg);
// to do : find better exception
throw new NoSuchDefinitionException(msg);
}
resolveInheritance(parent, locale, alreadyResolvedDefinitions);
overload(parent, definition);
}
/**
* Overloads a child definition with a given parent.
* All attributes present in child are kept. All missing attributes are
* copied from the parent.
* Special attribute 'template','role' and 'extends' are overloaded in child
* if not defined
*
* @param parent The parent definition.
* @param child The child that will be overloaded.
*/
// FIXME This is the same as DefinitionManager.overload.
protected void overload(Definition parent, Definition child) {
// Iterate on each parent's attribute and add it if not defined in child.
for (Map.Entry<String, Attribute> entry : parent.getAttributes().entrySet()) {
if (!child.hasAttributeValue(entry.getKey())) {
child.putAttribute(entry.getKey(), new Attribute(entry.getValue()));
}
}
// Set template and role if not setted
if (child.getTemplate() == null) {
child.setTemplate(parent.getTemplate());
}
if (child.getRole() == null) {
child.setRole(parent.getRole());
}
if (child.getPreparer() == null) {
child.setPreparer(parent.getPreparer());
}
}
}