001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.editor;
043:
044: import java.io.IOException;
045: import java.io.Reader;
046: import javax.swing.text.Segment;
047: import org.netbeans.lib.editor.util.CharacterConversions;
048:
049: /**
050: * Converters handling the various line separators.
051: *
052: * @author Miloslav Metelka
053: * @version 1.00
054: * @deprecated Use {@link org.netbeans.lib.editor.util.CharacterConversions} instead.
055: */
056:
057: public class LineSeparatorConversion {
058:
059: /**
060: * Unicode line separator. See #100842.
061: */
062: public static final char LS = CharacterConversions.LS;
063: /**
064: * Unicode paragraph separator. See #100842.
065: */
066: public static final char PS = CharacterConversions.PS;
067: public static final String LS_LS = String.valueOf(LS);
068: public static final String LS_PS = String.valueOf(PS);
069:
070: /**
071: * Default size of the conversion buffers.
072: */
073: private static final int DEFAULT_CONVERSION_BUFFER_SIZE = 16384;
074:
075: private LineSeparatorConversion() {
076: // no instances
077: }
078:
079: /**
080: * Convert all the occurrences of '\r' and '\r\n' in the text to '\n'.
081: * @param text text being converted
082: * @return converted text with '\n' instead of '\r' and '\r\n'.
083: */
084: public static String convertToLineFeed(String text) {
085: return CharacterConversions.lineSeparatorToLineFeed(text);
086: }
087:
088: /**
089: * Convert all the occurrences of '\r' and '\r\n' in the text to '\n'.
090: * @param text text being converted
091: * @param offset offset of the first character in the text to be converted.
092: * @param length number of characters to be converted.
093: * @param output output buffer to which the converted characters are added.
094: */
095: public static void convertToLineFeed(String text, int offset,
096: int length, StringBuffer output) {
097: String s = CharacterConversions.lineSeparatorToLineFeed(text
098: .subSequence(offset, offset + length));
099: output.append(s);
100: }
101:
102: /**
103: * Convert all the occurrences of '\n' in the given text
104: * to the requested line separator.
105: * @param text text being converted
106: * @param lineFeedReplace characters that replace the '\n' character
107: * in the converted text.
108: * @return converted text with replaced '\n' by characters from lineFeedReplace string
109: */
110: public static String convertFromLineFeed(String text,
111: String lineFeedReplace) {
112: return CharacterConversions.lineFeedToLineSeparator(text,
113: lineFeedReplace);
114: }
115:
116: /**
117: * Convert all the occurrences of '\n' in the given text
118: * to the requested line separator.
119: * @param text text being converted
120: * @param offset offset of the first character in the text to be converted.
121: * @param length number of characters to be converted.
122: * @param lineFeedReplace characters that replace the '\n' character in the output
123: * @param output output buffer to which the converted characters are added.
124: */
125: public static void convertFromLineFeed(String text, int offset,
126: int length, String lineFeedReplace, StringBuffer output) {
127: String s = CharacterConversions.lineFeedToLineSeparator(text
128: .subSequence(offset, offset + length), lineFeedReplace);
129: output.append(s);
130: }
131:
132: /**
133: * Convert all the occurrences of '\r' and '\r\n' in the text to '\n'.
134: * This class does conversion in chunks of fixed size
135: * and is therefore suitable for conversion of readers
136: * where the size is unknown.
137: */
138: public static class ToLineFeed {
139:
140: private Reader reader;
141:
142: private Segment convertedText;
143:
144: private boolean lastCharCR;
145:
146: public ToLineFeed(Reader reader) {
147: this (reader, DEFAULT_CONVERSION_BUFFER_SIZE);
148: }
149:
150: public ToLineFeed(Reader reader, int convertBufferSize) {
151: this .reader = reader;
152: convertedText = new Segment();
153: convertedText.array = new char[convertBufferSize];
154: }
155:
156: public Segment nextConverted() throws IOException {
157: if (reader == null) { // no more chars to read
158: return null;
159: }
160:
161: int readOffset = 0;
162: int readSize = readBuffer(reader, convertedText.array,
163: readOffset, true);
164:
165: if (readSize == 0) { // no more chars in reader
166: reader.close();
167: reader = null;
168: return null;
169: }
170:
171: if (lastCharCR && readSize > 0
172: && convertedText.array[readOffset] == '\n') {
173: /* the preceding '\r' was already converted to '\n'
174: * in the previous buffer so here just skip initial '\n'
175: */
176: readOffset++;
177: readSize--;
178: }
179:
180: convertedText.offset = readOffset;
181: convertedText.count = readSize;
182: lastCharCR = convertSegmentToLineFeed(convertedText);
183: return convertedText;
184: }
185:
186: /**
187: * Convert all the '\r\n' or '\r' to '\n' (linefeed).
188: * This method
189: * @param text the text to be converted. Text is converted
190: * in the original array of the given segment.
191: * The <CODE>count</CODE> field
192: * of the text parameter will possibly be changed by the conversion
193: * if '\r\n' sequences are present.
194: * @return whether the last character in the text was the '\r' character.
195: * That character was already converted to '\n' and is present
196: * in the segment. However this notification is important
197: * because if there would be '\n' at the begining
198: * of the next buffer then that character should be skipped.
199: */
200: private static boolean convertSegmentToLineFeed(Segment text) {
201: char[] chars = text.array;
202: int storeOffset = text.offset; // offset at which chars are stored
203: int endOffset = storeOffset + text.count;
204: boolean storeChar = false; // to prevent copying same chars to same offsets
205: boolean lastCharCR = false; // whether last char was '\r'
206:
207: for (int offset = storeOffset; offset < endOffset; offset++) {
208: char ch = chars[offset];
209:
210: if (lastCharCR && ch == '\n') { // found CRLF sequence
211: lastCharCR = false;
212: storeChar = true; // storeOffset now differs from offset
213:
214: } else { // not CRLF sequence
215: if (ch == '\r') {
216: lastCharCR = true;
217: chars[storeOffset++] = '\n'; // convert it to '\n'
218:
219: } else if (ch == LS || ch == PS) { // Unicode LS, PS
220: lastCharCR = false;
221: chars[storeOffset++] = '\n';
222: } else { // current char not '\r'
223: lastCharCR = false;
224: if (storeChar) {
225: chars[storeOffset] = ch;
226: }
227: storeOffset++;
228: }
229: }
230: }
231:
232: text.count = storeOffset - text.offset;
233:
234: return lastCharCR;
235: }
236:
237: private static int readBuffer(Reader reader, char[] buffer,
238: int offset, boolean joinReads) throws IOException {
239: int maxReadSize = buffer.length - offset;
240: int totalReadSize = 0;
241:
242: do {
243: int readSize = 0;
244: while (readSize == 0) { // eliminate empty reads
245: readSize = reader.read(buffer, offset, maxReadSize);
246: }
247:
248: if (readSize == -1) {
249: break; // no more chars in reader
250: }
251:
252: totalReadSize += readSize;
253: offset += readSize;
254: maxReadSize -= readSize;
255:
256: } while (joinReads && maxReadSize > 0);
257:
258: return totalReadSize;
259: }
260:
261: }
262:
263: /**
264: * Convert all the occurrences of '\n' in the given text
265: * to the requested line separator.
266: * This class does conversion in chunks of fixed size
267: * and is therefore suitable for conversion of large
268: * texts.
269: */
270: public static class FromLineFeed {
271:
272: private Object charArrayOrSequence;
273:
274: private int offset;
275:
276: private int endOffset;
277:
278: private String lineFeedReplace;
279:
280: private Segment convertedText;
281:
282: public FromLineFeed(char[] source, int offset, int length,
283: String lineFeedReplace) {
284: this (source, offset, length, lineFeedReplace,
285: DEFAULT_CONVERSION_BUFFER_SIZE);
286: }
287:
288: public FromLineFeed(char[] source, int offset, int length,
289: String lineFeedReplace, int conversionSegmentSize) {
290: this ((Object) source, offset, length, lineFeedReplace,
291: conversionSegmentSize);
292: }
293:
294: public FromLineFeed(String text, int offset, int length,
295: String lineFeedReplace) {
296: this (text, offset, length, lineFeedReplace,
297: DEFAULT_CONVERSION_BUFFER_SIZE);
298: }
299:
300: public FromLineFeed(String text, int offset, int length,
301: String lineFeedReplace, int conversionSegmentSize) {
302: this ((Object) text, offset, length, lineFeedReplace,
303: conversionSegmentSize);
304: }
305:
306: private FromLineFeed(Object charArrayOrSequence, int offset,
307: int length, String lineFeedReplace,
308: int conversionSegmentSize) {
309:
310: if (conversionSegmentSize < lineFeedReplace.length()) {
311: throw new IllegalArgumentException(
312: "conversionSegmentSize=" // NOI18N
313: + conversionSegmentSize
314: + " < lineFeedReplace.length()=" // NOI18N
315: + lineFeedReplace.length());
316: }
317:
318: this .charArrayOrSequence = charArrayOrSequence;
319: this .offset = offset;
320: this .endOffset = offset + length;
321: this .lineFeedReplace = lineFeedReplace;
322:
323: convertedText = new Segment();
324: convertedText.array = new char[conversionSegmentSize];
325: }
326:
327: public Segment nextConverted() {
328: if (offset == endOffset) { // no more chars to convert
329: return null;
330: }
331:
332: // [PENDING-PERF] optimization for '\n' -> arraycopy
333:
334: char[] convertedArray = convertedText.array;
335: int convertedArrayLength = convertedArray.length;
336: int convertedOffset = 0;
337:
338: /* Determine whether the source is char-sequence
339: * or char buffer.
340: * Assign either sourceText or sourceArray but not both.
341: */
342: String sourceText;
343: char[] sourceArray;
344: if (charArrayOrSequence instanceof String) {
345: sourceText = (String) charArrayOrSequence;
346: sourceArray = null;
347:
348: } else {
349: sourceArray = (char[]) charArrayOrSequence;
350: sourceText = null;
351: }
352:
353: int lineFeedReplaceLength = lineFeedReplace.length();
354: while (offset < endOffset
355: && convertedArrayLength - convertedOffset >= lineFeedReplaceLength) {
356: char ch = (sourceText != null) ? sourceText
357: .charAt(offset++) : sourceArray[offset++];
358:
359: if (ch == '\n') {
360: for (int i = 0; i < lineFeedReplaceLength; i++) {
361: convertedArray[convertedOffset++] = lineFeedReplace
362: .charAt(i);
363: }
364:
365: } else {
366: convertedArray[convertedOffset++] = ch;
367: }
368: }
369:
370: convertedText.offset = 0;
371: convertedText.count = convertedOffset;
372:
373: return convertedText;
374: }
375:
376: }
377:
378: public static class InitialSeparatorReader extends Reader {
379:
380: private static final int AFTER_CR_STATUS = -1;
381:
382: private static final int INITIAL_STATUS = 0;
383:
384: private static final int CR_SEPARATOR = 1;
385:
386: private static final int LF_SEPARATOR = 2;
387:
388: private static final int CRLF_SEPARATOR = 3;
389:
390: private static final int LS_SEPARATOR = 4;
391:
392: private static final int PS_SEPARATOR = 5;
393:
394: private Reader delegate;
395:
396: private int status = INITIAL_STATUS;
397:
398: public InitialSeparatorReader(Reader delegate) {
399: this .delegate = delegate;
400: }
401:
402: public String getInitialSeparator() {
403: String separator;
404: switch (status) {
405: case CR_SEPARATOR:
406: separator = "\r"; // NOI18N
407: break;
408:
409: case LF_SEPARATOR:
410: separator = "\n"; // NOI18N
411: break;
412:
413: case CRLF_SEPARATOR:
414: separator = "\r\n"; // NOI18N
415: break;
416:
417: case AFTER_CR_STATUS: // '\r' was last char
418: separator = "\r"; // NOI18N
419: break;
420:
421: case LS_SEPARATOR:
422: separator = LS_LS;
423: break;
424:
425: case PS_SEPARATOR:
426: separator = LS_PS;
427: break;
428:
429: default:
430: separator = System.getProperty("line.separator"); // default // NOI18N
431: break;
432: }
433:
434: return separator;
435: }
436:
437: private void resolveSeparator(char ch) {
438: switch (status) {
439: case INITIAL_STATUS:
440: switch (ch) {
441: case '\r':
442: status = AFTER_CR_STATUS;
443: break;
444: case '\n':
445: status = LF_SEPARATOR;
446: break;
447: case LS:
448: status = LS_SEPARATOR;
449: break;
450: case PS:
451: status = PS_SEPARATOR;
452: break;
453: }
454: break;
455:
456: case AFTER_CR_STATUS:
457: switch (ch) {
458: case '\n':
459: status = CRLF_SEPARATOR;
460: break;
461: default:
462: status = CR_SEPARATOR;
463: break;
464: }
465: break;
466:
467: default:
468: switch (ch) {
469: case '\r':
470: status = AFTER_CR_STATUS;
471: break;
472: case '\n':
473: status = LF_SEPARATOR;
474: break;
475: }
476: break;
477: }
478: }
479:
480: private boolean isSeparatorResolved() {
481: return (status > 0);
482: }
483:
484: public void close() throws IOException {
485: if (delegate == null) {
486: return;
487: }
488:
489: delegate.close();
490: delegate = null;
491: }
492:
493: public int read(char[] cbuf, int off, int len)
494: throws IOException {
495: if (delegate == null) {
496: throw new IOException("Reader already closed."); // NOI18N
497: }
498:
499: int readLen = delegate.read(cbuf, off, len);
500:
501: for (int endOff = off + readLen; off < endOff
502: && !isSeparatorResolved(); off++) {
503: resolveSeparator(cbuf[off]);
504: }
505:
506: return readLen;
507: }
508:
509: @Override
510: public int read() throws IOException {
511: if (delegate == null) {
512: throw new IOException("Reader already closed."); // NOI18N
513: }
514:
515: int r = delegate.read();
516: if (r != -1 && !isSeparatorResolved()) {
517: resolveSeparator((char) r);
518: }
519:
520: return r;
521: }
522:
523: }
524:
525: }
|