001: package com.quadcap.util.text;
002:
003: /* Copyright 1997 - 2003 Quadcap Software. All rights reserved.
004: *
005: * This software is distributed under the Quadcap Free Software License.
006: * This software may be used or modified for any purpose, personal or
007: * commercial. Open Source redistributions are permitted. Commercial
008: * redistribution of larger works derived from, or works which bundle
009: * this software requires a "Commercial Redistribution License"; see
010: * http://www.quadcap.com/purchase.
011: *
012: * Redistributions qualify as "Open Source" under one of the following terms:
013: *
014: * Redistributions are made at no charge beyond the reasonable cost of
015: * materials and delivery.
016: *
017: * Redistributions are accompanied by a copy of the Source Code or by an
018: * irrevocable offer to provide a copy of the Source Code for up to three
019: * years at the cost of materials and delivery. Such redistributions
020: * must allow further use, modification, and redistribution of the Source
021: * Code under substantially the same terms as this license.
022: *
023: * Redistributions of source code must retain the copyright notices as they
024: * appear in each source code file, these license terms, and the
025: * disclaimer/limitation of liability set forth as paragraph 6 below.
026: *
027: * Redistributions in binary form must reproduce this Copyright Notice,
028: * these license terms, and the disclaimer/limitation of liability set
029: * forth as paragraph 6 below, in the documentation and/or other materials
030: * provided with the distribution.
031: *
032: * The Software is provided on an "AS IS" basis. No warranty is
033: * provided that the Software is free of defects, or fit for a
034: * particular purpose.
035: *
036: * Limitation of Liability. Quadcap Software shall not be liable
037: * for any damages suffered by the Licensee or any third party resulting
038: * from use of the Software.
039: */
040:
041: import java.io.BufferedInputStream;
042: import java.io.IOException;
043: import java.io.InputStream;
044: import java.io.OutputStream;
045:
046: import com.quadcap.util.Debug;
047:
048: //#ifdef DEBUG
049: import java.io.ByteArrayOutputStream;
050: import com.quadcap.io.LogInputStream;
051:
052: //#endif
053:
054: /**
055: * The class implements a series of low-level stream scanning operations,
056: * using OctetMaps to implement 'while' and 'until'.
057: * @author Stan Bailes
058: */
059: public class Scanner {
060: InputStream is;
061: StringBuffer sb = new StringBuffer();
062: int pushback = -1;
063:
064: //#ifdef DEBUG
065: LogInputStream log = null;
066: ByteArrayOutputStream bos = null;
067:
068: public Scanner(InputStream is, boolean saveit) {
069: if (saveit) {
070: bos = new ByteArrayOutputStream();
071: this .is = new LogInputStream(is, bos, "");
072: } else {
073: this .is = is;
074: }
075: }
076:
077: public String getLog() {
078: return bos.toString();
079: }
080:
081: //#endif
082:
083: /**
084: * Construct a new scanner, attached to the specified input stream.
085: *
086: * @param is the stream to scan.
087: */
088: public Scanner(InputStream is) {
089: this .is = is;
090: }
091:
092: public void reset(InputStream is) {
093: this .is = is;
094: //#ifdef DEBUG
095: if (bos != null) {
096: bos.reset();
097: this .is = new LogInputStream(is, bos, "");
098: }
099: //#endif
100: pushback = -1;
101: }
102:
103: final int read() throws IOException {
104: int c = pushback;
105: if (c >= 0) {
106: pushback = -1;
107: } else {
108: c = is.read();
109: }
110: return c;
111: }
112:
113: final void unread(int c) {
114: pushback = c;
115: }
116:
117: /**
118: * Read and discard characters until a character is found which
119: * <b>is not</b> in the map. Then push back the terminating character.
120: *
121: * @param map the set of characters to skip.
122: * @exception IOException if an I/O exception is thrown
123: */
124: final public void skipWhile(OctetMap map) throws IOException {
125: int c;
126: while (map.has(c = read()) && c >= 0)
127: continue;
128: if (c >= 0)
129: unread(c);
130: }
131:
132: /**
133: * Read and discard characters until a character is found which
134: * <b>is</b> in the map. Then push back the terminating character.
135: *
136: * @param map the set of characters which skip.
137: * @exception IOException if an I/O exception is thrown
138: */
139: final public void skipUntil(OctetMap map) throws IOException {
140: int c;
141: while (!map.has(c = read()) && c >= 0)
142: continue;
143: if (c >= 0)
144: unread(c);
145: }
146:
147: /**
148: * Return the next portion of the stream which consists of characters
149: * <b>IN</b> the specified set.
150: *
151: * @param map the set of characters to parse.
152: * @exception IOException if an I/O exception is thrown
153: */
154: final public String parseWhile(OctetMap map) throws IOException {
155: sb.setLength(0);
156: int c;
157: while (map.has(c = read()) && c >= 0) {
158: sb.append((char) c);
159: }
160: if (c >= 0)
161: unread(c);
162: return sb.toString();
163: }
164:
165: /**
166: * Return the next portion of the stream which consists of characters
167: * <b>NOT IN</b> the specified set.
168: *
169: * @param map the set of characters to parse.
170: * @exception IOException if an I/O exception is thrown
171: */
172: final public String parseUntil(OctetMap map) throws IOException {
173: sb.setLength(0);
174: int c;
175: while (!map.has(c = read()) && c >= 0) {
176: sb.append((char) c);
177: }
178: if (c >= 0)
179: unread(c);
180: return sb.toString();
181: }
182:
183: /**
184: * Read the next character, and verify that it is equal to the expected
185: * value.
186: *
187: * @param expected the expected character
188: * @exception IOException if the character read from the stream is not
189: * equal to the specified character.
190: */
191: final public void matchChar(int expected) throws IOException {
192: int c = read();
193: if (c != expected) {
194: if (c < 0x1f || expected < 0x1f) {
195: throw new IOException("Expected: " + expected
196: + ", got " + c);
197: } else {
198: throw new IOException("Expected: " + expected + "("
199: + (char) expected + "), got " + c + "("
200: + (char) c + ")");
201: }
202: }
203: }
204:
205: /**
206: * Read the next string using the specified map, and verify that it
207: * <b>IS</b> equal to the expected value.
208: *
209: * @param expected the expected string
210: * @exception IOException if the character read from the stream is not
211: * equal to the specified character.
212: */
213: final public void matchString(OctetMap map, String expected)
214: throws IOException {
215: String actual = parseWhile(map);
216: if (!actual.equals(expected)) {
217: throw new IOException("Expected: " + expected + ", got: "
218: + actual);
219: }
220: }
221:
222: /**
223: * Read the next string using the specified map, and verify that it
224: * <b>IS</b> equal to the expected value, if all characters in both
225: * strings are converted to monocase.
226: *
227: * @param expected the expected string
228: * @exception IOException if the character read from the stream is not
229: * equal to the specified character.
230: */
231: final public void matchStringIgnoreCase(OctetMap map,
232: String expected) throws IOException {
233: String actual = parseWhile(map);
234: if (!actual.equalsIgnoreCase(expected)) {
235: throw new IOException("Expected: " + expected + ", got: '"
236: + actual + "', next char = " + ((char) peek())
237: + " (" + peek() + ")");
238: }
239: }
240:
241: /**
242: * Peek ahead one character in the stream by reading, then pushing back
243: * the character.
244: *
245: * @return the next character from the stream.
246: * @exception IOException if an exception is thrown when the character
247: * is read.
248: */
249: final public int peek() throws IOException {
250: if (pushback > 0) {
251: return pushback;
252: } else {
253: int c = read();
254: unread(c);
255: return c;
256: }
257: }
258:
259: /**
260: * Copy bytes from an input stream to an output stream until a byte
261: * not in the specified set is found. That byte is returned, or -1
262: * the end of file is reached.
263: *
264: * @param is the input stream from which bytes are is.read
265: * @param os the output stream to which bytes are written
266: * @param map the set of valid byte to copy
267: * @return the first non matching byte
268: * @exception IOException may be thrown
269: */
270: public static int copyWhile(InputStream is, OutputStream os,
271: OctetMap map) throws IOException {
272: int c = is.read();
273: while (c >= 0 && map.has(c)) {
274: os.write(c);
275: c = is.read();
276: }
277: return c;
278: }
279:
280: /**
281: * Copy bytes from an input stream to an output stream until a byte
282: * in the specified set is found. That byte is returned, or -1
283: * the end of file is reached.
284: *
285: * @param is the input stream from which bytes are is.read
286: * @param os the output stream to which bytes are written
287: * @param map the set of valid delimiter bytes
288: * @return the first matching byte
289: * @exception IOException may be thrown
290: */
291: public static int copyUntil(InputStream is, OutputStream os,
292: OctetMap map) throws IOException {
293: int c = is.read();
294: while (c >= 0 && !map.has(c)) {
295: os.write(c);
296: c = is.read();
297: }
298: return c;
299: }
300:
301: public static int copyUntil(InputStream is, OutputStream os, int dc)
302: throws IOException {
303: int c = is.read();
304: while (c >= 0 && c != dc) {
305: os.write(c);
306: c = is.read();
307: }
308: return c;
309: }
310:
311: /**
312: * Copy bytes from <code>in</code> to <code>out</code> until the
313: * specified string is is.read from <code>in</code. The string bytes
314: * are not written to <code>out</code>
315: *
316: * @param is the input stream from which bytes are is.read
317: * @param os the output stream to which bytes are written
318: * @param s the delimiter string
319: * @return < 0 if end of file is reached before the string is found,
320: * >= 0 otherwise
321: * @exception IOException may be thrown
322: */
323: public static int copyUntil(BufferedInputStream is,
324: OutputStream os, String s) throws IOException {
325: if (s.length() == 0)
326: throw new IOException("empty target");
327:
328: int ret = -1;
329: int dc = s.charAt(0) & 0xff;
330: for (boolean found = false; !found;) {
331: if (copyUntil(is, os, dc) < 0)
332: return -1;
333:
334: is.mark(s.length());
335: found = true;
336: for (int i = 1; found && i < s.length(); i++) {
337: int c = 0;
338: if ((c = is.read()) != s.charAt(i)) {
339: for (int j = 0; j < i; j++)
340: os.write(s.charAt(j));
341:
342: if (c < 0)
343: return -1;
344:
345: is.reset();
346: found = false;
347: }
348: }
349: }
350: return 1;
351: }
352:
353: /**
354: * Read and discard characters until a character is found which
355: * <b>is not</b> equal to the specified character. Return the
356: * terminating character.
357: *
358: * @param dc the character to discard
359: * @exception IOException if an I/O exception is thrown
360: */
361: public static int skipWhile(InputStream is, int dc)
362: throws IOException {
363: int c;
364: while (dc == (c = is.read()))
365: continue;
366: return c;
367: }
368: }
|