001: /*
002: * Copyright (C) 2006 Joe Walnes.
003: * Copyright (C) 2006, 2007 XStream Committers.
004: * All rights reserved.
005: *
006: * The software in this package is published under the terms of the BSD
007: * style license a copy of which has been included with this distribution in
008: * the LICENSE.txt file.
009: *
010: * Created on 15. March 2006 by Joerg Schaible
011: */
012: package com.thoughtworks.xstream.io;
013:
014: import com.thoughtworks.xstream.core.util.FastStack;
015:
016: import java.io.IOException;
017: import java.util.HashSet;
018: import java.util.Set;
019:
020: /**
021: * An wrapper for all {@link HierarchicalStreamWriter} implementations, that keeps the state.
022: * Writing in a wrong state will throw a {@link StreamException}, that wraps either an
023: * {@link IOException} (writing to a closed writer) or an {@link IllegalStateException}. The
024: * implementation will also track unbalanced nodes or multiple attributes with the same name.
025: *
026: * @author Jörg Schaible
027: * @since 1.2
028: */
029: public class StatefulWriter extends WriterWrapper {
030:
031: /**
032: * <code>STATE_OPEN</code> is the initial value of the writer.
033: *
034: * @since 1.2
035: */
036: public static int STATE_OPEN = 0;
037: /**
038: * <code>STATE_NODE_START</code> is the state of a new node has been started.
039: *
040: * @since 1.2
041: */
042: public static int STATE_NODE_START = 1;
043: /**
044: * <code>STATE_VALUE</code> is the state if the value of a node has been written.
045: *
046: * @since 1.2
047: */
048: public static int STATE_VALUE = 2;
049: /**
050: * <code>STATE_NODE_END</code> is the state if a node has ended
051: *
052: * @since 1.2
053: */
054: public static int STATE_NODE_END = 3;
055: /**
056: * <code>STATE_CLOSED</code> is the state if the writer has been closed.
057: *
058: * @since 1.2
059: */
060: public static int STATE_CLOSED = 4;
061:
062: private transient int state = STATE_OPEN;
063: private transient int balance;
064: private transient FastStack attributes;
065:
066: /**
067: * Constructs a StatefulWriter.
068: *
069: * @param wrapped the wrapped writer
070: * @since 1.2
071: */
072: public StatefulWriter(final HierarchicalStreamWriter wrapped) {
073: super (wrapped);
074: attributes = new FastStack(16);
075: }
076:
077: public void startNode(final String name) {
078: startNodeCommon();
079: super .startNode(name);
080: }
081:
082: public void startNode(final String name, final Class clazz) {
083: startNodeCommon();
084: super .startNode(name, clazz);
085: }
086:
087: private void startNodeCommon() {
088: checkClosed();
089: if (state == STATE_VALUE) {
090: // legal XML, but not in XStream ... ?
091: throw new StreamException(new IllegalStateException(
092: "Opening node after writing text"));
093: }
094: state = STATE_NODE_START;
095: ++balance;
096: attributes.push(new HashSet());
097: }
098:
099: public void addAttribute(String name, String value) {
100: checkClosed();
101: if (state != STATE_NODE_START) {
102: throw new StreamException(new IllegalStateException(
103: "Writing attribute '" + name
104: + "' without an opened node"));
105: }
106: Set currentAttributes = (Set) attributes.peek();
107: if (currentAttributes.contains(name)) {
108: throw new StreamException(new IllegalStateException(
109: "Writing attribute '" + name + "' twice"));
110: }
111: currentAttributes.add(name);
112: super .addAttribute(name, value);
113: }
114:
115: public void setValue(String text) {
116: checkClosed();
117: if (state != STATE_NODE_START) {
118: // STATE_NODE_END is legal XML, but not in XStream ... ?
119: throw new StreamException(new IllegalStateException(
120: "Writing text without an opened node"));
121: }
122: state = STATE_VALUE;
123: super .setValue(text);
124: }
125:
126: public void endNode() {
127: checkClosed();
128: if (balance-- == 0) {
129: throw new StreamException(new IllegalStateException(
130: "Unbalanced node"));
131: }
132: attributes.popSilently();
133: state = STATE_NODE_END;
134: super .endNode();
135: }
136:
137: public void flush() {
138: checkClosed();
139: super .flush();
140: }
141:
142: public void close() {
143: if (state != STATE_NODE_END && state != STATE_OPEN) {
144: // calling close in a finally block should not throw again
145: // throw new StreamException(new IllegalStateException("Closing with unbalanced tag"));
146: }
147: state = STATE_CLOSED;
148: super .close();
149: }
150:
151: private void checkClosed() {
152: if (state == STATE_CLOSED) {
153: throw new StreamException(new IOException(
154: "Writing on a closed stream"));
155: }
156: }
157:
158: /**
159: * Retrieve the state of the writer.
160: *
161: * @return one of the states
162: * @see #STATE_OPEN
163: * @see #STATE_NODE_START
164: * @see #STATE_VALUE
165: * @see #STATE_NODE_END
166: * @see #STATE_CLOSED
167: * @since 1.2
168: */
169: public int state() {
170: return state;
171: }
172:
173: private Object readResolve() {
174: attributes = new FastStack(16);
175: return this;
176: }
177: }
|