/** * 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.lang.reflect.Method; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import com.aptana.ide.io.SourceWriter; /** * @author Kevin Lindsey */ public class SchemaElement { private static final Class<?>[] enterSignature = new Class[] { String.class, String.class, String.class, Attributes.class }; private static final Class<?>[] exitSignature = new Class[] { String.class, String.class, String.class }; private String _name; private Schema _owningSchema; private Map<String,SchemaElement> _transitions; private Map<String,Integer> _attributes; private List<String> _requiredAttributes; private String _instanceAttributes; private Method _onEnter; private Method _onExit; private boolean _hasText; /** * Create a new instance of SchemaNode * * @param owningSchema * The schema that owns this element * @param name * The name of this node */ public SchemaElement(Schema owningSchema, String name) { // make sure we have a valid schema reference if (owningSchema == null) { throw new IllegalArgumentException(Messages.SchemaElement_Undefined_Owning_Schema); } // make sure we have a valid name if (name == null || name.length() == 0) { throw new IllegalArgumentException(Messages.SchemaElement_Undefined_Name); } this._owningSchema = owningSchema; this._name = name; this._transitions = new HashMap<String,SchemaElement>(); this._attributes = new HashMap<String,Integer>(); this._requiredAttributes = new ArrayList<String>(); } /** * Get the name associated with this Schema node * * @return this node's name */ public String getName() { return this._name; } /** * Return the Method to call when entering this element * * @return The Method to invoke. This value can be null if there is no OnEnter event handler associated with this * element */ public Method getOnEnterMethod() { return this._onEnter; } /** * Set a flag indicating whether this element expects text as a child node * * @param value */ public void setHasText(boolean value) { this._hasText = value; } /** * Set the method to call after entering this element * * @param onEnterMethod * The name of the method to call on the schema's handler object when we enter this element * @throws NoSuchMethodException * @throws SecurityException */ public void setOnEnter(String onEnterMethod) throws SecurityException, NoSuchMethodException { Class<?> handlerClass = this._owningSchema.getHandlerClass(); if (handlerClass != null) { // this._onEnter = handlerClass.getDeclaredMethod(onEnterMethod, // enterSignature); this._onEnter = handlerClass.getMethod(onEnterMethod, enterSignature); } else { this._onEnter = null; } } /** * Return the Method to call when exiting this element * * @return The Method to invoke. This value can be null if there is no OnExit event handler associated with this * element */ public Method getOnExitMethod() { return this._onExit; } /** * Set the method to call before exiting this element * * @param onExitMethod * The name of the method to call on the schema's handler object when we exit this element * @throws NoSuchMethodException * @throws SecurityException */ public void setOnExit(String onExitMethod) throws SecurityException, NoSuchMethodException { Class<?> handlerClass = this._owningSchema.getHandlerClass(); if (handlerClass != null) { // this._onExit = handlerClass.getDeclaredMethod(onExitMethod, // exitSignature); this._onExit = handlerClass.getMethod(onExitMethod, exitSignature); } else { this._onExit = null; } } /** * getTransitionElements * * @return Returns an array of schema elements to which this element can transition. */ public SchemaElement[] getTransitionElements() { Collection<SchemaElement> values = this._transitions.values(); return values.toArray(new SchemaElement[values.size()]); } /** * Determine if this element has a definition for the specified attribute name * * @param name * The name of the attribute to test * @return Returns true if this element has an entry for the specified attribute name */ public boolean hasAttribute(String name) { return (this._attributes.containsKey(name)); } /** * Determine if this element has an associated OnEnter handler * * @return Returns true if this element has an OnEnter handler */ public boolean hasOnEnterMethod() { return (this._onEnter != null); } /** * Determine if this element has an associated OnExit handler * * @return Returns true if this element has an OnExit handler */ public boolean hasOnExitMethod() { return (this._onExit != null); } /** * Determine if this element expects text as a child node or not * * @return Returns true if this element expects to contain text */ public boolean hasText() { return this._hasText; } /** * hasTransitions * * @return Returns true if this element has transitions */ public boolean hasTransitions() { return (this._transitions.size() > 0); } /** * Determine if the specified attribute name is optional on this element * * @param name * The name of the attribute to test * @return Returns true if the specified attribute name does not have to exist on this element */ public boolean isDeprecatedAttribute(String name) { boolean result = false; if (this.isValidAttribute(name)) { int flags = this._attributes.get(name).intValue(); result = ((flags & AttributeUsage.DEPRECATED) == AttributeUsage.DEPRECATED); } return result; } /** * Determine if the specified attribute name is optional on this element * * @param name * The name of the attribute to test * @return Returns true if the specified attribute name does not have to exist on this element */ public boolean isOptionalAttribute(String name) { boolean result = false; if (this.isValidAttribute(name)) { int flags = this._attributes.get(name).intValue(); result = ((flags & AttributeUsage.USAGE_MASK) == AttributeUsage.OPTIONAL); } return result; } /** * Determine if the specified attribute name is required on this element * * @param name * The name of the attribute to test * @return Returns true if the specified attribute name must exist on this element */ public boolean isRequiredAttribute(String name) { boolean result = false; if (this.isValidAttribute(name)) { int flags = this._attributes.get(name).intValue(); result = ((flags & AttributeUsage.USAGE_MASK) == AttributeUsage.REQUIRED); } return result; } /** * Determine if the specified attribute name is allowed on this element * * @param name * The name of the attribute to test * @return Returns true if the specified attribute name is allowed on this element */ public boolean isValidAttribute(String name) { return (this._attributes.containsKey(name)); } /** * Determine if this node can transition to another node using the given name * * @param name * The name of the node to test as a possible transition target * @return Returns true if this node can transition to the given node name */ public boolean isValidTransition(String name) { return this._transitions.containsKey(name); } /** * Add an attribute to this element * * @param name * The name of the attribute * @param usage * The usage requirements for the attribute */ public void addAttribute(String name, String usage) { // make sure we have a valid name if (name == null || name.length() == 0) { throw new IllegalArgumentException(Messages.SchemaElement_Undefined_Name); } // make sure we haven't defined this attribute already if (this.hasAttribute(name)) { String msg = MessageFormat.format(Messages.SchemaElement_Attribute_already_defined, name, this._name); throw new IllegalArgumentException(msg); } int usageValue; if (usage != null) { if (usage.equals("required")) //$NON-NLS-1$ { usageValue = AttributeUsage.REQUIRED; } else if (usage.equals("optional")) //$NON-NLS-1$ { usageValue = AttributeUsage.OPTIONAL; } else { String msg = MessageFormat.format(Messages.SchemaElement_Not_valid_usage_attribute, usage); throw new IllegalArgumentException(msg); } } else { usageValue = AttributeUsage.REQUIRED; } // store attribute and attribute usage this._attributes.put(name, new Integer(usageValue)); // add required attributes to array list for easier testing if ((usageValue & AttributeUsage.USAGE_MASK) == AttributeUsage.REQUIRED) { this._requiredAttributes.add(name); } } /** * Add a transition out of this node to another node * * @param node * The node to which this node can transition */ public void addTransition(SchemaElement node) { // make sure we have a valid object if (node == null) { throw new IllegalArgumentException(Messages.SchemaElement_Undefined_Node); } // get the new node's name String nodeName = node.getName(); // make sure we haven't added this name already if (this.isValidTransition(nodeName)) { String msg = "A node name '" + nodeName + "' has already been added to " + this._name; //$NON-NLS-1$ //$NON-NLS-2$ throw new IllegalArgumentException(msg); } // add a transition to the new node this._transitions.put(nodeName, node); } /** * Get the named SchemaElement that transitions from this element * * @param name * The name of the SchemaElement to transition to * @return The new SchemaElement */ public SchemaElement moveTo(String name) { return this._transitions.get(name); } /** * Validate the list of attributes against this element's definition. Required attributes must exist and no * attributes can be in the list that have not been defined for this element. * * @param attributes * The list of attributes to test * @throws SAXException */ public void validateAttributes(Attributes attributes) throws SAXException { // save attributes for possible error messaging if (attributes.getLength() > 0) { this._instanceAttributes = ""; //$NON-NLS-1$ for (int i = 0; i < attributes.getLength(); i++) { String key = attributes.getLocalName(i); String value = attributes.getValue(i); this._instanceAttributes += " " + key + "=\"" + value + "\""; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } // make sure all required attributes are in the list for (int i = 0; i < this._requiredAttributes.size(); i++) { String name = this._requiredAttributes.get(i); String value = attributes.getValue(name); if (value == null) { SourceWriter writer = new SourceWriter(); writer.print("<").print(this._name).print("> requires a '").print(name).println("' attribute"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ this._owningSchema.buildErrorMessage(writer, this._name, attributes); throw new SAXException(writer.toString()); } } // make sure all attributes are allowed on this element for (int i = 0; i < attributes.getLength(); i++) { String name = attributes.getLocalName(i); if (this._attributes.containsKey(name) == false) { SourceWriter writer = new SourceWriter(); writer.println(MessageFormat.format(Messages.SchemaElement_Invalid_attribute_on_tag, name, this._name)); this._owningSchema.buildErrorMessage(writer, this._name, attributes); throw new SAXException(writer.toString()); } } } /** * @see java.lang.Object#toString() */ public String toString() { String result = "<" + this._name; //$NON-NLS-1$ if (this._instanceAttributes != null) { result += this._instanceAttributes; } if (this.hasTransitions()) { result += ">"; //$NON-NLS-1$ } else { result += "/>"; //$NON-NLS-1$ } return result; } }