/**
* This file Copyright (c) 2005-2008 Aptana, Inc. This program is
* dual-licensed under both the Aptana Public License and the GNU General
* Public license. You may elect to use one or the other of these licenses.
*
* This program is distributed in the hope that it will be useful, but
* AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or
* NONINFRINGEMENT. Redistribution, except as permitted by whichever of
* the GPL or APL you select, is prohibited.
*
* 1. For the GPL license (GPL), you can redistribute and/or modify this
* program under the terms of the GNU General Public License,
* Version 3, as published by the Free Software Foundation. You should
* have received a copy of the GNU General Public License, Version 3 along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Aptana provides a special exception to allow redistribution of this file
* with certain other free and open source software ("FOSS") code and certain additional terms
* pursuant to Section 7 of the GPL. You may view the exception and these
* terms on the web at http://www.aptana.com/legal/gpl/.
*
* 2. For the Aptana Public License (APL), this program and the
* accompanying materials are made available under the terms of the APL
* v1.0 which accompanies this distribution, and is available at
* http://www.aptana.com/legal/apl/.
*
* You may view the GPL, Aptana's exception and additional terms, and the
* APL in the file titled license.html at the root of the corresponding
* plugin containing this source file.
*
* Any modifications to this file must keep this entire header intact.
*/
package com.aptana.sax;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
/**
* @author Kevin Lindsey
*/
public final class SchemaBuilder extends ValidatingReader
{
private static final String USAGE_ATTRIBUTE = "usage"; //$NON-NLS-1$
private static final String HAS_TEXT_ATTRIBUTE = "hasText"; //$NON-NLS-1$
private static final String ON_EXIT_ATTRIBUTE = "onExit"; //$NON-NLS-1$
private static final String ON_ENTER_ATTRIBUTE = "onEnter"; //$NON-NLS-1$
private static final String TYPE_ATTRIBUTE = "type"; //$NON-NLS-1$
private static final String NAME_ATTRIBUTE = "name"; //$NON-NLS-1$
private static final String USE_ELEMENT_SET_ELEMENT = "use-element-set"; //$NON-NLS-1$
private static final String ELEMENT_SET_ELEMENT = "element-set"; //$NON-NLS-1$
private static final String SETS_ELEMENT = "sets"; //$NON-NLS-1$
private static final String CHILD_ELEMENT_ELEMENT = "child-element"; //$NON-NLS-1$
private static final String ATTRIBUTE_ELEMENT = "attribute"; //$NON-NLS-1$
private static final String OPTIONAL = "optional"; //$NON-NLS-1$
private static final String REQUIRED = "required"; //$NON-NLS-1$
private static final String ELEMENT_ELEMENT = "element"; //$NON-NLS-1$
private static final String SCHEMA_ELEMENT = "schema"; //$NON-NLS-1$
private static String SCHEMA_1_0_NAMESPACE = "http://www.aptana.com/2005/schema/1.0"; //$NON-NLS-1$
private static String SCHEMA_1_1_NAMESPACE = "http://www.aptana.com/2007/schema/1.1"; //$NON-NLS-1$
private static SchemaBuilder _builder = null;
private Schema _newSchema;
private Stack<SchemaElement> _elementStack;
private SchemaElement _currentElement;
private String _currentSetId;
private Map<String,SchemaElement> _sets;
private Schema _versionSelectorSchema;
private Schema _schema10;
private Schema _schema11;
/**
* Create a new instance of SchemaParser
*
* @param schema
* @throws SchemaInitializationException
*/
private SchemaBuilder() throws SchemaInitializationException
{
this._elementStack = new Stack<SchemaElement>();
this._sets = new HashMap<String,SchemaElement>();
try
{
buildSchemaSchemas();
}
catch (SecurityException e)
{
String msg = Messages.SchemaBuilder_Insufficient_Reflection_Security;
SchemaInitializationException ie = new SchemaInitializationException(msg, e);
throw ie;
}
catch (NoSuchMethodException e)
{
String msg = Messages.SchemaBuilder_Missing_Handler_Method;
SchemaInitializationException ie = new SchemaInitializationException(msg, e);
throw ie;
}
}
/**
* Create the state machine that loads and recognizes our schema xml format
*
* @throws NoSuchMethodException
* @throws SecurityException
*/
private void buildSchemaSchemas() throws SecurityException, NoSuchMethodException
{
// create 1.0 schema schema
this._schema10 = this.buildSchema10Schema();
// create 1.1 schema schema
this._schema11 = this.buildSchema11Schema();
// create schema schema to select the proper version schema
this._versionSelectorSchema = this.buildVersionSelectorSchema();
}
/**
* buildVersionSelectorSchema
*
* @return Schema
* @throws SecurityException
* @throws NoSuchMethodException
*/
private Schema buildVersionSelectorSchema() throws SecurityException, NoSuchMethodException
{
Schema result = new Schema(this);
// create single element schema used to detect which schema version to use
// to process the current schema definition file
// create root element
SchemaElement root = result.createElement(SCHEMA_ELEMENT); //$NON-NLS-1$
// tell schema this is the root
result.setRootElement(SCHEMA_ELEMENT); //$NON-NLS-1$
// set schema's onEnter handler
root.setOnEnter("startSchemaElement"); //$NON-NLS-1$
return result;
}
/**
* buildSchema10Schema
*
* @throws SecurityException
* @throws NoSuchMethodException
*/
private Schema buildSchema10Schema() throws SecurityException, NoSuchMethodException
{
Schema result = new Schema(this);
// create the root element
SchemaElement root = result.createElement(SCHEMA_ELEMENT);
// tell schema this is the root
result.setRootElement(SCHEMA_ELEMENT);
// create element element and add as child of root
SchemaElement element = result.createElement(ELEMENT_ELEMENT);
root.addTransition(element);
// set element's attributes
element.addAttribute(NAME_ATTRIBUTE, REQUIRED);
element.addAttribute(TYPE_ATTRIBUTE, OPTIONAL);
element.addAttribute(ON_ENTER_ATTRIBUTE, OPTIONAL);
element.addAttribute(ON_EXIT_ATTRIBUTE, OPTIONAL);
element.addAttribute(HAS_TEXT_ATTRIBUTE, OPTIONAL);
// set element's onEnter and onExit handlers
element.setOnEnter("startElementElement"); //$NON-NLS-1$
element.setOnExit("exitElementElement"); //$NON-NLS-1$
// create attribute element and add as child of element
SchemaElement attribute = result.createElement(ATTRIBUTE_ELEMENT);
element.addTransition(attribute);
// set attribute's attributes
attribute.addAttribute(NAME_ATTRIBUTE, REQUIRED);
attribute.addAttribute(USAGE_ATTRIBUTE, OPTIONAL);
// set attribute element's onEnter handler
attribute.setOnEnter("startAttributeElement"); //$NON-NLS-1$
// create child-element element and add as child of element
SchemaElement childElement = result.createElement(CHILD_ELEMENT_ELEMENT);
element.addTransition(childElement);
// set child-element's attributes
childElement.addAttribute(NAME_ATTRIBUTE, REQUIRED);
// set child-element's onEnter handler
childElement.setOnEnter("startChildElementElement"); //$NON-NLS-1$
return result;
}
/**
* buildSchema10Schema
*
* @throws SecurityException
* @throws NoSuchMethodException
*/
private Schema buildSchema11Schema() throws SecurityException, NoSuchMethodException
{
Schema result = new Schema(this);
// create the root element
SchemaElement root = result.createElement(SCHEMA_ELEMENT);
// tell schema this is the root
result.setRootElement(SCHEMA_ELEMENT);
// create element element and add as child of root
SchemaElement element = result.createElement(ELEMENT_ELEMENT);
root.addTransition(element);
// set element's attributes
element.addAttribute(NAME_ATTRIBUTE, REQUIRED);
element.addAttribute(ON_ENTER_ATTRIBUTE, OPTIONAL);
element.addAttribute(ON_EXIT_ATTRIBUTE, OPTIONAL);
element.addAttribute(HAS_TEXT_ATTRIBUTE, OPTIONAL);
// set element's onEnter and onExit handlers
element.setOnEnter("startElementElement"); //$NON-NLS-1$
element.setOnExit("exitElementElement"); //$NON-NLS-1$
// allow element to transition to self
element.addTransition(element);
// create attribute element and add as child of element
SchemaElement attribute = result.createElement(ATTRIBUTE_ELEMENT);
element.addTransition(attribute);
// set attribute's attributes
attribute.addAttribute(NAME_ATTRIBUTE, REQUIRED);
attribute.addAttribute(USAGE_ATTRIBUTE, OPTIONAL);
// set attribute element's onEnter handler
attribute.setOnEnter("startAttributeElement"); //$NON-NLS-1$
// create sets element and add as child of root
SchemaElement sets = result.createElement(SETS_ELEMENT);
root.addTransition(sets);
// create element-set element and add as child of sets element
SchemaElement elementSet = result.createElement(ELEMENT_SET_ELEMENT);
sets.addTransition(elementSet);
// set elementSet's attributes
elementSet.addAttribute("id", REQUIRED); //$NON-NLS-1$
// set elementSet's onEnter and onExit handlers
elementSet.setOnEnter("startElementSetElement"); //$NON-NLS-1$
elementSet.setOnExit("exitElementSetElement"); //$NON-NLS-1$
// add element as child of element-set
elementSet.addTransition(element);
// create use-element-set element and add as child of root, element, and element-set
SchemaElement useElementSet = result.createElement(USE_ELEMENT_SET_ELEMENT);
root.addTransition(useElementSet);
element.addTransition(useElementSet);
elementSet.addTransition(useElementSet);
// add useElementSet's attributes
useElementSet.addAttribute(NAME_ATTRIBUTE, REQUIRED);
// add useElementSet's onEnter handler
useElementSet.setOnEnter("startUseElementSetElement"); //$NON-NLS-1$
return result;
}
/**
* Finish processing the specified element
*
* @param namespaceURI
* @param localName
* @param qualifiedName
*/
public void exitElementElement(String namespaceURI, String localName, String qualifiedName)
{
// restore parent element as the new current element
this._currentElement = this._elementStack.pop();
}
/**
* Finish processing the specified element-set
*
* @param namespaceURI
* @param localName
* @param qualifiedName
*/
public void exitElementSetElement(String namespaceURI, String localName, String qualifiedName)
{
// save set
this._sets.put(this._currentSetId, this._currentElement);
// clear name
this._currentSetId = null;
// restore parent element as the new current element
this._currentElement = this._elementStack.pop();
}
/**
* Load an xml schema that describes and recognizes a specific xml format
*
* @param filename
* The name of the xml schema file to load
* @param handler
* The handler to use for event callbacks
* @return A validating XML reader that will recognize and validate against the loaded schema
* @throws SchemaInitializationException
*/
public static Schema fromXML(String filename, Object handler) throws SchemaInitializationException
{
FileInputStream fi = null;
Schema schema = null;
try
{
fi = new FileInputStream(filename);
schema = fromXML(fi, handler);
}
catch (FileNotFoundException e)
{
String msg = Messages.SchemaBuilder_File_Unlocatable + filename;
SchemaInitializationException ie = new SchemaInitializationException(msg, e);
throw ie;
}
finally
{
try
{
fi.close();
}
catch (IOException e)
{
}
}
return schema;
}
/**
* Load an xml schema that describes and recognizes a specific xml format
*
* @param in
* The input stream of xml schema data
* @param handler
* The handler to use for event callbacks
* @return A validating XML reader that will recognize and validate against the loaded schema
* @throws SchemaInitializationException
*/
public static Schema fromXML(InputStream in, Object handler) throws SchemaInitializationException
{
Schema result = new Schema(handler);
if (_builder == null)
{
_builder = new SchemaBuilder();
}
// setup selector schema and reset in case it has been used previously
_builder._schema = _builder._versionSelectorSchema;
_builder._schema.reset();
// create new schema and associate with resulting reader
_builder._newSchema = result;
// build new schema from XML description
try
{
_builder.read(in);
}
catch (ParserConfigurationException e)
{
String msg = Messages.SchemaBuilder_SAX_Parser_Initialization_Error;
SchemaInitializationException ie = new SchemaInitializationException(msg, e);
throw ie;
}
catch (SAXException e)
{
String msg = Messages.SchemaBuilder_SAX_Parser_Error;
SchemaInitializationException ie = new SchemaInitializationException(msg, e);
throw ie;
}
catch (IOException e)
{
String msg = Messages.SchemaBuilder_IO_Error;
SchemaInitializationException ie = new SchemaInitializationException(msg, e);
throw ie;
}
// return results
return result;
}
/**
* Process an <attribute>
*
* @param namespaceURI
* @param localName
* @param qualifiedName
* @param attributes
*/
public void startAttributeElement(String namespaceURI, String localName, String qualifiedName, Attributes attributes)
{
String name = attributes.getValue(NAME_ATTRIBUTE); //$NON-NLS-1$
String usage = attributes.getValue(USAGE_ATTRIBUTE); //$NON-NLS-1$
this._currentElement.addAttribute(name, usage);
}
/**
* Process a <child-element>
*
* @param namespaceURI
* @param localName
* @param qualifiedName
* @param attributes
*/
public void startChildElementElement(String namespaceURI, String localName, String qualifiedName,
Attributes attributes)
{
// get target element's name
String elementName = attributes.getValue(NAME_ATTRIBUTE); //$NON-NLS-1$
// create a new SchemaElement for our target element
SchemaElement element = this._newSchema.createElement(elementName);
this._currentElement.addTransition(element);
}
/**
* startDocument handler
*
* @throws SAXException
*/
public void startDocument() throws SAXException
{
super.startDocument();
// reset the stack
this._elementStack.clear();
// set the root element as our current element
this._currentElement = this._newSchema.getRootElement();
}
/**
* Start processing a element element
*
* @param namespaceURI
* @param localName
* @param qualifiedName
* @param attributes
* @throws SAXException
*/
public void startElementElement(String namespaceURI, String localName, String qualifiedName, Attributes attributes)
throws SAXException
{
// get target element's name and type
String elementName = attributes.getValue(NAME_ATTRIBUTE); //$NON-NLS-1$
String elementType = attributes.getValue(TYPE_ATTRIBUTE); //$NON-NLS-1$
String onEnter = attributes.getValue(ON_ENTER_ATTRIBUTE); //$NON-NLS-1$
String onExit = attributes.getValue(ON_EXIT_ATTRIBUTE); //$NON-NLS-1$
String hasText = attributes.getValue(HAS_TEXT_ATTRIBUTE); //$NON-NLS-1$
// create a new SchemaElement for our target element
SchemaElement element = this._newSchema.createElement(elementName);
// tag as root node, if needed
if (SCHEMA_1_0_NAMESPACE.equals(namespaceURI))
{
if (elementType != null && elementType.equals("root")) //$NON-NLS-1$
{
// add new target element as a transition from the current element
this._currentElement.addTransition(element);
}
}
else
{
// assuming 1.1
// add new target element as a transition from the current element
this._currentElement.addTransition(element);
}
// set onEnter, if defined
if (onEnter != null && onEnter.length() > 0)
{
try
{
element.setOnEnter(onEnter);
}
catch (SecurityException e)
{
String message = Messages.SchemaBuilder_Unable_To_Get_OnEnter_Method + onEnter;
throw new SAXException(message, e);
}
catch (NoSuchMethodException e)
{
String message = Messages.SchemaBuilder_Unable_To_Locate_OnEnter_Method + onEnter;
throw new SAXException(message, e);
}
}
// set onExit, if defined
if (onExit != null && onExit.length() > 0)
{
try
{
element.setOnExit(onExit);
}
catch (SecurityException e)
{
String message = Messages.SchemaBuilder_Unable_To_Get_OnExit_Method + onExit;
throw new SAXException(message, e);
}
catch (NoSuchMethodException e)
{
String message = Messages.SchemaBuilder_Unable_To_Locate_OnExit_Method + onExit;
throw new SAXException(message, e);
}
}
// set hasText, if defined
if (hasText != null && hasText.length() > 0)
{
String lowerHasText = hasText.toLowerCase();
boolean hasTextValue = (lowerHasText.equals("true") || lowerHasText.equals("yes")); //$NON-NLS-1$ //$NON-NLS-2$
element.setHasText(hasTextValue);
}
// save current element on the stack
this._elementStack.push(this._currentElement);
// set our new element as the new current element
this._currentElement = element;
}
/**
* Start processing a element-set element
*
* @param namespaceURI
* @param localName
* @param qualifiedName
* @param attributes
* @throws SAXException
*/
public void startElementSetElement(String namespaceURI, String localName, String qualifiedName, Attributes attributes) throws SAXException
{
// create a new SchemaElement for our target element
SchemaElement set = this._newSchema.createElement(localName, false);
// get id
String id = attributes.getValue("id"); //$NON-NLS-1$
// save set name for later
this._currentSetId = id;
// save current element on the stack
this._elementStack.push(this._currentElement);
// set our new element as the new current element
this._currentElement = set;
}
/**
* Start processing a schema element
*
* @param namespaceURI
* @param localName
* @param qualifiedName
* @param attributes
* @throws SAXException
*/
public void startSchemaElement(String namespaceURI, String localName, String qualifiedName, Attributes attributes)
throws SAXException
{
if (SCHEMA_1_0_NAMESPACE.equals(namespaceURI))
{
this._schema = this._schema10;
}
else if (SCHEMA_1_1_NAMESPACE.equals(namespaceURI))
{
this._schema = this._schema11;
}
else
{
String message = Messages.SchemaBuilder_Unknown_Schema_Namespace + namespaceURI;
throw new SAXException(message);
}
try
{
this._schema.reset();
this._schema.moveTo(namespaceURI, localName, qualifiedName, attributes);
}
catch (IllegalArgumentException e)
{
throw new SAXException(e);
}
catch (InvalidTransitionException e)
{
throw new SAXException(e);
}
catch (IllegalAccessException e)
{
throw new SAXException(e);
}
catch (InvocationTargetException e)
{
throw new SAXException(e);
}
}
/**
* Start processing a schema element
*
* @param namespaceURI
* @param localName
* @param qualifiedName
* @param attributes
* @throws SAXException
*/
public void startUseElementSetElement(String namespaceURI, String localName, String qualifiedName, Attributes attributes) throws SAXException
{
String name = attributes.getValue(NAME_ATTRIBUTE); //$NON-NLS-1$
String id = name.substring(1);
this.addSetToElement(id, this._currentElement);
}
/**
* addSetToElement
*
* @param id
* The name of the element to add
* @param element
* The element to which the set children will be added
*/
private void addSetToElement(String id, SchemaElement element)
{
if (this._sets.containsKey(id))
{
SchemaElement set = this._sets.get(id);
SchemaElement[] children = set.getTransitionElements();
for (int i = 0; i < children.length; i++)
{
SchemaElement child = children[i];
this._currentElement.addTransition(child);
}
}
else
{
throw new IllegalArgumentException(Messages.SchemaBuilder_Set_ID_Not_Defined + id);
}
}
}