/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and others contributors as indicated
* by the @authors tag. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* (C) 2005-2006, JBoss Inc.
*/
package org.jboss.tools.smooks.templating.model;
import java.util.Properties;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.milyn.xml.DomUtils;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
/**
* Abstract message model builder.
*
* @author <a href="mailto:tom.fennelly@jboss.com">tom.fennelly@jboss.com</a>
*/
public abstract class ModelBuilder {
public static final String NAMESPACE = "http://www.jboss.org/xsd/tools/smooks"; // TODO: Make same as JBT eclipse extension namespace ?? //$NON-NLS-1$
public static final String REQUIRED = "#required"; //$NON-NLS-1$
public static final String OPTIONAL = "#optional"; //$NON-NLS-1$
private static final String HIDDEN_ELEMENT = "hidden"; //$NON-NLS-1$
public static enum ElementType {
simple,
complex
}
private Properties namespaces = new Properties();
/**
* Build the message model.
* @return The message model.
* @throws ModelBuilderException Error building model.
*/
public abstract Document buildModel() throws ModelBuilderException;
/**
* Get the namespace prefix-to-URI mappings for this model.
* @return The namespace prefix-to-URI mappings for this model.
*/
public Properties getNamespaces() {
return namespaces;
}
/**
* Mark a fragment as being hidden.
* <p/>
* When hidden, it is illegal to attempt a mapping onto a fragment.
*
* @param fragment The fragment to be marked as hidden.
*/
public static void hideFragment(Element fragment) {
fragment.setAttributeNS(NAMESPACE, HIDDEN_ELEMENT, "true"); //$NON-NLS-1$
}
/**
* Unmark a fragment as being hidden.
* <p/>
* When hidden, it is illegal to attempt a mapping onto a fragment.
*
* @param fragment The fragment to be unmarked as hidden.
*/
public static void unhideFragment(Element fragment) {
fragment.removeAttributeNS(NAMESPACE, HIDDEN_ELEMENT);
}
/**
* Is the specified node marked as being hidden.
* <p/>
* When hidden, it is illegal to attempt a mapping onto a fragment.
*
* @return True if the node is hidden, otherwise false.
*/
public static boolean isHidden(Node node) {
if(node != null) {
switch(node.getNodeType()) {
case Node.ATTRIBUTE_NODE :
return isHidden(node.getParentNode());
case Node.ELEMENT_NODE :
if(((Element)node).getAttributeNS(NAMESPACE, HIDDEN_ELEMENT).equals("true")) { //$NON-NLS-1$
return true;
}
return isHidden(node.getParentNode());
}
}
return false;
}
/**
* Is the specified node required.
* <p/>
* For an Element node, this means does it have a minOccurs > 0. For an Attr node,
* it means is its value set to "#REQUIRED".
*
* @return True if the node is hidden, otherwise false.
*/
public static boolean isRequired(Node node) {
if(node != null) {
switch(node.getNodeType()) {
case Node.ATTRIBUTE_NODE :
return (REQUIRED.equals(node.getTextContent()) || XMLConstants.XML_NS_URI.equals(node.getNamespaceURI()));
case Node.ELEMENT_NODE :
if(getMinOccurs((Element) node) > 0) {
return true;
}
}
}
return false;
}
/**
* Is the specified node in the reserved namespace.
*
* @return True if the node is in the reserved namespace, otherwise false.
*/
public static boolean isInReservedNamespace(Node node) {
if(node != null) {
return NAMESPACE.equals(node.getNamespaceURI());
}
return false;
}
protected Document createModelInstance() throws ModelBuilderException {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder;
try {
builder = builderFactory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new ModelBuilderException("Error constructing DOM DocumentBuilder.", e); //$NON-NLS-1$
}
return builder.newDocument();
}
public static void setMinMax(Element element, int minOccurs, int maxOccurs) {
element.setAttributeNS(NAMESPACE, "smk:minOccurs", Integer.toString(minOccurs)); //$NON-NLS-1$
element.setAttributeNS(NAMESPACE, "smk:maxOccurs", Integer.toString(maxOccurs)); //$NON-NLS-1$
}
public static Node getParentNode(Node node) {
if(node.getNodeType() == Node.ATTRIBUTE_NODE) {
return ((Attr)node).getOwnerElement();
} else {
return node.getParentNode();
}
}
public static int getMinOccurs(Element element) {
String minOccurs = element.getAttributeNS(NAMESPACE, "minOccurs"); //$NON-NLS-1$
if(minOccurs.equals("")) { //$NON-NLS-1$
return 1;
}
return Integer.parseInt(minOccurs);
}
public static int getMaxOccurs(Element element) {
String maxOccurs = element.getAttributeNS(NAMESPACE, "maxOccurs"); //$NON-NLS-1$
if(maxOccurs.equals("")) { //$NON-NLS-1$
return 1;
}
return Integer.parseInt(maxOccurs);
}
public static boolean isCollection(Element element) {
if(element == null) {
return false;
}
int maxOccurs = getMaxOccurs(element);
return (maxOccurs > 1 || maxOccurs == -1);
}
public static void setElementType(Element element, ElementType type) {
element.setAttributeNS(NAMESPACE, "smk:elementType", type.toString()); //$NON-NLS-1$
}
public static ElementType getElementType(Element element) {
String elementType = element.getAttributeNS(NAMESPACE, "elementType"); //$NON-NLS-1$
if(elementType == null || elementType.length() == 0) {
return null;
}
return ElementType.valueOf(elementType);
}
/**
* Create a compositor Element.
* @param document Owner Document node.
* @return The compositor Element.
*/
public static Element createCompositor(Document document) {
return document.createElementNS(NAMESPACE, "smk:compositor"); //$NON-NLS-1$
}
/**
* Is the supplied DOM node the compositor Element.
* @param node The DOM node to test.
* @return True if the node is the compositor element, otherwise false.
*/
public static boolean isCompositor(Node node) {
if(node.getNodeType() == Node.ELEMENT_NODE) {
if(DomUtils.getName((Element)node).equals("compositor")) { //$NON-NLS-1$
if(NAMESPACE.equals(node.getNamespaceURI())) {
return true;
}
}
}
return false;
}
/**
* Turn on/off enforcement of collection sub-mapping rules.
* <p/>
* If turned on, the collection mapping must be made on the model collection element (having maxOccurs > 1)
* before mappings can be made on sub elements.
*
* @param element The model element.
* @param enforce True if enforcement is to be turned on, otherwise false.
*/
public static void setEnforceCollectionSubMappingRules(Element element, boolean enforce) {
element.setAttributeNS(NAMESPACE, "smk:enforceCollectionSubMappingRules", Boolean.toString(enforce)); //$NON-NLS-1$
}
/**
* Is collection sub-mapping rules turned on for the supplied model element.
* @param element The model element.
* @return True if enforcement is turned on, otherwise false.
* @see #setEnforceCollectionSubMappingRules(Element, boolean)
*/
public static boolean getEnforceCollectionSubMappingRules(Element element) {
String enforce = element.getAttributeNS(NAMESPACE, "enforceCollectionSubMappingRules"); //$NON-NLS-1$
if(enforce == null || enforce.length() == 0) {
return true;
}
return !enforce.equals("false"); //$NON-NLS-1$
}
/**
* Does the model element contain child elements.
* @param element The element to test.
* @return true if the element contains child elements, otherwise false.
*/
public static boolean hasChildElements(Element element) {
NodeList childNodes = element.getChildNodes();
int numChildren = childNodes.getLength();
for(int i = 0; i < numChildren; i++) {
if(childNodes.item(i).getNodeType() == Node.ELEMENT_NODE) {
return true;
}
}
return false;
}
/**
* Does the model element have child content of any kind.
* <p/>
* Does it have child elements or attributes.
*
* @param element The element to test.
* @return true if the element contains child content of any kind, otherwise false.
*/
public static boolean hasChildContent(Element element) {
return (element.hasAttributes() || hasChildElements(element));
}
/**
* Set the model as being strict, or not.
* <p/>
* A strict model should have cardinality info attached e.g. minOccurs/maxOccurs, required etc.
* @param model The model Document node.
* @param strict True if the model is strict, otherwise false.
*/
public static void setStrictModel(Document model, boolean strict) {
Element documentElement = model.getDocumentElement();
if(documentElement == null) {
throw new IllegalStateException("Call to 'markStrictModel' before the model's root element has been added."); //$NON-NLS-1$
}
documentElement.setAttributeNS(NAMESPACE, "smk:strict", Boolean.toString(strict)); //$NON-NLS-1$
}
/**
* Is the model strict, or not.
* <p/>
* A strict model should have cardinality info attached e.g. minOccurs/maxOccurs, required etc.
* @param model The model Document node.
* @return True if the model is strict, otherwise false.
*/
public static boolean isStrictModel(Document model) {
Element documentElement = model.getDocumentElement();
if(documentElement == null) {
throw new IllegalStateException("Call to 'isStrictModel' before the model's root element has been added."); //$NON-NLS-1$
}
String strict = documentElement.getAttributeNS(NAMESPACE, "strict"); //$NON-NLS-1$
return Boolean.parseBoolean(strict);
}
}