/*
* @(#)NanoXMLDOMInput.java
*
* Copyright (c) 1996-2010 The authors and contributors of JHotDraw.
* You may not use, copy or modify this file, except in compliance with the
* accompanying license terms.
*/
package org.jhotdraw.xml;
import javax.annotation.Nullable;
import java.util.*;
import java.io.*;
import net.n3.nanoxml.*;
import org.jhotdraw.app.Disposable;
/**
* NanoXMLDOMInput.
* <p>
* Design pattern:<br>
* Name: Adapter.<br>
* Role: Adapter.<br>
* Partners: {@link net.n3.nanoxml.XMLElement} as Adaptee.
*
* @author Werner Randelshofer
* @version $Id$
*/
public class NanoXMLDOMInput implements DOMInput, Disposable {
/**
* This map is used to unmarshall references to objects to
* the XML DOM. A key in this map is a String representing a marshalled
* reference. A value in this map is an unmarshalled Object.
*/
private HashMap<String,Object> idobjects = new HashMap<String,Object>();
/**
* The document used for input.
*/
@Nullable private XMLElement document;
/**
* The current node used for input.
*/
private IXMLElement current;
/**
* The factory used to create objects from XML tag names.
*/
private DOMFactory factory;
/**
* The stack.
*/
private Stack<IXMLElement> stack = new Stack<IXMLElement>();
public NanoXMLDOMInput(DOMFactory factory, InputStream in) throws IOException {
this(factory, new InputStreamReader(in, "UTF8"));
}
public NanoXMLDOMInput(DOMFactory factory, Reader in) throws IOException {
this.factory = factory;
try {
IXMLParser parser = XMLParserFactory.createDefaultXMLParser();
IXMLReader reader = new StdXMLReader(in);
parser.setReader(reader);
document = new XMLElement();
current = (XMLElement) parser.parse();
if (current != null) {
document.addChild(current);
}
current = document;
} catch (Exception e) {
IOException error = new IOException(e.getMessage());
error.initCause(e);
throw error;
}
}
/**
* Returns the tag name of the current element.
*/
@Override
public String getTagName() {
return current.getName();
}
/**
* Gets an attribute of the current element of the DOM Document.
*/
@Override
public String getAttribute(String name, String defaultValue) {
return current.getAttribute(name, defaultValue);
}
/**
* Gets an attribute of the current element of the DOM Document and of
* all parent DOM elements.
*/
@Override
public java.util.List<String> getInheritedAttribute(String name) {
LinkedList<String> values = new LinkedList<String>();
for (IXMLElement node: stack) {
String value = node.getAttribute(name,null);
values.add(value);
}
String value = current.getAttribute(name,null);
values.add(value);
return values;
}
/**
* Gets the text of the current element of the DOM Document.
*/
@Override
public String getText() {
return getText(null);
}
/**
* Gets the text of the current element of the DOM Document.
*/
@Override
public String getText(String defaultValue) {
String value = current.getContent();
return (value == null) ? defaultValue : value;
}
/**
* Gets an attribute of the current element of the DOM Document.
*/
@Override
public int getAttribute(String name, int defaultValue) {
String value = current.getAttribute(name, null);
return (value == null || value.length() == 0) ? defaultValue : Long.decode(value).intValue();
}
/**
* Gets an attribute of the current element of the DOM Document.
*/
@Override
public double getAttribute(String name, double defaultValue) {
String value = current.getAttribute(name, null);
return (value == null || value.length() == 0) ? defaultValue : Double.parseDouble(value);
}
/**
* Gets an attribute of the current element of the DOM Document.
*/
@Override
public boolean getAttribute(String name, boolean defaultValue) {
String value = current.getAttribute(name, null);
return (value == null || value.length() == 0) ? defaultValue : Boolean.valueOf(value).booleanValue();
}
/**
* Returns the number of child elements of the current element.
*/
@Override
public int getElementCount() {
return current.getChildrenCount();
}
/**
* Returns the number of child elements with the specified tag name
* of the current element.
*/
@Override
public int getElementCount(String tagName) {
int count = 0;
ArrayList<IXMLElement> list = current.getChildren();
for (IXMLElement node: list) {
if (node.getName().equals(tagName)) {
count++;
}
}
return count;
}
/**
* Opens the element with the specified index and makes it the current node.
*/
@Override
public void openElement(int index) {
stack.push(current);
ArrayList<IXMLElement> list = current.getChildren();
current = list.get(index);
}
/**
* Opens the last element with the specified name and makes it the current node.
*/
@Override
public void openElement(String tagName) throws IOException {
ArrayList<IXMLElement> list = current.getChildren();
for (IXMLElement node :list) {
if (node.getName().equals(tagName)) {
stack.push(current);
current = node;
return;
}
}
throw new IOException("no such element:"+tagName);
}
/**
* Opens the element with the specified name and index and makes it the
* current node.
*/
@Override
public void openElement(String tagName, int index) throws IOException {
int count = 0;
ArrayList<IXMLElement> list = current.getChildren();
for (IXMLElement node : list) {
if (node.getName().equals(tagName)) {
if (count++ == index) {
stack.push(current);
current = node;
return;
}
}
}
throw new IOException("no such element:"+tagName+" at index:"+index);
}
/**
* Closes the current element of the DOM Document.
* The parent of the current element becomes the current element.
* @exception IllegalArgumentException if the provided tagName does
* not match the tag name of the element.
*/
@Override
public void closeElement() {
current = (XMLElement) stack.pop();
}
/**
* Reads an object from the current element.
*/
@Override
public Object readObject() throws IOException {
return readObject(0);
}
/**
* Reads an object from the current element.
*/
@Override
public Object readObject(int index) throws IOException {
openElement(index);
Object o;
String ref = getAttribute("ref", null);
String id = getAttribute("id", null);
if (ref != null && id != null) {
throw new IOException("Element has both an id and a ref attribute: <" + getTagName() + " id=\"" + id + "\" ref=\"" + ref + "\"> in line number "+current.getLineNr());
}
if (id != null && idobjects.containsKey(id)) {
throw new IOException("Duplicate id attribute: <" + getTagName() + " id=\"" + id + "\"> in line number "+current.getLineNr());
}
if (ref != null && !idobjects.containsKey(ref)) {
throw new IOException("Referenced element not found: <" + getTagName() + " ref=\"" + ref + "\"> in line number "+current.getLineNr());
}
// Keep track of objects which have an ID
if (ref != null) {
o = idobjects.get(ref);
} else {
o = factory.read(this);
if (id != null) {
idobjects.put(id, o);
}
if (o instanceof DOMStorable) {
((DOMStorable) o).read(this);
}
}
closeElement();
return o;
}
@Override
public void dispose() {
if (document != null) {
document.dispose();
document = null;
}
}
}