/* * Copyright 2008 Lockheed Martin Corporation, except as stated in the file * entitled Licensing-Information. All modifications copyright 2009 Data Access Technologies, Inc. Licensed under the Academic Free License * version 3.0 (http://www.opensource.org/licenses/afl-3.0.php), except as stated * in the file entitled Licensing-Information. * * Contributors: * MDS - initial API and implementation * */ package org.modeldriven.fuml.xmi.stream; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.xml.namespace.QName; import javax.xml.stream.Location; import javax.xml.stream.events.Attribute; import javax.xml.stream.events.XMLEvent; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.modeldriven.fuml.config.FumlConfiguration; import org.modeldriven.fuml.config.ImportExemption; import org.modeldriven.fuml.config.ImportExemptionType; import org.modeldriven.fuml.config.NamespaceDomain; import org.modeldriven.fuml.xmi.XmiConstants; import org.modeldriven.fuml.xmi.XmiNode; import org.modeldriven.fuml.xmi.XmiNodeVisitor; import org.modeldriven.fuml.xmi.XmiNodeVisitorStatus; /** * Encapsulates a set of related XML (stream) events. Stream events are objects * allocated by the StAX parser from the current stream and include element and * attribute as well as namespace and location information. The primary event * encapsulated is always a start-element event, but one of more 'characters' * events may be added if character data for the element is encountered in the * stream. * @author Scott Cinnamond */ public class StreamNode implements XmiNode { private static Log log = LogFactory.getLog(StreamNode.class); private XMLEvent startElementEvent; private List<XMLEvent> characterEvents; private StreamContext context; private Location location; private List<XmiNode> nodes; // TODO: do we really want interfaces in this list? private XmiNode parent; // TODO: same issue @SuppressWarnings("unused") private StreamNode() {} public StreamNode(XMLEvent startElement, XmiNode parent, StreamContext context) { this.startElementEvent = startElement; this.parent = parent; this.context = context; // The Sun StAX impl "loses" element location info, seemingly // immediately after it's read into an event. We just copy it // off here. Tried the default event allocator in both // the Sun RI and the associated utilities. this.location = new StreamLocation(startElement.getLocation()); if (log.isDebugEnabled()) log.debug("created '" + this.getLocation() + "' node (" + this.getXmiId() + ")"); } public StreamNode(XMLEvent startElement, StreamContext context) { this(startElement, null, context); } public Location getLocation() { return location; } public String getLocalName() { return startElementEvent.asStartElement().getName().getLocalPart(); } public String getNamespaceURI() { QName name = startElementEvent.asStartElement().getName(); String uri = name.getNamespaceURI(); if (uri == null || uri.trim().length() == 0) return this.context.getDefaultNamespace().getNamespaceURI(); else return name.getNamespaceURI(); } public String getPrefix() { QName name = startElementEvent.asStartElement().getName(); String uri = name.getNamespaceURI(); if (uri == null || uri.trim().length() == 0) return this.context.getDefaultNamespace().getPrefix(); else return name.getPrefix(); } public String getData() { if (this.characterEvents != null) { StringBuffer buf = new StringBuffer(); Iterator<XMLEvent> events = this.characterEvents.iterator(); while (events.hasNext()) buf.append(events.next().asCharacters().getData()); return buf.toString(); } return null; } public String getXmiType() { QName typeAttrib = new QName(context.getXmiNamespace().getNamespaceURI(), XmiConstants.ATTRIBUTE_XMI_TYPE); Attribute attrib = startElementEvent.asStartElement().getAttributeByName(typeAttrib); if (attrib != null) { String value = attrib.getValue(); return value.substring(context.getUmlNamespace().getPrefix().length() + 1); // prefix + ':' } return null; } public boolean hasAttribute(QName name) { Attribute attrib = startElementEvent.asStartElement().getAttributeByName(name); return (attrib != null); } @SuppressWarnings("unchecked") public boolean hasAttributes() { if (startElementEvent != null) { Iterator<Attribute> attributes = startElementEvent.asStartElement().getAttributes(); return attributes.hasNext(); } return false; } @SuppressWarnings("unchecked") public Iterator<Attribute> getAttributes() { if (startElementEvent != null) { Iterator<Attribute> attributes = startElementEvent.asStartElement().getAttributes(); return attributes; } return null; } public Attribute getAttribute(QName name) { if (startElementEvent != null) { Attribute attrib = startElementEvent.asStartElement().getAttributeByName(name); return attrib; } return null; } public Attribute getAttribute(String name) { if (startElementEvent != null) { Attribute attrib = startElementEvent.asStartElement().getAttributeByName( new QName(name)); return attrib; } return null; } public String getAttributeValue(QName name) { if (startElementEvent != null) { Attribute attrib = startElementEvent.asStartElement().getAttributeByName(name); if (attrib != null) return attrib.getValue(); } return null; } public String getAttributeValue(String name) { if (startElementEvent != null) { Attribute attrib = startElementEvent.asStartElement().getAttributeByName( new QName(name)); if (attrib != null) return attrib.getValue(); } return null; } public boolean hasXmiType() { String xmiType = this.getXmiType(); return xmiType != null && xmiType.length() > 0; } public String getXmiId() { QName typeAttrib = new QName(context.getXmiNamespace().getNamespaceURI(), XmiConstants.ATTRIBUTE_XMI_ID); Attribute attrib = startElementEvent.asStartElement().getAttributeByName(typeAttrib); if (attrib != null) { String value = attrib.getValue(); return value; } return null; } public int getLineNumber() { return this.location.getLineNumber(); } public int getColumnNumber() { return this.location.getColumnNumber(); } public void add(StreamNode node) { if (nodes == null) nodes = new ArrayList<XmiNode>(); nodes.add(node); if (log.isDebugEnabled()) log.debug("added '" + node.getLocalName() + "' (" + node.getXmiId() + ") child node to " + this.getLocation() + "' parent node (" + this.getXmiId() + ")"); } public XmiNode getParent() { return parent; } public void setParent(XmiNode parent) { this.parent = parent; } public List<XmiNode> getNodes() { return nodes; } public boolean hasNodes() { return nodes != null; } public XmiNode findChildByName(String name) { if (this.nodes != null) { Iterator<XmiNode> iter = this.nodes.iterator(); while (iter.hasNext()) { XmiNode child = iter.next(); if (child.getLocalName().equals(name)) { return child; } } } return null; } public XmiNode findChildById(String id) { if (this.nodes != null) { Iterator<XmiNode> iter = this.nodes.iterator(); while (iter.hasNext()) { XmiNode child = iter.next(); if (child.getXmiId().equals(id)) { return child; } } } return null; } public boolean removeChild(XmiNode child) { boolean removed = this.nodes.remove(child); if (log.isDebugEnabled()) log.debug("removed '" + child.getLocalName() + "' (" + child.getXmiId() + ") child node to " + this.getLocation() + "' parent node (" + this.getXmiId() + ")"); return removed; } public XMLEvent getStartElementEvent() { return startElementEvent; } public void accept(XmiNodeVisitor visitor) { if (log.isDebugEnabled()) log.debug(this.getClass().getSimpleName()); accept(visitor, this, null, null, this, new HashMap<XmiNode, XmiNode>(), new XmiNodeVisitorStatus(), 0); } private void accept(XmiNodeVisitor visitor, XmiNode target, XmiNode source, String sourceKey, XmiNode root, Map<XmiNode, XmiNode> visited, XmiNodeVisitorStatus status, int level) { if (log.isDebugEnabled()) log.debug("accept: " + target.getLocalName()); if (visited.get(target) == null) visited.put(target, target); else { if (log.isDebugEnabled()) log.debug("ignoring, " + target.getClass().getName()); return; } visitor.visit(target, source, sourceKey, status, level); if (status.getStatus() == XmiNodeVisitorStatus.STATUS_ABORT) return; List<XmiNode> nodes = target.getNodes(); if (nodes != null) for (int i = 0; i < nodes.size(); i++) { XmiNode child = nodes.get(i); if (((StreamNode)child).isIgnored()) { if (log.isDebugEnabled()) log.debug("ignoring XMI 'Extension' node"); continue; // bag these and their children for now } if (log.isDebugEnabled()) log.debug("processing node: " + child.getLocalName()); accept(visitor, child, target, child.getLocalName(), root, visited, status, level++); } } public StreamContext getContext() { return context; } public void addCharactersEvent(XMLEvent charactersEvent) { if (this.characterEvents == null) this.characterEvents = new ArrayList<XMLEvent>(); this.characterEvents.add(charactersEvent); } public boolean hasCharacters() { return this.characterEvents != null && characterEvents.size() > 0; } public boolean isIgnored() { if (startElementEvent == null) return false; // can't ignore character nodes String uri = startElementEvent.asStartElement().getName().getNamespaceURI(); if (uri == null || uri.length() == 0) { uri = this.context.getDefaultNamespace().getNamespaceURI(); } ImportExemption importExemption = FumlConfiguration.getInstance().findImportExemptionByElement(this.getLocalName()); if (importExemption != null) { NamespaceDomain domain = FumlConfiguration.getInstance().getNamespaceDomain(uri); if (importExemption.getType().ordinal() == ImportExemptionType.ELEMENT.ordinal() && importExemption.getDomain().ordinal() == domain.ordinal()) return true; } return false; } }