001: /*
002: * BufferIORequest.java - I/O request
003: * :tabSize=8:indentSize=8:noTabs=false:
004: * :folding=explicit:collapseFolds=1:
005: *
006: * Copyright (C) 2000, 2004 Slava Pestov
007: *
008: * This program is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU General Public License
010: * as published by the Free Software Foundation; either version 2
011: * of the License, or any later version.
012: *
013: * This program is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: * GNU General Public License for more details.
017: *
018: * You should have received a copy of the GNU General Public License
019: * along with this program; if not, write to the Free Software
020: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
021: */
022:
023: package org.gjt.sp.jedit.bufferio;
024:
025: //{{{ Imports
026: import java.io.BufferedOutputStream;
027: import java.io.CharConversionException;
028: import java.io.IOException;
029: import java.io.InputStream;
030: import java.io.OutputStream;
031: import java.io.Reader;
032: import java.io.Writer;
033: import java.nio.charset.CharacterCodingException;
034:
035: import javax.swing.text.Segment;
036:
037: import org.gjt.sp.jedit.Buffer;
038: import org.gjt.sp.jedit.MiscUtilities;
039: import org.gjt.sp.jedit.View;
040: import org.gjt.sp.jedit.jEdit;
041: import org.gjt.sp.jedit.buffer.JEditBuffer;
042: import org.gjt.sp.jedit.io.VFS;
043: import org.gjt.sp.jedit.io.Encoding;
044: import org.gjt.sp.jedit.io.EncodingServer;
045: import org.gjt.sp.util.IntegerArray;
046: import org.gjt.sp.util.SegmentBuffer;
047: import org.gjt.sp.util.WorkRequest;
048:
049: //}}}
050:
051: /**
052: * A buffer I/O request.
053: * @author Slava Pestov
054: * @version $Id: BufferIORequest.java 9494 2007-05-04 12:42:18Z k_satoda $
055: */
056: public abstract class BufferIORequest extends WorkRequest {
057: //{{{ Constants
058:
059: /**
060: * Size of I/O buffers.
061: */
062: public static final int IOBUFSIZE = 32768;
063:
064: /**
065: * Number of lines per progress increment.
066: */
067: public static final int PROGRESS_INTERVAL = 300;
068:
069: public static final String LOAD_DATA = "BufferIORequest__loadData";
070: public static final String END_OFFSETS = "BufferIORequest__endOffsets";
071: public static final String NEW_PATH = "BufferIORequest__newPath";
072:
073: /**
074: * Buffer boolean property set when an error occurs.
075: */
076: public static final String ERROR_OCCURRED = "BufferIORequest__error";
077:
078: // These are no longer used but still here only for compatibility.
079: @Deprecated
080: public static final int UTF8_MAGIC_1 = 0xef;
081: @Deprecated
082: public static final int UTF8_MAGIC_2 = 0xbb;
083: @Deprecated
084: public static final int UTF8_MAGIC_3 = 0xbf;
085: @Deprecated
086: public static final int UNICODE_MAGIC_1 = 0xfe;
087: @Deprecated
088: public static final int UNICODE_MAGIC_2 = 0xff;
089: @Deprecated
090: public static final int XML_PI_LENGTH = 50;
091: @Deprecated
092: public static final int GZIP_MAGIC_1 = 0x1f;
093: @Deprecated
094: public static final int GZIP_MAGIC_2 = 0x8b;
095:
096: //}}}
097:
098: //{{{ Instance variables
099: protected final View view;
100: protected final Buffer buffer;
101: protected final Object session;
102: protected final VFS vfs;
103: protected String path;
104: protected final String markersPath;
105:
106: //}}}
107:
108: //{{{ BufferIORequest constructor
109: /**
110: * Creates a new buffer I/O request.
111: * @param view The view
112: * @param buffer The buffer
113: * @param session The VFS session
114: * @param vfs The VFS
115: * @param path The path
116: */
117: protected BufferIORequest(View view, Buffer buffer, Object session,
118: VFS vfs, String path) {
119: this .view = view;
120: this .buffer = buffer;
121: this .session = session;
122: this .vfs = vfs;
123: this .path = path;
124:
125: markersPath = Buffer.getMarkersPath(vfs, path);
126: } //}}}
127:
128: //{{{ toString() method
129: public String toString() {
130: return getClass().getName() + '[' + buffer + ']';
131: } //}}}
132:
133: //{{{ getCharIOBufferSize() method
134: /**
135: * Size of character I/O buffers.
136: */
137: public static int getCharIOBufferSize() {
138: return IOBUFSIZE;
139: } //}}}
140:
141: //{{{ getByteIOBufferSize() method
142: /**
143: * Size of byte I/O buffers.
144: */
145: public static int getByteIOBufferSize() {
146: // 2 is sizeof char in byte;
147: return IOBUFSIZE * 2;
148: } //}}}
149:
150: //{{{ autodetect() method
151: /**
152: * Tries to detect if the stream is gzipped, and if it has an encoding
153: * specified with an XML PI.
154: */
155: protected Reader autodetect(InputStream in) throws IOException {
156: return MiscUtilities.autodetect(in, buffer);
157: } //}}}
158:
159: //{{{ read() method
160: protected SegmentBuffer read(Reader in, long length, boolean insert)
161: throws IOException {
162: /* we guess an initial size for the array */
163: IntegerArray endOffsets = new IntegerArray(Math.max(1,
164: (int) (length / 50)));
165:
166: // only true if the file size is known
167: boolean trackProgress = !buffer.isTemporary() && length != 0;
168:
169: if (trackProgress) {
170: setMaximum(length);
171: setValue(0);
172: }
173:
174: // if the file size is not known, start with a resonable
175: // default buffer size
176: if (length == 0)
177: length = IOBUFSIZE;
178:
179: SegmentBuffer seg = new SegmentBuffer((int) length + 1);
180:
181: char[] buf = new char[IOBUFSIZE];
182:
183: /* Number of characters in 'buf' array.
184: InputStream.read() doesn't always fill the
185: array (eg, the file size is not a multiple of
186: IOBUFSIZE, or it is a GZipped file, etc) */
187: int len;
188:
189: // True if a \n was read after a \r. Usually
190: // means this is a DOS/Windows file
191: boolean CRLF = false;
192:
193: // A \r was read, hence a MacOS file
194: boolean CROnly = false;
195:
196: // Was the previous read character a \r?
197: // If we read a \n and this is true, we assume
198: // we have a DOS/Windows file
199: boolean lastWasCR = false;
200:
201: // Number of lines read. Every 100 lines, we update the
202: // progress bar
203: int lineCount = 0;
204:
205: while ((len = in.read(buf, 0, buf.length)) != -1) {
206: // Offset of previous line, relative to
207: // the start of the I/O buffer (NOT
208: // relative to the start of the document)
209: int lastLine = 0;
210:
211: for (int i = 0; i < len; i++) {
212: // Look for line endings.
213: switch (buf[i]) {
214: case '\r':
215: // If we read a \r and
216: // lastWasCR is also true,
217: // it is probably a Mac file
218: // (\r\r in stream)
219: if (lastWasCR) {
220: CROnly = true;
221: CRLF = false;
222: }
223: // Otherwise set a flag,
224: // so that \n knows that last
225: // was a \r
226: else {
227: lastWasCR = true;
228: }
229:
230: // Insert a line
231: seg.append(buf, lastLine, i - lastLine);
232: seg.append('\n');
233: endOffsets.add(seg.count);
234: if (trackProgress
235: && lineCount++ % PROGRESS_INTERVAL == 0)
236: setValue(seg.count);
237:
238: // This is i+1 to take the
239: // trailing \n into account
240: lastLine = i + 1;
241: break;
242: case '\n':
243: /* If lastWasCR is true, we just read a \r followed
244: by a \n. We specify that this is a Windows file,
245: but take no further action and just ignore the \r. */
246: if (lastWasCR) {
247: CROnly = false;
248: CRLF = true;
249: lastWasCR = false;
250: /* Bump lastLine so that the next line doesn't erronously
251: pick up the \r */
252: lastLine = i + 1;
253: }
254: /* Otherwise, we found a \n that follows some other
255: * character, hence we have a Unix file */
256: else {
257: CROnly = false;
258: CRLF = false;
259: seg.append(buf, lastLine, i - lastLine);
260: seg.append('\n');
261: endOffsets.add(seg.count);
262: if (trackProgress
263: && lineCount++ % PROGRESS_INTERVAL == 0)
264: setValue(seg.count);
265: lastLine = i + 1;
266: }
267: break;
268: default:
269: /* If we find some other character that follows
270: a \r, so it is not a Windows file, and probably
271: a Mac file */
272: if (lastWasCR) {
273: CROnly = true;
274: CRLF = false;
275: lastWasCR = false;
276: }
277: break;
278: }
279: }
280:
281: if (trackProgress)
282: setValue(seg.count);
283:
284: // Add remaining stuff from buffer
285: seg.append(buf, lastLine, len - lastLine);
286: }
287:
288: setAbortable(false);
289:
290: String lineSeparator;
291: if (seg.count == 0) {
292: // fix for "[ 865589 ] 0-byte files should open using
293: // the default line seperator"
294: lineSeparator = jEdit.getProperty("buffer.lineSeparator",
295: System.getProperty("line.separator"));
296: } else if (CRLF)
297: lineSeparator = "\r\n";
298: else if (CROnly)
299: lineSeparator = "\r";
300: else
301: lineSeparator = "\n";
302:
303: // Chop trailing newline and/or ^Z (if any)
304: int bufferLength = seg.count;
305: if (bufferLength != 0) {
306: char ch = seg.array[bufferLength - 1];
307: if (ch == 0x1a /* DOS ^Z */)
308: seg.count--;
309: }
310:
311: buffer.setBooleanProperty(Buffer.TRAILING_EOL, false);
312: if (bufferLength != 0
313: && jEdit.getBooleanProperty("stripTrailingEOL")) {
314: char ch = seg.array[bufferLength - 1];
315: if (ch == '\n') {
316: buffer.setBooleanProperty(Buffer.TRAILING_EOL, true);
317: seg.count--;
318: endOffsets.setSize(endOffsets.getSize() - 1);
319: }
320: }
321:
322: // add a line marker at the end for proper offset manager
323: // operation
324: endOffsets.add(seg.count + 1);
325:
326: // to avoid having to deal with read/write locks and such,
327: // we insert the loaded data into the buffer in the
328: // post-load cleanup runnable, which runs in the AWT thread.
329: if (!insert) {
330: buffer.setProperty(LOAD_DATA, seg);
331: buffer.setProperty(END_OFFSETS, endOffsets);
332: buffer.setProperty(NEW_PATH, path);
333: if (lineSeparator != null)
334: buffer.setProperty(JEditBuffer.LINESEP, lineSeparator);
335: }
336:
337: // used in insert()
338: return seg;
339: } //}}}
340:
341: //{{{ write() method
342: protected void write(Buffer buffer, OutputStream out)
343: throws IOException {
344: String encodingName = buffer
345: .getStringProperty(JEditBuffer.ENCODING);
346: Encoding encoding = EncodingServer.getEncoding(encodingName);
347: Writer writer = encoding
348: .getTextWriter(new BufferedOutputStream(out,
349: getByteIOBufferSize()));
350:
351: Segment lineSegment = new Segment();
352: String newline = buffer.getStringProperty(JEditBuffer.LINESEP);
353: if (newline == null)
354: newline = System.getProperty("line.separator");
355:
356: final int bufferLineCount = buffer.getLineCount();
357: setMaximum(bufferLineCount / PROGRESS_INTERVAL);
358: setValue(0);
359:
360: int i = 0;
361: while (i < bufferLineCount) {
362: buffer.getLineText(i, lineSegment);
363: try {
364: writer.write(lineSegment.array, lineSegment.offset,
365: lineSegment.count);
366: if (i < bufferLineCount - 1
367: || (jEdit
368: .getBooleanProperty("stripTrailingEOL") && buffer
369: .getBooleanProperty(Buffer.TRAILING_EOL))) {
370: writer.write(newline);
371: }
372: } catch (CharacterCodingException e) {
373: String message = getWriteEncodingErrorMessage(
374: encodingName, encoding, lineSegment, i);
375: IOException wrapping = new CharConversionException(
376: message);
377: wrapping.initCause(e);
378: throw wrapping;
379: }
380:
381: if (++i % PROGRESS_INTERVAL == 0)
382: setValue(i / PROGRESS_INTERVAL);
383: }
384: writer.flush();
385: } //}}}
386:
387: //{{{ Private members
388:
389: //{{{ createEncodingErrorMessage() method
390: private static String getWriteEncodingErrorMessage(
391: String encodingName, Encoding encoding, Segment line,
392: int lineIndex) {
393: String args[] = { encodingName,
394: Integer.toString(lineIndex + 1), "UNKNOWN", // column
395: "UNKNOWN" // the character
396: };
397: try {
398: int charIndex = getFirstGuiltyCharacterIndex(encoding, line);
399: if (0 <= charIndex && charIndex < line.count) {
400: char c = line.array[line.offset + charIndex];
401: args[2] = Integer.toString(charIndex + 1);
402: args[3] = "'" + c + "' (U+"
403: + Integer.toHexString(c).toUpperCase() + ")";
404: }
405: } catch (Exception e) {
406: // Ignore.
407: }
408: return jEdit.getProperty("ioerror.write-encoding-error", args);
409: } //}}}
410:
411: //{{{ getFirstGuiltyCharacterIndex() method
412: // Look for the first character which causes encoding error.
413: private static int getFirstGuiltyCharacterIndex(Encoding encoding,
414: Segment line) throws IOException {
415: if (line.count < 1) {
416: return -1;
417: } else if (line.count == 1) {
418: return 0;
419: }
420:
421: Writer tester = encoding.getTextWriter(new OutputStream() {
422: public void write(int b) {
423: }
424: });
425: for (int i = 0; i < line.count; ++i) {
426: try {
427: tester.write(line.array[line.offset + i]);
428: } catch (CharacterCodingException e) {
429: return i;
430: }
431: }
432: return -1;
433: } //}}}
434:
435: //}}}
436: }
|