001: /*
002: * Copyright (C) 2007, 2008 XStream Committers.
003: * All rights reserved.
004: *
005: * The software in this package is published under the terms of the BSD
006: * style license a copy of which has been included with this distribution in
007: * the LICENSE.txt file.
008: *
009: * Created on 13. September 2007 by Joerg Schaible.
010: */
011:
012: package com.thoughtworks.xstream.core.util;
013:
014: import java.io.ByteArrayOutputStream;
015: import java.io.IOException;
016: import java.io.InputStream;
017: import java.io.InputStreamReader;
018: import java.io.PushbackInputStream;
019: import java.io.Reader;
020: import java.io.UnsupportedEncodingException;
021: import java.util.HashMap;
022: import java.util.Map;
023:
024: /**
025: * A {@link Reader} that evaluates the XML header. It selects its encoding based on the encoding read with the XML
026: * header of the provided {@link InputStream}. The default encoding is <em>UTF-8</em> and the version is 1.0 if the
027: * stream does not contain an XML header or the attributes are not set within the header.
028: *
029: * @author Jörg Schaible
030: * @since 1.3
031: */
032: public final class XmlHeaderAwareReader extends Reader {
033:
034: private final InputStreamReader reader;
035: private final double version;
036:
037: private static final String KEY_ENCODING = "encoding";
038: private static final String KEY_VERSION = "version";
039:
040: private static final String XML_TOKEN = "?xml";
041:
042: private static final int STATE_START = 0;
043: private static final int STATE_AWAIT_XML_HEADER = 1;
044: private static final int STATE_ATTR_NAME = 2;
045: private static final int STATE_ATTR_VALUE = 3;
046:
047: /**
048: * Constructs an XmlHeaderAwareReader.
049: *
050: * @param in the {@link InputStream}
051: * @throws UnsupportedEncodingException if the encoding is not supported
052: * @throws IOException occurred while reading the XML header
053: * @since 1.3
054: */
055: public XmlHeaderAwareReader(final InputStream in)
056: throws UnsupportedEncodingException, IOException {
057: final PushbackInputStream pin = in instanceof PushbackInputStream ? (PushbackInputStream) in
058: : new PushbackInputStream(in, 64);
059: final Map header = getHeader(pin);
060: version = Double.parseDouble((String) header.get(KEY_VERSION));
061: reader = new InputStreamReader(pin, (String) header
062: .get(KEY_ENCODING));
063: }
064:
065: private Map getHeader(final PushbackInputStream in)
066: throws IOException {
067: final Map header = new HashMap();
068: header.put(KEY_ENCODING, "utf-8");
069: header.put(KEY_VERSION, "1.0");
070:
071: int state = STATE_START;
072: final ByteArrayOutputStream out = new ByteArrayOutputStream(64);
073: int i = 0;
074: char ch = 0;
075: char valueEnd = 0;
076: final StringBuffer name = new StringBuffer();
077: final StringBuffer value = new StringBuffer();
078: boolean escape = false;
079: while (i != -1 && (i = in.read()) != -1) {
080: out.write(i);
081: ch = (char) i;
082: switch (state) {
083: case STATE_START:
084: if (!Character.isWhitespace(ch)) {
085: if (ch == '<') {
086: state = STATE_AWAIT_XML_HEADER;
087: } else {
088: i = -1;
089: }
090: }
091: break;
092: case STATE_AWAIT_XML_HEADER:
093: if (!Character.isWhitespace(ch)) {
094: name.append(Character.toLowerCase(ch));
095: if (!XML_TOKEN.startsWith(name.substring(0))) {
096: i = -1;
097: }
098: } else {
099: if (name.toString().equals(XML_TOKEN)) {
100: state = STATE_ATTR_NAME;
101: name.setLength(0);
102: } else {
103: i = -1;
104: }
105: }
106: break;
107: case STATE_ATTR_NAME:
108: if (!Character.isWhitespace(ch)) {
109: if (ch == '=') {
110: state = STATE_ATTR_VALUE;
111: } else {
112: ch = Character.toLowerCase(ch);
113: if (Character.isLetter(ch)) {
114: name.append(ch);
115: } else {
116: i = -1;
117: }
118: }
119: } else if (name.length() > 0) {
120: i = -1;
121: }
122: break;
123: case STATE_ATTR_VALUE:
124: if (valueEnd == 0) {
125: if (ch == '"' || ch == '\'') {
126: valueEnd = ch;
127: } else {
128: i = -1;
129: }
130: } else {
131: if (ch == '\\' && !escape) {
132: escape = true;
133: break;
134: }
135: if (ch == valueEnd && !escape) {
136: valueEnd = 0;
137: state = STATE_ATTR_NAME;
138: header.put(name.toString(), value.toString());
139: name.setLength(0);
140: value.setLength(0);
141: } else {
142: escape = false;
143: if (ch != '\n') {
144: value.append(ch);
145: } else {
146: i = -1;
147: }
148: }
149: }
150: break;
151: }
152: }
153:
154: in.unread(out.toByteArray());
155: return header;
156: }
157:
158: /**
159: * @see InputStreamReader#getEncoding()
160: * @since 1.3
161: */
162: public String getEncoding() {
163: return reader.getEncoding();
164: }
165:
166: /**
167: * @see InputStreamReader#getEncoding()
168: * @since 1.3
169: */
170: public double getVersion() {
171: return version;
172: }
173:
174: /**
175: * @see java.io.Reader#mark(int)
176: */
177: public void mark(final int readAheadLimit) throws IOException {
178: reader.mark(readAheadLimit);
179: }
180:
181: /**
182: * @see java.io.Reader#markSupported()
183: */
184: public boolean markSupported() {
185: return reader.markSupported();
186: }
187:
188: /**
189: * @see java.io.Reader#read()
190: */
191: public int read() throws IOException {
192: return reader.read();
193: }
194:
195: /**
196: * @see java.io.Reader#read(char[], int, int)
197: */
198: public int read(final char[] cbuf, final int offset,
199: final int length) throws IOException {
200: return reader.read(cbuf, offset, length);
201: }
202:
203: /**
204: * @see java.io.Reader#read(char[])
205: */
206: public int read(final char[] cbuf) throws IOException {
207: return reader.read(cbuf);
208: }
209:
210: // TODO: This is JDK 1.5
211: // public int read(final CharBuffer target) throws IOException {
212: // return reader.read(target);
213: // }
214:
215: /**
216: * @see java.io.Reader#ready()
217: */
218: public boolean ready() throws IOException {
219: return reader.ready();
220: }
221:
222: /**
223: * @see java.io.Reader#reset()
224: */
225: public void reset() throws IOException {
226: reader.reset();
227: }
228:
229: /**
230: * @see java.io.Reader#skip(long)
231: */
232: public long skip(final long n) throws IOException {
233: return reader.skip(n);
234: }
235:
236: /**
237: * @see java.io.Reader#close()
238: */
239: public void close() throws IOException {
240: reader.close();
241: }
242:
243: /**
244: * @see java.lang.Object#equals(java.lang.Object)
245: */
246: public boolean equals(final Object obj) {
247: return reader.equals(obj);
248: }
249:
250: /**
251: * @see java.lang.Object#hashCode()
252: */
253: public int hashCode() {
254: return reader.hashCode();
255: }
256:
257: /**
258: * @see java.lang.Object#toString()
259: */
260: public String toString() {
261: return reader.toString();
262: }
263: }
|