/*
* (c) Copyright 2004 by Heng Yuan
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* ITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
package cookxml.core;
import java.io.File;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import javax.xml.parsers.DocumentBuilder;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import cookxml.core.exception.CookXmlException;
import cookxml.core.exception.InvalidInputException;
import cookxml.core.exceptionhandler.DebugExceptionHandler;
import cookxml.core.interfaces.ExceptionHandler;
import cookxml.core.interfaces.StringHook;
import cookxml.core.interfaces.TagLibrary;
import cookxml.core.interfaces.VarLookup;
import cookxml.core.stringhook.ResourceBundleStringHook;
import cookxml.core.util.DocumentElement;
/**
* This class is a general purpose XML configurator.
*
* @author Heng Yuan
* @version $Id: CookXml.java 244 2007-06-07 04:29:13Z coconut $
* @since CookXml 1.0
*/
public class CookXml
{
private static ExceptionHandler s_defaultExceptionHandler = DebugExceptionHandler.getInstance ();
private static boolean s_defaultAccessible = false;
private static ClassLoader s_defaultClassLoader = CookXml.class.getClassLoader ();
/**
* Obtain the default ExceptionHandler for all instances of CookXml.
* @return the default ExceptionHandler for all instances of CookXml
* @see ExceptionHandler
* @since CookXml 2.0
*/
public static ExceptionHandler getDefaultExceptionHandler ()
{
return s_defaultExceptionHandler;
}
/**
* Sets the default ExceptionHandler for all instances of CookXml.
* @param defaultExceptionHandler
* the default ExceptionHandler to be used
* @see ExceptionHandler
* @since CookXml 2.0
*/
public static void setDefaultExceptionHandler (ExceptionHandler defaultExceptionHandler)
{
s_defaultExceptionHandler = defaultExceptionHandler;
}
/**
* Setting this flag to true to allow CookXml to read/write non-public variables
* (including package scope, protected, and private variables) by default.
* <p>
* Note, reading/writing non-public variable should be avoided for Web Start
* applications or applications that run in a sandbox, as such action can
* generate a SecurityException.
* <p>
* Usually, it is rather rare to have frequent access to a single variable.
* So, performance-wise should be okay. If you read/write a particular
* variable more often, consider to cache the object.
*
* @see cookxml.core.exception.AccessException
* @param accessible if CookXml should attempt to access non-public variables
* @since CookXml 2.3
*/
public static void setDefaultAccessible (boolean accessible)
{
s_defaultAccessible = accessible;
}
/**
* Returns if CookXml by default should attempt to access non-public variables.
* @return if CookXml by default should attempt to access non-public variables
* @since CookXml 2.3
*/
public static boolean isDefaultAccessible ()
{
return s_defaultAccessible;
}
/**
* Obtain the default ClassLoader that is used to locate resources.
* @return the default ClassLoader
* @since CookXml 2.3
*/
public static ClassLoader getDefaultClassLoader ()
{
return s_defaultClassLoader;
}
/**
* Set the default ClassLaoder that is used to locate resouces.
* @param defaultClassLoader the default ClassLoader to be used.
* @since CookXml 2.3
*/
public static void setDefaultClassLoader (ClassLoader defaultClassLoader)
{
s_defaultClassLoader = defaultClassLoader;
}
/**
* This function is supposed to work in conjunction with assert to generate
* a debug message but does not really throw an Exception
*
* @param msg the message to be printed.
* @return true
*/
public static boolean debug (String msg)
{
if (msg != null)
System.out.println (msg);
return true;
}
/**
* This function is supposed to work in conjunction with assert to generate
* a debug message but does not really throw an Exception
*
* @param msg the message to be printed.
* @param ex the exception message to be printed.
* @return true
*/
public static boolean debug (String msg, Exception ex)
{
if (msg != null)
System.out.println (msg);
if (ex != null)
ex.printStackTrace (System.out);
return true;
}
/**
* This function is supposed to work in conjunction with assert to generate
* a debug message but does not really throw an Exception
*
* @param ex the exception message to be printed.
* @return true
*/
public static boolean debug (Exception ex)
{
if (ex != null)
ex.printStackTrace (System.out);
return true;
}
private final TagLibrary m_tagLibrary;
private Object m_rootObj;
private DocumentBuilder m_docBuilder;
private VarLookup m_varLookup;
private ResourceBundle m_bundle;
private StringHook m_userhook;
private ExceptionHandler m_exceptionHandler;
private boolean m_accessible = s_defaultAccessible;
private final Map m_idMap = new HashMap ();
private ClassLoader m_classLoader = s_defaultClassLoader;
/**
* constructor.
*
* @param builder the document builder use to parse the xml document.
* @param tagLibrary the tag library to be used.
* @param varObj inside where all the variables are contained.
*/
public CookXml (DocumentBuilder builder, TagLibrary tagLibrary, Object varObj)
{
m_docBuilder = builder;
m_tagLibrary = tagLibrary;
if (varObj != null)
m_varLookup = new DefaultVarLookup (varObj);
}
/**
* constructor.
*
* @param builder the document builder use to parse the xml document.
* @param tagLibrary the tag library to be used.
* @param varLookup a handler for looking up variables
*/
public CookXml (DocumentBuilder builder, TagLibrary tagLibrary, VarLookup varLookup)
{
m_docBuilder = builder;
m_tagLibrary = tagLibrary;
m_varLookup = varLookup;
}
/**
* returns the tag library used by this CookXml object
*
* @return the tag library used by this CookXml object
*/
public TagLibrary getTagLibrary ()
{
return m_tagLibrary;
}
/**
* get the element associated with the id.
*
* @param id the id to be retrieved.
* @return the information associated with the id.
*/
public IdReference getId (String id)
{
return (IdReference)m_idMap.get (id);
}
/**
* Save an element with an id in a map such that this element can be referenced later.
*
* @param id the id to be saved.
* @param tag the tag name.
* @param obj the object associated with the tag.
*/
public void setId (String id, String ns, String tag, Object obj)
{
m_idMap.put (id, new IdReference (id, ns, tag, obj));
}
/**
* Call this function to start decoding the input. The input can be
* a string (which would be looked up using ClassLoader), a File, an InputStream,
* or an InputSource.
*
* @param input the input source
* @return the object constructed.
*/
public Object xmlDecode (Object input) throws CookXmlException
{
return xmlDecode (input, null, null, null);
}
/**
* Call this function to start decoding the input. The input can be
* a string (which would be looked up using ClassLoader), a File, an InputStream,
* or an InputSource.
*
* @param parentTag the assumed parent tag
* @param parentObj the assumed parentObj
* @param input the input source
* @return the object constructed.
*/
public Object xmlDecode (Object input, String parentNS, String parentTag, Object parentObj) throws CookXmlException
{
try
{
DocumentBuilder builder = m_docBuilder;
Document doc = null;
Element elm = null;
if (input instanceof String)
{
InputStream inputStream = m_classLoader.getResourceAsStream ((String)input);
if (inputStream == null)
doc = builder.parse ((String)input);
else
doc = builder.parse (inputStream);
}
else if (input instanceof InputStream)
doc = builder.parse ((InputStream)input);
else if (input instanceof File)
doc = builder.parse ((File)input);
else if (input instanceof InputSource)
doc = builder.parse ((InputSource)input);
else if (input instanceof Document)
doc = (Document)input;
else if (input instanceof DocumentElement)
{
doc = ((DocumentElement)input).doc;
elm = ((DocumentElement)input).element;
}
if (doc == null)
{
getExceptionHandler ().handleException (null, new InvalidInputException (input));
return null;
}
StringHook stringHook = null;
if(m_userhook != null)
stringHook = m_userhook;
else if (m_bundle != null)
stringHook = new ResourceBundleStringHook (m_bundle);
DecodeEngine decodeEngine = DecodeEngine.createDecodeEngine (this, m_varLookup, stringHook);
decodeEngine.setDocument (doc);
if (elm == null)
elm = doc.getDocumentElement ();
Object obj = decodeEngine.decodeElement (parentNS, parentTag, elm, parentObj);
decodeEngine.cleanup ();
return obj;
}
catch (CookXmlException ex)
{
// avoid handling the same exception twice
throw ex;
}
catch (Exception ex)
{
getExceptionHandler ().handleException (null, ex);
}
return null;
}
/**
* Gets the root object associated with the xml document root element.
*
* @return the object to be associated with.
*/
public Object getRootObject ()
{
return m_rootObj;
}
/**
* Sets the root object associated with the xml document root element.
*
* @param rootObj the object to be associated with.
*/
public void setRootObject (Object rootObj)
{
m_rootObj = rootObj;
}
/**
* Gets the DocumentBuilder used by this CookXml object.
*
* @return the DocumentBuilder used by this CookXml object.
*/
public DocumentBuilder getDocumentBuilder ()
{
return m_docBuilder;
}
/**
* sets the DocumentBuilder used by this CookXml object.
*
* @param docBuilder the DocumentBuilder to be used by this CookXml object.
*/
public void setDocumentBuilder (DocumentBuilder docBuilder)
{
m_docBuilder = docBuilder;
}
/**
* Gets the resource bundle used by this CookXml object.
*
* @return the resource bundle used by the xml document.
*/
public ResourceBundle getResourceBundle ()
{
return m_bundle;
}
/**
* Sets the resource bundle to be used by this CookXml object.
*
* @param bundle the resource bundle to be used by the xml document.
*/
public void setResourceBundle (ResourceBundle bundle)
{
m_bundle = bundle;
}
public StringHook getUserStringHook() {
return m_userhook;
}
public void setUserStringHook(StringHook hook) {
m_userhook = hook;
}
/**
* returns the current VarLookup object.
* @return the current VarLookup object.
* @since CookXml 2.0
*/
public VarLookup getVarLookup ()
{
return m_varLookup;
}
/**
* sets the current VarLookup object.
* @param varLookup
* the VarLookup object which is used for variable lookup.
* @since CookXml 2.0
*/
public void setVarLookup (VarLookup varLookup)
{
m_varLookup = varLookup;
}
/**
* returns the current ExceptionHandler.
* @return the current ExceptionHandler.
* @since CookXml 2.0
*/
public ExceptionHandler getExceptionHandler ()
{
ExceptionHandler handler = m_exceptionHandler;
if (handler == null)
return getDefaultExceptionHandler ();
return handler;
}
/**
* sets the current ExceptionHandler.
* @param exceptionHandler
* the handler for exceptions.
* @since CookXml 2.0
*/
public void setExceptionHandler (ExceptionHandler exceptionHandler)
{
m_exceptionHandler = exceptionHandler;
}
/**
* Returns if CookXml should attempt to access non-public variables.
* @return if CookXml should attempt to access non-public variables
* @since CookXml 2.3
*/
public boolean isAccessible ()
{
return m_accessible;
}
/**
* Setting this flag to true to allow this CookXml object to read/write
* non-public variables (including package scope, protected, and private
* variables).
* <p>
* Note, reading/writing non-public variable should be avoided for Web Start
* applications or applications that run in a sandbox, as such action can
* generate a SecurityException.
* <p>
* Usually, it is rather rare to have frequent access to a single variable.
* So, performance-wise should be okay. If you read/write a particular
* variable more often, consider to cache the object.
*
* @see cookxml.core.exception.AccessException
* @param accessible if CookXml should attempt to access non-public variables
* @since CookXml 2.3
*/
public void setAccessible (boolean accessible)
{
m_accessible = accessible;
}
/**
* Obtain the ClassLoader that is used to locate resources.
* @return the default ClassLoader
* @since CookXml 2.4
*/
public ClassLoader getClassLoader ()
{
return m_classLoader;
}
/**
* Set the ClassLaoder that is used to locate resouces.
* @param classLoader the ClassLoader to be used.
* @since CookXml 2.4
*/
public void setClassLoader (ClassLoader classLoader)
{
m_classLoader = classLoader;
}
}