001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, GeoTools Project Managment Committee (PMC)
005: * (C) 2001, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation;
010: * version 2.1 of the License.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.io;
018:
019: // Miscellaneous
020: import java.io.FilterWriter;
021: import java.io.IOException;
022: import java.io.Writer;
023:
024: // Geotools dependencies
025: import org.geotools.resources.XArray;
026:
027: /**
028: * Write characters to a stream while replacing various EOL by a unique string.
029: * This class catch all occurrences of <code>"\r"</code>, <code>"\n"</code> and
030: * <code>"\r\n"</code>, and replace them by the platform depend EOL string
031: * (<code>"\r\n"</code> on Windows, <code>"\n"</code> on Unix), or any other EOL
032: * explicitly set at construction time. This writer also remove trailing blanks
033: * before end of lines.
034: *
035: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/metadata/src/main/java/org/geotools/io/LineWriter.java $
036: * @version $Id: LineWriter.java 22482 2006-10-31 02:58:00Z desruisseaux $
037: * @author Martin Desruisseaux
038: *
039: * @since 2.0
040: */
041: public class LineWriter extends FilterWriter {
042: /**
043: * The line separator for End Of Line (EOL).
044: */
045: private String lineSeparator;
046:
047: /**
048: * Tells if the next '\n' character must be ignored. This field
049: * is used in order to avoid writing two EOL in place of "\r\n".
050: */
051: private boolean skipCR;
052:
053: /**
054: * Temporary buffer containing blanks to write. Whitespaces are put
055: * in this buffer before to be written. If whitespaces are followed
056: * by a character, they are written to the underlying stream before
057: * the character. Otherwise, if whitespaces are followed by a line
058: * separator, then they are discarted. The buffer capacity will be
059: * expanded as needed.
060: */
061: private char[] buffer = new char[64];
062:
063: /**
064: * Number of valid characters in {@link #buffer}.
065: */
066: private int count = 0;
067:
068: /**
069: * Construct a {@code LineWriter} object that
070: * will use the platform dependent line separator.
071: *
072: * @param out a Writer object to provide the underlying stream.
073: * @throws IllegalArgumentException if {@code out} is {@code null}.
074: */
075: public LineWriter(final Writer out) {
076: this (out, System.getProperty("line.separator", "\n"));
077: }
078:
079: /**
080: * Construct a {@code LineWriter} object
081: * that will use the specified line separator.
082: *
083: * @param out a Writer object to provide the underlying stream.
084: * @param lineSeparator String to use as line separator.
085: * @throws IllegalArgumentException if {@code out} or
086: * {@code lineSeparator} is {@code null}.
087: */
088: public LineWriter(final Writer out, final String lineSeparator) {
089: super (out);
090: this .lineSeparator = lineSeparator;
091: if (out == null || lineSeparator == null) {
092: throw new IllegalArgumentException();
093: }
094: }
095:
096: /**
097: * Returns the current line separator.
098: *
099: * @return The current line separator.
100: */
101: public String getLineSeparator() {
102: return lineSeparator;
103: }
104:
105: /**
106: * Change the line separator. This is the string to insert in place of
107: * every occurences of "\r", "\n" or "\r\n".
108: *
109: * @param lineSeparator The new line separator.
110: * @throws IllegalArgumentException if {@code lineSeparator} is {@code null}.
111: */
112: public void setLineSeparator(final String lineSeparator) {
113: if (lineSeparator == null) {
114: throw new IllegalArgumentException();
115: }
116: synchronized (lock) {
117: this .lineSeparator = lineSeparator;
118: }
119: }
120:
121: /**
122: * Write a line separator.
123: *
124: * @throws IOException If an I/O error occurs
125: */
126: private void writeEOL() throws IOException {
127: assert count == 0;
128: // Do NOT call super.write(String).
129: out.write(lineSeparator);
130: }
131:
132: /**
133: * Returns {@code true} if {@link #buffer} contains only
134: * white spaces. It should always be the case. This method is
135: * used for assertions only.
136: */
137: private boolean bufferBlank() {
138: for (int i = count; --i >= 0;) {
139: if (!Character.isSpaceChar(buffer[i])) {
140: return false;
141: }
142: }
143: return true;
144: }
145:
146: /**
147: * Flush the content of {@link #buffer}
148: * to the underlying stream.
149: *
150: * @throws IOException If an I/O error occurs
151: */
152: private void flushBuffer() throws IOException {
153: assert bufferBlank();
154: if (count != 0) {
155: out.write(buffer, 0, count);
156: count = 0;
157: }
158: }
159:
160: /**
161: * Write a portion of an array of characters. This
162: * portion must NOT contains any line separator.
163: */
164: private void writeLine(final char[] cbuf, final int lower, int upper)
165: throws IOException {
166: while (upper != lower) {
167: final char c = cbuf[upper - 1];
168: assert (c != '\r' && c != '\n');
169: if (Character.isSpaceChar(c)) {
170: upper--;
171: continue;
172: }
173: flushBuffer();
174: out.write(cbuf, lower, upper - lower);
175: break;
176: }
177: assert bufferBlank();
178: count = 0;
179: }
180:
181: /**
182: * Write a portion of an array of characters. This
183: * portion must NOT contains any line separator.
184: */
185: private void writeLine(final String str, final int lower, int upper)
186: throws IOException {
187: while (upper != lower) {
188: final char c = str.charAt(upper - 1);
189: assert (c != '\r' && c != '\n');
190: if (Character.isSpaceChar(c)) {
191: upper--;
192: continue;
193: }
194: flushBuffer();
195: out.write(str, lower, upper - lower);
196: break;
197: }
198: assert bufferBlank();
199: count = 0;
200: }
201:
202: /**
203: * Write a single character.
204: *
205: * @throws IOException If an I/O error occurs
206: */
207: public void write(final int c) throws IOException {
208: synchronized (lock) {
209: switch (c) {
210: case '\r': {
211: assert bufferBlank();
212: count = 0; // Discard whitespaces
213: writeEOL();
214: skipCR = true;
215: break;
216: }
217: case '\n': {
218: if (!skipCR) {
219: assert bufferBlank();
220: count = 0; // Discard whitespaces
221: writeEOL();
222: }
223: skipCR = false;
224: break;
225: }
226: default: {
227: if (c >= Character.MIN_VALUE
228: && c <= Character.MAX_VALUE
229: && Character.isSpaceChar((char) c)) {
230: if (count >= buffer.length) {
231: buffer = XArray.resize(buffer, count
232: + Math.min(8192, count));
233: }
234: buffer[count++] = (char) c;
235: } else {
236: flushBuffer();
237: out.write(c);
238: }
239: skipCR = false;
240: break;
241: }
242: }
243: }
244: }
245:
246: /**
247: * Write a portion of an array of characters.
248: *
249: * @param cbuf Buffer of characters to be written
250: * @param offset Offset from which to start reading characters
251: * @param length Number of characters to be written
252: * @throws IOException If an I/O error occurs
253: */
254: public void write(final char cbuf[], int offset, int length)
255: throws IOException {
256: if (offset < 0 || length < 0 || (offset + length) > cbuf.length) {
257: throw new IndexOutOfBoundsException();
258: }
259: if (length == 0) {
260: return;
261: }
262: synchronized (lock) {
263: if (skipCR && cbuf[offset] == '\n') {
264: offset++;
265: length--;
266: }
267: int upper = offset;
268: for (; length != 0; length--) {
269: switch (cbuf[upper++]) {
270: case '\r': {
271: writeLine(cbuf, offset, upper - 1);
272: writeEOL();
273: if (length != 0 && cbuf[upper] == '\n') {
274: upper++;
275: length--;
276: }
277: offset = upper;
278: break;
279: }
280: case '\n': {
281: writeLine(cbuf, offset, upper - 1);
282: writeEOL();
283: offset = upper;
284: break;
285: }
286: }
287: }
288: skipCR = (cbuf[upper - 1] == '\r');
289: /*
290: * Write the remainding characters and
291: * put trailing blanks into the buffer.
292: */
293: for (int i = upper; --i >= offset;) {
294: if (!Character.isSpaceChar(cbuf[i])) {
295: writeLine(cbuf, offset, offset = i + 1);
296: break;
297: }
298: }
299: length = upper - offset;
300: final int newCount = count + length;
301: if (newCount > buffer.length) {
302: buffer = XArray.resize(buffer, newCount);
303: }
304: System.arraycopy(cbuf, offset, buffer, count, length);
305: count = newCount;
306: }
307: }
308:
309: /**
310: * Write a portion of an array of a string.
311: *
312: * @param string String to be written
313: * @param offset Offset from which to start reading characters
314: * @param length Number of characters to be written
315: * @throws IOException If an I/O error occurs
316: */
317: public void write(final String string, int offset, int length)
318: throws IOException {
319: if (offset < 0 || length < 0
320: || (offset + length) > string.length()) {
321: throw new IndexOutOfBoundsException();
322: }
323: if (length == 0) {
324: return;
325: }
326: synchronized (lock) {
327: if (skipCR && string.charAt(offset) == '\n') {
328: offset++;
329: length--;
330: }
331: int upper = offset;
332: for (; length != 0; length--) {
333: switch (string.charAt(upper++)) {
334: case '\r': {
335: writeLine(string, offset, upper - 1);
336: writeEOL();
337: if (length != 0 && string.charAt(upper) == '\n') {
338: upper++;
339: length--;
340: }
341: offset = upper;
342: break;
343: }
344: case '\n': {
345: writeLine(string, offset, upper - 1);
346: writeEOL();
347: offset = upper;
348: break;
349: }
350: }
351: }
352: skipCR = (string.charAt(upper - 1) == '\r');
353: /*
354: * Write the remainding characters and
355: * put trailing blanks into the buffer.
356: */
357: for (int i = upper; --i >= offset;) {
358: if (!Character.isSpaceChar(string.charAt(i))) {
359: writeLine(string, offset, offset = i + 1);
360: break;
361: }
362: }
363: length = upper - offset;
364: final int newCount = count + length;
365: if (newCount > buffer.length) {
366: buffer = XArray.resize(buffer, newCount);
367: }
368: while (--length >= 0) {
369: buffer[count++] = string.charAt(offset++);
370: }
371: assert count == newCount;
372: }
373: }
374:
375: /**
376: * Flush the stream's content to the underlying stream. This method flush completly
377: * all internal buffers, including any whitespace characters that should have been
378: * skipped if the next non-blank character is a line separator.
379: *
380: * @throws IOException If an I/O error occurs
381: */
382: public void flush() throws IOException {
383: synchronized (lock) {
384: flushBuffer();
385: super.flush();
386: }
387: }
388: }
|