/*
* org.openmicroscopy.shoola.env.config.Entry
*
*------------------------------------------------------------------------------
* Copyright (C) 2006 University of Dundee. All rights reserved.
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*------------------------------------------------------------------------------
*/
package org.openmicroscopy.shoola.env.config;
//Java imports
import java.util.HashMap;
import java.util.Map;
//Third-party libraries
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
//Application-internal dependencies
/**
* Sits at the base of a hierarchy of classes that represent entries in
* configuration files.
* It represents a name-value pair, where the name is the content of the
* <i>name</i> attribute of a configuration entry (which is stored by the
* <code>name</code> field) and the value is the object representing the
* content of the entry tag.
* <p>As the logic for building an object from the content of the entry tag
* depends on what is specified by the <i>type</i> attribute, this class
* declares an abstract {@link #getValue() getValue} method which subclasses
* implement to return the desired object. So we have subclasses
* ({@link StringEntry}, {@link IntegerEntry}, {@link IconFactoryEntry}, etc.)
* to handle the content of an entry tag (either <i>entry</i> or
* <i>structuredEntry</i>) in correspondence of each predefined value of
* the <i>type</i> attribute (<i>"string"</i>, <i>"integer"</i>,
* <i>"icons"</i>, and so on).</p>
* <p>Given an entry tag, the {@link #createEntryFor(Node) createEntryFor}
* static method (which can be considered a Factory Method) creates a concrete
* <code>Entry</code> object to handle the conversion of the content of that tag
* into an object. Subclasses implement the
* {@link #setContent(Node) setContent} method to grab the content of the tag,
* which is then used for building the object returned by the implementation of
* {@link #getValue()}.</p>
*
* @author Jean-Marie Burel
* <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
* @author <br>Andrea Falconi
* <a href="mailto:a.falconi@dundee.ac.uk">
* a.falconi@dundee.ac.uk</a>
* @version 2.2
* <small>
* (<b>Internal version:</b> $Revision$ $Date$)
* </small>
* @since OME2.2
*/
abstract class Entry
{
/** The <i>entry</i> tag. */
static String ENTRY = "entry";
/** The <i>structuredEntry</i> tag. */
static String STRUCT_ENTRY = "structuredEntry";
/** The <i>name</i> attribute. */
static private String NAME = "name";
/** The <i>type</i> attribute. */
static private String TYPE = "type";
/**
* The default value of the <i>type</i> attribute for the <i>entry</i> tag.
*/
static private String DEFAULT_ENTRY = "string";
/**
* The default value of the <i>type</i> attribute for the
* <i>structuredEntry</i> tag.
*/
static private String DEFAULT_STRUCT_ENTRY ="map";
/**
* Maps each predefined value of the <i>type</i> attribute onto the
* <i>FQN</i> of the handler class.
*/
static private Map<String, Class<?>> contentHandlers;
static {
contentHandlers = new HashMap<String, Class<?>>();
contentHandlers.put("map", MapEntry.class);
contentHandlers.put("string", StringEntry.class);
contentHandlers.put("integer", IntegerEntry.class);
contentHandlers.put("float", FloatEntry.class);
contentHandlers.put("long", LongEntry.class);
contentHandlers.put("double", DoubleEntry.class);
contentHandlers.put("boolean", BooleanEntry.class);
contentHandlers.put("OMERODS", OMEROEntry.class);
contentHandlers.put("font", FontEntry.class);
contentHandlers.put("color", ColorEntry.class);
contentHandlers.put("icons", IconFactoryEntry.class);
contentHandlers.put("agents", AgentsEntry.class);
contentHandlers.put("agents", AgentsEntry.class);
contentHandlers.put("plugins", PluginEntry.class);
}
/**
* Holds the contents of the <i>name</i> and <i>type</i> attributes of an
* entry tag (either <i>entry</i> or <i>structuredEntry</i>).
*/
private static class NameTypePair
{
/** The name attribute. */
String name;
/** The type attribute. */
String type;
}
/**
* Creates a concrete <code>Entry</code> object to handle the conversion of
* the content of the passed tag into an object.
*
* @param tag DOM node representing either an <i>entry</i> or
* <i>structuredEntry</i> tag.
* @return See above.
* @throws ConfigException If the configuration entry couldn't be handled.
*/
static Entry createEntryFor(Node tag)
throws ConfigException
{
Entry entry = null;
//TODO: remove this check when we have a proper schema.
if (!tag.hasAttributes())
throw new ConfigException("Missing tag's attributes.");
//First get the couple (name, type) -- type will be set to the
//appropriate default if necessary.
NameTypePair ntp = retrieveEntryAttributes(tag);
//Retrieve the handler for type if it's a built-in type.
Class<?> handler = contentHandlers.get(ntp.type);
try {
if (handler == null)
//Then type is not one of built-in types, this means that
//it must be the FQN of a custom handler. Load it.
handler = Class.forName(ntp.type);
//Finally create the handler.
entry = (Entry) handler.newInstance();
} catch(Exception e) {
rethrow(ntp, e);
}
//Set name, but delegate value setting.
entry.name = ntp.name;
entry.setContent(tag);
return entry;
}
/**
* Convenience method to wrap and re-throw an exception occurred while
* trying to create a tag handler.
* Wraps the original exception into a {@link ConfigException}, which is
* then re-thrown with an error message.
*
* @param ntp The name and type of the tag.
* @param e The original exception.
* @throws ConfigException Wraps the original exception and contains an
* error message.
*/
private static void rethrow(NameTypePair ntp, Exception e)
throws ConfigException
{
StringBuffer msg = new StringBuffer(
"Can't instantiate tag's handler. Tag name: ");
msg.append(ntp.name);
msg.append(", type: ");
msg.append(ntp.type);
msg.append(".");
throw new ConfigException(msg.toString(), e);
}
/**
* Retrieves the value of the <i>name</i> and <i>type</i> attributes.
* Sets the appropriate default for <i>type</i> if no value was provided.
*
* @param tag DOM node representing either an <i>entry</i> or
* <i>structuredEntry</i> tag.
* @return A <code>NameTypePair</code> object that stores the the value of
* the <i>name</i> and <i>type</i> attributes.
* @throws ConfigException If no value was provided for the name attribute.
*/
private static NameTypePair retrieveEntryAttributes(Node tag)
throws ConfigException
{
NameTypePair ntp = new NameTypePair();
NamedNodeMap attrList = tag.getAttributes(); //Get attrs.
Node attribute;
//Store the values of name and type attributes into ntp.
for (int i = 0; i < attrList.getLength(); ++i) {
attribute = attrList.item(i);
if (NAME.equals(attribute.getNodeName()))
ntp.name = attribute.getNodeValue();
else if (TYPE.equals(attribute.getNodeName()))
ntp.type = attribute.getNodeValue();
}
//Complain if no name attribute was provided.
//TODO: remove this check when we have a proper schema.
if (ntp.name == null || ntp.name.length() == 0)
throw new ConfigException("Missing name attribute");
//Set appropriate default for type if no value was provided.
if (ntp.type == null)
ntp.type = (ENTRY.equals(tag.getNodeName()) ? DEFAULT_ENTRY :
//if not entry tag then must be structuredEntry tag
DEFAULT_STRUCT_ENTRY);
return ntp;
}
/** The content of the <i>name</i> attribute. */
private String name;
/**
* Wraps the original exception into a {@link ConfigException}, which is
* then re-thrown with an error message.
* The error message will contain the specified context information plus
* the message, if any, of the original exception.
* This method is used by subclasses to re-throw exceptions that may occur
* during the process of parsing a tag and build an object in the
* {@link #setContent(Node) setContent} method.
*
* @param message Some context information.
* @param e The original exception.
* @throws ConfigException Wraps the original exception and contains an
* error message.
*/
protected void rethrow(String message, Exception e)
throws ConfigException
{
StringBuffer msg = new StringBuffer();
if (message == null || message.length() == 0)
message = "An error occurred.";
msg.append(message);
String explanation = e.getMessage();
if (explanation != null && explanation.length() != 0) {
msg.append(" (");
msg.append(explanation);
msg.append(")");
}
throw new ConfigException(msg.toString(), e);
}
/**
* Subclasses implement this method to grab the content of the tag,
* which is then used for building the object returned by the
* implementation of {@link #getValue()}.
*
* @param tag DOM node representing either an <i>entry</i> or
* <i>structuredEntry</i> tag.
* @throws ConfigException If an error occurs in the process of
* transforming the configuration entry into an
* object.
*/
protected abstract void setContent(Node tag) throws ConfigException;
/**
* Subclasses implement this method to return an object that represents the
* contents of the configuration entry.
*
* @return See above.
*/
abstract Object getValue();
/**
* Returns the content of the <i>name</i> attribute
* of a configuration entry.
*
* @return The content of the <i>name</i> attribute.
*/
String getName() { return name; }
}