com.thoughtworks.xstream.io.StatefulWriter Maven / Gradle / Ivy
/*
* Copyright (C) 2006 Joe Walnes.
* Copyright (C) 2006, 2007 XStream Committers.
* All rights reserved.
*
* The software in this package is published under the terms of the BSD
* style license a copy of which has been included with this distribution in
* the LICENSE.txt file.
*
* Created on 15. March 2006 by Joerg Schaible
*/
package com.thoughtworks.xstream.io;
import com.thoughtworks.xstream.core.util.FastStack;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
/**
* An wrapper for all {@link HierarchicalStreamWriter} implementations, that keeps the state.
* Writing in a wrong state will throw a {@link StreamException}, that wraps either an
* {@link IOException} (writing to a closed writer) or an {@link IllegalStateException}. The
* implementation will also track unbalanced nodes or multiple attributes with the same name.
*
* @author Jörg Schaible
* @since 1.2
*/
public class StatefulWriter extends WriterWrapper {
/**
* STATE_OPEN
is the initial value of the writer.
*
* @since 1.2
*/
public static int STATE_OPEN = 0;
/**
* STATE_NODE_START
is the state of a new node has been started.
*
* @since 1.2
*/
public static int STATE_NODE_START = 1;
/**
* STATE_VALUE
is the state if the value of a node has been written.
*
* @since 1.2
*/
public static int STATE_VALUE = 2;
/**
* STATE_NODE_END
is the state if a node has ended
*
* @since 1.2
*/
public static int STATE_NODE_END = 3;
/**
* STATE_CLOSED
is the state if the writer has been closed.
*
* @since 1.2
*/
public static int STATE_CLOSED = 4;
private transient int state = STATE_OPEN;
private transient int balance;
private transient FastStack attributes;
/**
* Constructs a StatefulWriter.
*
* @param wrapped the wrapped writer
* @since 1.2
*/
public StatefulWriter(final HierarchicalStreamWriter wrapped) {
super(wrapped);
attributes = new FastStack(16);
}
public void startNode(final String name) {
startNodeCommon();
super.startNode(name);
}
public void startNode(final String name, final Class clazz) {
startNodeCommon();
super.startNode(name, clazz);
}
private void startNodeCommon() {
checkClosed();
if (state == STATE_VALUE) {
// legal XML, but not in XStream ... ?
throw new StreamException(new IllegalStateException("Opening node after writing text"));
}
state = STATE_NODE_START;
++balance;
attributes.push(new HashSet());
}
public void addAttribute(String name, String value) {
checkClosed();
if (state != STATE_NODE_START) {
throw new StreamException(new IllegalStateException("Writing attribute '"
+ name
+ "' without an opened node"));
}
Set currentAttributes = (Set)attributes.peek();
if (currentAttributes.contains(name)) {
throw new StreamException(new IllegalStateException("Writing attribute '"
+ name
+ "' twice"));
}
currentAttributes.add(name);
super.addAttribute(name, value);
}
public void setValue(String text) {
checkClosed();
if (state != STATE_NODE_START) {
// STATE_NODE_END is legal XML, but not in XStream ... ?
throw new StreamException(new IllegalStateException(
"Writing text without an opened node"));
}
state = STATE_VALUE;
super.setValue(text);
}
public void endNode() {
checkClosed();
if (balance-- == 0) {
throw new StreamException(new IllegalStateException("Unbalanced node"));
}
attributes.popSilently();
state = STATE_NODE_END;
super.endNode();
}
public void flush() {
checkClosed();
super.flush();
}
public void close() {
if (state != STATE_NODE_END && state != STATE_OPEN) {
// calling close in a finally block should not throw again
// throw new StreamException(new IllegalStateException("Closing with unbalanced tag"));
}
state = STATE_CLOSED;
super.close();
}
private void checkClosed() {
if (state == STATE_CLOSED) {
throw new StreamException(new IOException("Writing on a closed stream"));
}
}
/**
* Retrieve the state of the writer.
*
* @return one of the states
* @see #STATE_OPEN
* @see #STATE_NODE_START
* @see #STATE_VALUE
* @see #STATE_NODE_END
* @see #STATE_CLOSED
* @since 1.2
*/
public int state() {
return state;
}
private Object readResolve() {
attributes = new FastStack(16);
return this;
}
}