001: /*
002: * CharSequenceReader.java
003: *
004: * This file is part of SQL Workbench/J, http://www.sql-workbench.net
005: *
006: * Copyright 2002-2008, Thomas Kellerer
007: * No part of this code maybe reused without the permission of the author
008: *
009: * To contact the author please send an email to: support@sql-workbench.net
010: *
011: */
012: package workbench.util;
013:
014: import java.io.IOException;
015: import java.io.Reader;
016:
017: /**
018: * A character stream whose source is a CharSequence.
019: *
020: * Copied from the JDK's StringReader as that does not
021: * support a CharSequence as the input.
022: */
023: public class CharSequenceReader extends Reader {
024: private CharSequence str;
025: private int length;
026: private int next = 0;
027: private int mark = 0;
028:
029: /**
030: * Creates a new string reader.
031: *
032: * @param s String providing the character stream.
033: */
034: public CharSequenceReader(CharSequence s) {
035: this .str = s;
036: this .length = s.length();
037: }
038:
039: /** Check to make sure that the stream has not been closed */
040: private void ensureOpen() throws IOException {
041: if (str == null)
042: throw new IOException("Stream closed");
043: }
044:
045: /**
046: * Reads a single character.
047: *
048: * @return The character read, or -1 if the end of the stream has been
049: * reached
050: *
051: * @exception IOException If an I/O error occurs
052: */
053: public int read() throws IOException {
054: synchronized (lock) {
055: ensureOpen();
056: if (next >= length)
057: return -1;
058: return str.charAt(next++);
059: }
060: }
061:
062: /**
063: * Reads characters into a portion of an array.
064: *
065: * @param cbuf Destination buffer
066: * @param off Offset at which to start writing characters
067: * @param len Maximum number of characters to read
068: *
069: * @return The number of characters read, or -1 if the end of the
070: * stream has been reached
071: *
072: * @exception IOException If an I/O error occurs
073: */
074: public int read(char[] cbuf, int off, int len) throws IOException {
075: synchronized (lock) {
076: ensureOpen();
077: if ((off < 0) || (off > cbuf.length) || (len < 0)
078: || ((off + len) > cbuf.length) || ((off + len) < 0)) {
079: throw new IndexOutOfBoundsException();
080: } else if (len == 0) {
081: return 0;
082: }
083: if (next >= length)
084: return -1;
085: int n = Math.min(length - next, len);
086:
087: int buffPos = off;
088: for (int i = next; i < next + n; i++) {
089: cbuf[buffPos] = str.charAt(i);
090: buffPos++;
091: }
092:
093: next += n;
094: return n;
095: }
096: }
097:
098: /**
099: * Skips the specified number of characters in the stream. Returns
100: * the number of characters that were skipped.
101: *
102: * <p>The <code>ns</code> parameter may be negative, even though the
103: * <code>skip</code> method of the {@link Reader} superclass throws
104: * an exception in this case. Negative values of <code>ns</code> cause the
105: * stream to skip backwards. Negative return values indicate a skip
106: * backwards. It is not possible to skip backwards past the beginning of
107: * the string.
108: *
109: * <p>If the entire string has been read or skipped, then this method has
110: * no effect and always returns 0.
111: *
112: * @exception IOException If an I/O error occurs
113: */
114: public long skip(long ns) throws IOException {
115: synchronized (lock) {
116: ensureOpen();
117: if (next >= length)
118: return 0;
119: // Bound skip by beginning and end of the source
120: long n = Math.min(length - next, ns);
121: n = Math.max(-next, n);
122: next += n;
123: return n;
124: }
125: }
126:
127: /**
128: * Tells whether this stream is ready to be read.
129: *
130: * @return True if the next read() is guaranteed not to block for input
131: *
132: * @exception IOException If the stream is closed
133: */
134: public boolean ready() throws IOException {
135: synchronized (lock) {
136: ensureOpen();
137: return true;
138: }
139: }
140:
141: /**
142: * Tells whether this stream supports the mark() operation, which it does.
143: */
144: public boolean markSupported() {
145: return true;
146: }
147:
148: /**
149: * Marks the present position in the stream. Subsequent calls to reset()
150: * will reposition the stream to this point.
151: *
152: * @param readAheadLimit Limit on the number of characters that may be
153: * read while still preserving the mark. Because
154: * the stream's input comes from a string, there
155: * is no actual limit, so this argument must not
156: * be negative, but is otherwise ignored.
157: *
158: * @exception IllegalArgumentException If readAheadLimit is < 0
159: * @exception IOException If an I/O error occurs
160: */
161: public void mark(int readAheadLimit) throws IOException {
162: if (readAheadLimit < 0) {
163: throw new IllegalArgumentException("Read-ahead limit < 0");
164: }
165: synchronized (lock) {
166: ensureOpen();
167: mark = next;
168: }
169: }
170:
171: /**
172: * Resets the stream to the most recent mark, or to the beginning of the
173: * string if it has never been marked.
174: *
175: * @exception IOException If an I/O error occurs
176: */
177: public void reset() throws IOException {
178: synchronized (lock) {
179: ensureOpen();
180: next = mark;
181: }
182: }
183:
184: /**
185: * Closes the stream and releases any system resources associated with
186: * it. Once the stream has been closed, further read(),
187: * ready(), mark(), or reset() invocations will throw an IOException.
188: * Closing a previously closed stream has no effect.
189: */
190: public void close() {
191: str = null;
192: }
193: }
|