/* * BufferIORequest.java - I/O request * :tabSize=4:indentSize=4:noTabs=false: * :folding=explicit:collapseFolds=1: * * Copyright (C) 2000, 2004 Slava Pestov * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.gjt.sp.jedit.bufferio; //{{{ Imports import java.io.BufferedOutputStream; import java.io.CharConversionException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; import java.nio.charset.CharacterCodingException; import javax.swing.text.Segment; import org.gjt.sp.jedit.Buffer; import org.gjt.sp.jedit.MiscUtilities; import org.gjt.sp.jedit.View; import org.gjt.sp.jedit.jEdit; import org.gjt.sp.jedit.buffer.JEditBuffer; import org.gjt.sp.jedit.io.VFS; import org.gjt.sp.jedit.io.VFSManager; import org.gjt.sp.jedit.io.Encoding; import org.gjt.sp.jedit.io.EncodingServer; import org.gjt.sp.util.IntegerArray; import org.gjt.sp.util.SegmentBuffer; import org.gjt.sp.util.Log; //}}} /** * A buffer I/O request. * @author Slava Pestov * @version $Id$ */ public abstract class BufferIORequest extends IoTask { //{{{ Constants /** * Size of I/O buffers. */ public static final int IOBUFSIZE = 32768; /** * Number of lines per progress increment. */ public static final int PROGRESS_INTERVAL = 300; public static final String LOAD_DATA = "BufferIORequest__loadData"; public static final String END_OFFSETS = "BufferIORequest__endOffsets"; public static final String NEW_PATH = "BufferIORequest__newPath"; /** * Buffer boolean property set when an error occurs. */ public static final String ERROR_OCCURRED = "BufferIORequest__error"; //}}} //{{{ Instance variables protected final View view; protected final Buffer buffer; protected final Object session; protected final VFS vfs; protected String path; protected final String markersPath; //}}} //{{{ BufferIORequest constructor /** * Creates a new buffer I/O request. * @param view The view * @param buffer The buffer * @param session The VFS session * @param vfs The VFS * @param path The path */ protected BufferIORequest(View view, Buffer buffer, Object session, VFS vfs, String path) { super(); this.view = view; this.buffer = buffer; this.session = session; this.vfs = vfs; this.path = path; markersPath = Buffer.getMarkersPath(vfs, path); //buffer.setIoTask(this); } //}}} //{{{ toString() method public String toString() { return getClass().getName() + '[' + buffer + ']'; } //}}} //{{{ getCharIOBufferSize() method /** * Size of character I/O buffers. */ public static int getCharIOBufferSize() { return IOBUFSIZE; } //}}} //{{{ getByteIOBufferSize() method /** * Size of byte I/O buffers. */ public static int getByteIOBufferSize() { // 2 is sizeof char in byte; return IOBUFSIZE * 2; } //}}} //{{{ autodetect() method /** * Tries to detect if the stream is gzipped, and if it has an encoding * specified with an XML PI. */ protected Reader autodetect(InputStream in) throws IOException { return MiscUtilities.autodetect(in, buffer); } //}}} //{{{ read() method protected SegmentBuffer read(Reader in, long length, boolean insert) throws IOException, InterruptedException { /* we guess an initial size for the array */ IntegerArray endOffsets = new IntegerArray( Math.max(1,(int)(length / 50))); // only true if the file size is known boolean trackProgress = !buffer.isTemporary() && length != 0; if(trackProgress) { setMaximum(length); setValue(0); } // if the file size is not known, start with a resonable // default buffer size if(length == 0) length = IOBUFSIZE; SegmentBuffer seg = new SegmentBuffer((int)length + 1); char[] buf = new char[IOBUFSIZE]; /* Number of characters in 'buf' array. InputStream.read() doesn't always fill the array (eg, the file size is not a multiple of IOBUFSIZE, or it is a GZipped file, etc) */ int len; // True if a \n was read after a \r. Usually // means this is a DOS/Windows file boolean CRLF = false; // A \r was read, hence a MacOS file boolean CROnly = false; // Was the previous read character a \r? // If we read a \n and this is true, we assume // we have a DOS/Windows file boolean lastWasCR = false; // Number of lines read. Every 100 lines, we update the // progress bar int lineCount = 0; while((len = in.read(buf,0,buf.length)) != -1) { if(Thread.interrupted()) throw new InterruptedException(); // Offset of previous line, relative to // the start of the I/O buffer (NOT // relative to the start of the document) int lastLine = 0; for(int i = 0; i < len; i++) { // Look for line endings. switch(buf[i]) { case '\r': // If we read a \r and // lastWasCR is also true, // it is probably a Mac file // (\r\r in stream) if(lastWasCR) { CROnly = true; CRLF = false; } // Otherwise set a flag, // so that \n knows that last // was a \r else { lastWasCR = true; } // Insert a line seg.append(buf,lastLine,i - lastLine); seg.append('\n'); endOffsets.add(seg.count); if(trackProgress && lineCount++ % PROGRESS_INTERVAL == 0) setValue(seg.count); // This is i+1 to take the // trailing \n into account lastLine = i + 1; break; case '\n': /* If lastWasCR is true, we just read a \r followed by a \n. We specify that this is a Windows file, but take no further action and just ignore the \r. */ if(lastWasCR) { CROnly = false; CRLF = true; lastWasCR = false; /* Bump lastLine so that the next line doesn't erronously pick up the \r */ lastLine = i + 1; } /* Otherwise, we found a \n that follows some other * character, hence we have a Unix file */ else { CROnly = false; CRLF = false; seg.append(buf,lastLine, i - lastLine); seg.append('\n'); endOffsets.add(seg.count); if(trackProgress && lineCount++ % PROGRESS_INTERVAL == 0) setValue(seg.count); lastLine = i + 1; } break; default: /* If we find some other character that follows a \r, so it is not a Windows file, and probably a Mac file */ if(lastWasCR) { CROnly = true; CRLF = false; lastWasCR = false; } break; } } if(trackProgress) setValue(seg.count); // Add remaining stuff from buffer seg.append(buf,lastLine,len - lastLine); } setCancellable(false); String lineSeparator; if(seg.count == 0) { // fix for "[ 865589 ] 0-byte files should open using // the default line seperator" lineSeparator = jEdit.getProperty( "buffer.lineSeparator", System.getProperty("line.separator")); } else if(CRLF) lineSeparator = "\r\n"; else if(CROnly) lineSeparator = "\r"; else lineSeparator = "\n"; // Chop trailing newline and/or ^Z (if any) int bufferLength = seg.count; if(bufferLength != 0) { char ch = seg.array[bufferLength - 1]; if(ch == 0x1a /* DOS ^Z */) seg.count--; } buffer.setBooleanProperty(Buffer.TRAILING_EOL,false); if(bufferLength != 0 && jEdit.getBooleanProperty("stripTrailingEOL")) { char ch = seg.array[bufferLength - 1]; if(ch == '\n') { buffer.setBooleanProperty(Buffer.TRAILING_EOL,true); seg.count--; endOffsets.setSize(endOffsets.getSize() - 1); } } // add a line marker at the end for proper offset manager // operation endOffsets.add(seg.count + 1); // to avoid having to deal with read/write locks and such, // we insert the loaded data into the buffer in the // post-load cleanup runnable, which runs in the AWT thread. if(!insert) { buffer.setProperty(LOAD_DATA,seg); buffer.setProperty(END_OFFSETS,endOffsets); buffer.setProperty(NEW_PATH,path); if(lineSeparator != null) buffer.setProperty(JEditBuffer.LINESEP,lineSeparator); } // used in insert() return seg; } //}}} //{{{ write() method protected void write(Buffer buffer, OutputStream out) throws IOException, InterruptedException { String encodingName = buffer.getStringProperty(JEditBuffer.ENCODING); Encoding encoding = EncodingServer.getEncoding(encodingName); Writer writer = encoding.getTextWriter( new BufferedOutputStream(out, getByteIOBufferSize())); Segment lineSegment = new Segment(); String newline = buffer.getStringProperty(JEditBuffer.LINESEP); if(newline == null) newline = System.getProperty("line.separator"); final int bufferLineCount = buffer.getLineCount(); setMaximum(bufferLineCount / PROGRESS_INTERVAL); setValue(0); int i = 0; while(i < bufferLineCount) { if(Thread.interrupted()) throw new InterruptedException(); buffer.getLineText(i,lineSegment); try { writer.write(lineSegment.array, lineSegment.offset, lineSegment.count); if(i < bufferLineCount - 1 || (jEdit.getBooleanProperty("stripTrailingEOL") && buffer.getBooleanProperty(Buffer.TRAILING_EOL))) { writer.write(newline); } } catch(CharacterCodingException e) { String message = getWriteEncodingErrorMessage( encodingName, encoding, lineSegment, i); IOException wrapping = new CharConversionException(message); wrapping.initCause(e); throw wrapping; } if(++i % PROGRESS_INTERVAL == 0) setValue(i / PROGRESS_INTERVAL); } writer.flush(); } //}}} //{{{ endSessionQuietly() method protected void endSessionQuietly() { try { vfs._endVFSSession(session,view); } catch(Exception e) { Log.log(Log.ERROR,this,e); String[] pp = { e.toString() }; VFSManager.error(view,path,"ioerror.read-error",pp); buffer.setBooleanProperty(ERROR_OCCURRED,true); } } //}}} //{{{ Private members //{{{ createEncodingErrorMessage() method private static String getWriteEncodingErrorMessage( String encodingName, Encoding encoding, Segment line, int lineIndex) { String args[] = { encodingName, Integer.toString(lineIndex + 1), "UNKNOWN", // column "UNKNOWN" // the character }; try { int charIndex = getFirstGuiltyCharacterIndex(encoding, line); if(0 <= charIndex && charIndex < line.count) { char c = line.array[line.offset + charIndex]; args[2] = Integer.toString(charIndex + 1); args[3] = "'" + c + "' (U+" + Integer.toHexString(c).toUpperCase() + ")"; } } catch(Exception e) { // Ignore. } return jEdit.getProperty("ioerror.write-encoding-error", args); } //}}} //{{{ getFirstGuiltyCharacterIndex() method // Look for the first character which causes encoding error. private static int getFirstGuiltyCharacterIndex(Encoding encoding, Segment line) throws IOException { if(line.count < 1) { return -1; } else if(line.count == 1) { return 0; } Writer tester = encoding.getTextWriter( new OutputStream() { public void write(int b) {} }); for(int i = 0; i < line.count; ++i) { try { tester.write(line.array[line.offset + i]); } catch(CharacterCodingException e) { return i; } } return -1; } //}}} //}}} }