/*
* Javolution - Java(TM) Solution for Real-Time and Embedded Systems
* Copyright (C) 2005 - Javolution (http://javolution.org/)
* All rights reserved.
*
* Permission to use, copy, modify, and distribute this software is
* freely granted, provided that this notice is preserved.
*/
package javolution37.javolution.xml;
import java.lang.IllegalStateException;
import java.io.CharConversionException;
import javolution37.javolution.lang.Reusable;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
/**
* This class represents an object input stream using {@link ObjectReader}
* for object deserialization.
*
* Instances of this class embed their own data buffer, wrapping using
* a java.io.BufferedInputStream is therefore unnescessary.
*
* @author Jean-Marie Dautelle
* @version 2.2, January 8, 2005
* @see XmlOutputStream
*/
public class XmlInputStream extends InputStream implements Reusable {
/**
* Holds the object reader.
*/
private final ObjectReader _objectReader;
/**
* Holds the xml reader used for parsing.
*/
private final XmlReader _xmlReader = new XmlReader();
/**
* Default constructor.
*/
public XmlInputStream() {
this(new ObjectReader ());
}
/**
* Creates a xml input stream using the specified object reader.
*
* @param reader the object reader.
*/
public XmlInputStream(ObjectReader reader) {
_objectReader = reader;
}
/**
* Sets the underlying input source for this stream.
*
* @param in the input source.
* @return this
* @throws IllegalStateException if this stream is being reused and
* it has not been {@link #close closed} or {@link #reset reset}.
*/
public XmlInputStream setInputStream(InputStream in) {
if (_xmlReader._inputStream != null)
throw new IllegalStateException("Stream not closed or reset");
_xmlReader._inputStream = in;
return this;
}
/**
* Reads an object from the underlying stream using an {@link ObjectReader}.
*
* @return the object read from its xml representation.
* @throws IOException if an I/O error occurs.
*/
public T readObject() throws IOException {
try {
return _objectReader.read(_xmlReader);
} finally {
_xmlReader.resume();
}
}
/**
* Closes and {@link #reset resets} this stream for reuse.
*
* @throws IOException if an I/O error occurs.
*/
public void close() throws IOException {
if (_xmlReader._inputStream != null) {
_xmlReader._inputStream.close();
reset();
}
}
/**
* Reads the next byte of data from the input stream.
*
* @return the next byte of data, or -1 if the end of the stream is reached.
* @throws IOException if an I/O error occurs.
*/
public int read() throws IOException {
if (_xmlReader._start < _xmlReader._end) {
return _xmlReader._bytes[_xmlReader._start++];
} else { // Use the reader buffer.
return _xmlReader.fillBuffer() ?
_xmlReader._bytes[_xmlReader._start++] : -1;
}
}
/**
* Reads up to len bytes of data from the input stream into an array of
* bytes.
*
* @param b the buffer into which the data is read.
* @param off the start offset in array b at which the data is written.
* @param len the maximum number of bytes to read.
* @return the total number of bytes read into the buffer, or -1 if there
* is no more data because the end of the stream has been reached.
* @throws IOException if an I/O error occurs.
*/
public int read(byte b[], int off, int len) throws IOException {
int rem = _xmlReader._end - _xmlReader._start;
if (rem == 0) { // Buffer empty, go straight to the source.
return _xmlReader._inputStream.read(b, off, len);
}
// Returns bytes from buffer.
int count = (len < rem) ? len : rem;
System.arraycopy(_xmlReader._bytes, _xmlReader._start, b, off, count);
_xmlReader._start += count;
return count;
}
// Implements Reusable interface.
public void reset() {
_objectReader.reset();
_xmlReader.reset();
}
/**
* This inner class represents a custom reader for reading the xml
* data when parsing. It stops reading when the
* {@link XmlOutputStream#END_XML} byte is encountered, it then returns
* the code -1 (end of stream) until {@link #resume resumed}.
*/
private static final class XmlReader extends Reader implements Reusable {
private InputStream _inputStream;
private int _code;
private int _moreBytes;
private int _start;
private int _end;
private final byte[] _bytes = new byte[2048];
private boolean _isHalted;
/**
* Resumes reading after an {@link XmlOutputStream#END_UTF8} byte
* is encountered.
*/
public void resume() {
_isHalted = false;
}
/**
* Fills buffer.
*
* @return true if at least one more byte has been read;
* false otherwise.
* @throws IOException if an I/O error occurs.
*/
public boolean fillBuffer() throws IOException {
if (_inputStream == null) throw new IOException("Stream closed");
_start = 0;
_end = _inputStream.read(_bytes, 0, _bytes.length);
return _end > 0;
}
// Implements abstract method.
public int read(char[] cbuf, int off, int len) throws IOException {
if (_isHalted) {
return -1;
} else if (_start >= _end) {
if (!fillBuffer()) {
return - 1;
}
}
final int off_plus_len = off + len;
for (int i = off; i < off_plus_len;) {
// assert(_start < _end)
byte b = _bytes[_start];
if ((b >= 0) && (++_start < _end)) {
cbuf[i++] = (char) b; // Most common case.
} else if (b < 0) {
if (b == XmlOutputStream.END_XML) {
++_start;
_isHalted = true;
return i - off;
} else if (i < off_plus_len - 1) { // Up to two 'char' can be read.
int code = read2();
if (code < 0x10000) {
cbuf[i++] = (char) code;
} else if (code <= 0x10ffff) { // Surrogates.
cbuf[i++] = (char) (((code - 0x10000) >> 10) + 0xd800);
cbuf[i++] = (char) (((code - 0x10000) & 0x3ff) + 0xdc00);
} else {
throw new CharConversionException(
"Cannot convert U+"
+ Integer.toHexString(code)
+ " to char (code greater than U+10FFFF)");
}
if (_start < _end) {
continue;
}
}
return i - off;
} else { // End of buffer (_start >= _end).
cbuf[i++] = (char) b;
return i - off;
}
}
return len;
}
// Implements abstract method.
public void close() throws IOException {
// Do nothing (we don't want the object reader to close
// the input stream).
}
// Reads one full character, blocks if necessary.
private int read2() throws IOException {
if (_start < _end) {
byte b = _bytes[_start++];
// Decodes UTF-8.
if ((b >= 0) && (_moreBytes == 0)) {
// 0xxxxxxx
return b;
} else if (((b & 0xc0) == 0x80) && (_moreBytes != 0)) {
// 10xxxxxx (continuation byte)
_code = (_code << 6) | (b & 0x3f); // Adds 6 bits to code.
if (--_moreBytes == 0) {
return _code;
} else {
return read2();
}
} else if (((b & 0xe0) == 0xc0) && (_moreBytes == 0)) {
// 110xxxxx
_code = b & 0x1f;
_moreBytes = 1;
return read2();
} else if (((b & 0xf0) == 0xe0) && (_moreBytes == 0)) {
// 1110xxxx
_code = b & 0x0f;
_moreBytes = 2;
return read2();
} else if (((b & 0xf8) == 0xf0) && (_moreBytes == 0)) {
// 11110xxx
_code = b & 0x07;
_moreBytes = 3;
return read2();
} else {
throw new CharConversionException("Invalid UTF-8 Encoding");
}
} else { // No more bytes in buffer.
if (fillBuffer()) {
return read2(); // Continues.
} else { // Done.
if (_moreBytes == 0) {
return -1;
} else { // Incomplete sequence.
throw new CharConversionException(
"Unexpected end of stream");
}
}
}
}
// Implements Reusable interface.
public void reset() {
_code = 0;
_end = 0;
_inputStream = null;
_moreBytes = 0;
_start = 0;
}
}
}
Ads help maintain this website.