PageRenderTime 219ms CodeModel.GetById 141ms app.highlight 57ms RepoModel.GetById 14ms app.codeStats 0ms

/jEdit/tags/jedit-4-1-pre5/org/gjt/sp/jedit/buffer/BufferIORequest.java

#
Java | 863 lines | 606 code | 106 blank | 151 comment | 100 complexity | 7c395363375ed842ad886d4d3631343d MD5 | raw file
  1/*
  2 * BufferIORequest.java - I/O request
  3 * :tabSize=8:indentSize=8:noTabs=false:
  4 * :folding=explicit:collapseFolds=1:
  5 *
  6 * Copyright (C) 2000, 2001 Slava Pestov
  7 *
  8 * This program is free software; you can redistribute it and/or
  9 * modify it under the terms of the GNU General Public License
 10 * as published by the Free Software Foundation; either version 2
 11 * of the License, or any later version.
 12 *
 13 * This program is distributed in the hope that it will be useful,
 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 16 * GNU General Public License for more details.
 17 *
 18 * You should have received a copy of the GNU General Public License
 19 * along with this program; if not, write to the Free Software
 20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 21 */
 22
 23package org.gjt.sp.jedit.buffer;
 24
 25//{{{ Imports
 26import javax.swing.text.Segment;
 27import java.io.*;
 28import java.util.zip.*;
 29import java.util.Vector;
 30import org.gjt.sp.jedit.io.*;
 31import org.gjt.sp.jedit.*;
 32import org.gjt.sp.util.*;
 33//}}}
 34
 35/**
 36 * A buffer I/O request.
 37 * @author Slava Pestov
 38 * @version $Id: BufferIORequest.java 4316 2002-08-20 19:11:47Z spestov $
 39 */
 40public class BufferIORequest extends WorkRequest
 41{
 42	//{{{ Constants
 43	/**
 44	 * Size of I/O buffers.
 45	 */
 46	public static final int IOBUFSIZE = 32768;
 47
 48	/**
 49	 * Number of lines per progress increment.
 50	 */
 51	public static final int PROGRESS_INTERVAL = 300;
 52
 53	public static final String LOAD_DATA = "BufferIORequest__loadData";
 54	public static final String END_OFFSETS = "BufferIORequest__endOffsets";
 55	public static final String NEW_PATH = "BufferIORequest__newPath";
 56
 57	/**
 58	 * Buffer boolean property set when an error occurs.
 59	 */
 60	public static final String ERROR_OCCURRED = "BufferIORequest__error";
 61
 62	/**
 63	 * A file load request.
 64	 */
 65	public static final int LOAD = 0;
 66
 67	/**
 68	 * A file save request.
 69	 */
 70	public static final int SAVE = 1;
 71
 72	/**
 73	 * An autosave request. Only supported for local files.
 74	 */
 75	public static final int AUTOSAVE = 2;
 76
 77	/**
 78	 * An insert file request.
 79	 */
 80	public static final int INSERT = 3;
 81
 82	/**
 83	 * Magic number used for auto-detecting Unicode and GZIP files.
 84	 */
 85	public static final int GZIP_MAGIC_1 = 0x1f;
 86	public static final int GZIP_MAGIC_2 = 0x8b;
 87	public static final int UNICODE_MAGIC_1 = 0xfe;
 88	public static final int UNICODE_MAGIC_2 = 0xff;
 89	//}}}
 90
 91	//{{{ BufferIORequest constructor
 92	/**
 93	 * Creates a new buffer I/O request.
 94	 * @param type The request type
 95	 * @param view The view
 96	 * @param buffer The buffer
 97	 * @param session The VFS session
 98	 * @param vfs The VFS
 99	 * @param path The path
100	 */
101	public BufferIORequest(int type, View view, Buffer buffer,
102		Object session, VFS vfs, String path)
103	{
104		this.type = type;
105		this.view = view;
106		this.buffer = buffer;
107		this.session = session;
108		this.vfs = vfs;
109		this.path = path;
110
111		markersPath = vfs.getParentOfPath(path)
112			+ '.' + vfs.getFileName(path)
113			+ ".marks";
114	} //}}}
115
116	//{{{ run() method
117	public void run()
118	{
119		switch(type)
120		{
121		case LOAD:
122			load();
123			break;
124		case SAVE:
125			save();
126			break;
127		case AUTOSAVE:
128			autosave();
129			break;
130		case INSERT:
131			insert();
132			break;
133		}
134	} //}}}
135
136	//{{{ toString() method
137	public String toString()
138	{
139		String typeString;
140		switch(type)
141		{
142		case LOAD:
143			typeString = "LOAD";
144			break;
145		case SAVE:
146			typeString = "SAVE";
147			break;
148		case AUTOSAVE:
149			typeString = "AUTOSAVE";
150			break;
151		default:
152			typeString = "UNKNOWN!!!";
153		}
154
155		return getClass().getName() + "[type=" + typeString
156			+ ",buffer=" + buffer + "]";
157	} //}}}
158
159	//{{{ Private members
160
161	//{{{ Instance variables
162	private int type;
163	private View view;
164	private Buffer buffer;
165	private Object session;
166	private VFS vfs;
167	private String path;
168	private String markersPath;
169	//}}}
170
171	//{{{ load() method
172	private void load()
173	{
174		InputStream in = null;
175
176		try
177		{
178			try
179			{
180				String[] args = { vfs.getFileName(path) };
181				setAbortable(true);
182				if(!buffer.isTemporary())
183				{
184					setStatus(jEdit.getProperty("vfs.status.load",args));
185					setProgressValue(0);
186				}
187
188				path = vfs._canonPath(session,path,view);
189
190				VFS.DirectoryEntry entry = vfs._getDirectoryEntry(
191					session,path,view);
192				long length;
193				if(entry != null)
194					length = entry.length;
195				else
196					length = 0L;
197
198				in = vfs._createInputStream(session,path,false,view);
199				if(in == null)
200					return;
201
202				in = new BufferedInputStream(in);
203
204				if(in.markSupported())
205				{
206					in.mark(2);
207					int b1 = in.read();
208					int b2 = in.read();
209					in.reset();
210
211					if(b1 == GZIP_MAGIC_1 && b2 == GZIP_MAGIC_2)
212					{
213						in = new GZIPInputStream(in);
214						buffer.setBooleanProperty(Buffer.GZIPPED,true);
215					}
216					else if((b1 == UNICODE_MAGIC_1 && b2 == UNICODE_MAGIC_2)
217						|| (b1 == UNICODE_MAGIC_2 && b2 == UNICODE_MAGIC_1))
218					{
219						buffer.setProperty(Buffer.ENCODING,"Unicode");
220					}
221				}
222				else if(path.toLowerCase().endsWith(".gz"))
223					in = new GZIPInputStream(in);
224
225				read(buffer,in,length);
226				buffer.setNewFile(false);
227			}
228			catch(CharConversionException ch)
229			{
230				Log.log(Log.ERROR,this,ch);
231				Object[] pp = { buffer.getProperty(Buffer.ENCODING),
232					ch.toString() };
233				VFSManager.error(view,path,"ioerror.encoding-error",pp);
234
235				buffer.setBooleanProperty(ERROR_OCCURRED,true);
236			}
237			catch(UnsupportedEncodingException uu)
238			{
239				Log.log(Log.ERROR,this,uu);
240				Object[] pp = { buffer.getProperty(Buffer.ENCODING),
241					uu.toString() };
242				VFSManager.error(view,path,"ioerror.encoding-error",pp);
243
244				buffer.setBooleanProperty(ERROR_OCCURRED,true);
245			}
246			catch(IOException io)
247			{
248				Log.log(Log.ERROR,this,io);
249				Object[] pp = { io.toString() };
250				VFSManager.error(view,path,"ioerror.read-error",pp);
251
252				buffer.setBooleanProperty(ERROR_OCCURRED,true);
253			}
254			catch(OutOfMemoryError oom)
255			{
256				Log.log(Log.ERROR,this,oom);
257				VFSManager.error(view,path,"out-of-memory-error",null);
258
259				buffer.setBooleanProperty(ERROR_OCCURRED,true);
260			}
261
262			if(jEdit.getBooleanProperty("persistentMarkers"))
263			{
264				try
265				{
266					String[] args = { vfs.getFileName(path) };
267					if(!buffer.isTemporary())
268						setStatus(jEdit.getProperty("vfs.status.load-markers",args));
269					setAbortable(true);
270
271					in = vfs._createInputStream(session,markersPath,true,view);
272					if(in != null)
273						readMarkers(buffer,in);
274				}
275				catch(IOException io)
276				{
277					// ignore
278				}
279			}
280		}
281		catch(WorkThread.Abort a)
282		{
283			if(in != null)
284			{
285				try
286				{
287					in.close();
288				}
289				catch(IOException io)
290				{
291				}
292			}
293
294			buffer.setBooleanProperty(ERROR_OCCURRED,true);
295		}
296		finally
297		{
298			try
299			{
300				vfs._endVFSSession(session,view);
301			}
302			catch(IOException io)
303			{
304				Log.log(Log.ERROR,this,io);
305				String[] pp = { io.toString() };
306				VFSManager.error(view,path,"ioerror.read-error",pp);
307
308				buffer.setBooleanProperty(ERROR_OCCURRED,true);
309			}
310			catch(WorkThread.Abort a)
311			{
312				buffer.setBooleanProperty(ERROR_OCCURRED,true);
313			}
314		}
315	} //}}}
316
317	//{{{ read() method
318	private void read(Buffer buffer, InputStream _in, long length)
319		throws IOException
320	{
321		IntegerArray endOffsets = new IntegerArray();
322
323		// only true if the file size is known
324		boolean trackProgress = (!buffer.isTemporary() && length != 0);
325
326		if(trackProgress)
327		{
328			setProgressValue(0);
329			setProgressMaximum((int)length);
330		}
331
332		// if the file size is not known, start with a resonable
333		// default buffer size
334		if(length == 0)
335			length = IOBUFSIZE;
336
337		SegmentBuffer seg = new SegmentBuffer((int)length + 1);
338
339		InputStreamReader in = new InputStreamReader(_in,
340			buffer.getStringProperty(Buffer.ENCODING));
341		char[] buf = new char[IOBUFSIZE];
342
343		// Number of characters in 'buf' array.
344		// InputStream.read() doesn't always fill the
345		// array (eg, the file size is not a multiple of
346		// IOBUFSIZE, or it is a GZipped file, etc)
347		int len;
348
349		// True if a \n was read after a \r. Usually
350		// means this is a DOS/Windows file
351		boolean CRLF = false;
352
353		// A \r was read, hence a MacOS file
354		boolean CROnly = false;
355
356		// Was the previous read character a \r?
357		// If we read a \n and this is true, we assume
358		// we have a DOS/Windows file
359		boolean lastWasCR = false;
360
361		// Number of lines read. Every 100 lines, we update the
362		// progress bar
363		int lineCount = 0;
364
365		while((len = in.read(buf,0,buf.length)) != -1)
366		{
367			// Offset of previous line, relative to
368			// the start of the I/O buffer (NOT
369			// relative to the start of the document)
370			int lastLine = 0;
371
372			for(int i = 0; i < len; i++)
373			{
374				// Look for line endings.
375				switch(buf[i])
376				{
377				case '\r':
378					// If we read a \r and
379					// lastWasCR is also true,
380					// it is probably a Mac file
381					// (\r\r in stream)
382					if(lastWasCR)
383					{
384						CROnly = true;
385						CRLF = false;
386					}
387					// Otherwise set a flag,
388					// so that \n knows that last
389					// was a \r
390					else
391					{
392						lastWasCR = true;
393					}
394
395					// Insert a line
396					seg.append(buf,lastLine,i -
397						lastLine);
398					endOffsets.add(seg.count);
399					seg.append('\n');
400					if(trackProgress && lineCount++ % PROGRESS_INTERVAL == 0)
401						setProgressValue(seg.count);
402
403					// This is i+1 to take the
404					// trailing \n into account
405					lastLine = i + 1;
406					break;
407				case '\n':
408					// If lastWasCR is true,
409					// we just read a \r followed
410					// by a \n. We specify that
411					// this is a Windows file,
412					// but take no further
413					// action and just ignore
414					// the \r.
415					if(lastWasCR)
416					{
417						CROnly = false;
418						CRLF = true;
419						lastWasCR = false;
420						// Bump lastLine so
421						// that the next line
422						// doesn't erronously
423						// pick up the \r
424						lastLine = i + 1;
425					}
426					// Otherwise, we found a \n
427					// that follows some other
428					// character, hence we have
429					// a Unix file
430					else
431					{
432						CROnly = false;
433						CRLF = false;
434						seg.append(buf,lastLine,
435							i - lastLine);
436						endOffsets.add(seg.count);
437						seg.append('\n');
438						if(trackProgress && lineCount++ % PROGRESS_INTERVAL == 0)
439							setProgressValue(seg.count);
440						lastLine = i + 1;
441					}
442					break;
443				default:
444					// If we find some other
445					// character that follows
446					// a \r, so it is not a
447					// Windows file, and probably
448					// a Mac file
449					if(lastWasCR)
450					{
451						CROnly = true;
452						CRLF = false;
453						lastWasCR = false;
454					}
455					break;
456				}
457			}
458
459			if(trackProgress)
460				setProgressValue(seg.count);
461
462			// Add remaining stuff from buffer
463			seg.append(buf,lastLine,len - lastLine);
464		}
465
466		setAbortable(false);
467
468		String lineSeparator;
469		if(CRLF)
470			lineSeparator = "\r\n";
471		else if(CROnly)
472			lineSeparator = "\r";
473		else
474			lineSeparator = "\n";
475
476		in.close();
477
478		// Chop trailing newline and/or ^Z (if any)
479		int bufferLength = seg.count;
480		if(bufferLength != 0)
481		{
482			char ch = seg.array[bufferLength - 1];
483			if(ch == 0x1a /* DOS ^Z */)
484				seg.count--;
485		}
486
487		buffer.setBooleanProperty(Buffer.TRAILING_EOL,false);
488		if(bufferLength != 0 && jEdit.getBooleanProperty("stripTrailingEOL"))
489		{
490			char ch = seg.array[bufferLength - 1];
491			if(ch == '\n')
492			{
493				buffer.setBooleanProperty(Buffer.TRAILING_EOL,true);
494				seg.count--;
495				endOffsets.setSize(endOffsets.getSize() - 1);
496			}
497		}
498
499		// to avoid having to deal with read/write locks and such,
500		// we insert the loaded data into the buffer in the
501		// post-load cleanup runnable, which runs in the AWT thread.
502		buffer.setProperty(LOAD_DATA,seg);
503		buffer.setProperty(END_OFFSETS,endOffsets);
504		buffer.setProperty(NEW_PATH,path);
505		buffer.setProperty(Buffer.LINESEP,lineSeparator);
506	} //}}}
507
508	//{{{ readMarkers() method
509	private void readMarkers(Buffer buffer, InputStream _in)
510		throws IOException
511	{
512		// For `reload' command
513		buffer.removeAllMarkers();
514
515		BufferedReader in = new BufferedReader(new InputStreamReader(_in));
516
517		String line;
518		while((line = in.readLine()) != null)
519		{
520			// compatibility kludge for jEdit 3.1 and earlier
521			if(!line.startsWith("!"))
522				continue;
523
524			char shortcut = line.charAt(1);
525			int start = line.indexOf(';');
526			int end = line.indexOf(';',start + 1);
527			int position = Integer.parseInt(line.substring(start + 1,end));
528			buffer.addMarker(shortcut,position);
529		}
530
531		in.close();
532	} //}}}
533
534	//{{{ save() method
535	private void save()
536	{
537		OutputStream out = null;
538
539		try
540		{
541			String[] args = { vfs.getFileName(path) };
542			setStatus(jEdit.getProperty("vfs.status.save",args));
543
544			// the entire save operation can be aborted...
545			setAbortable(true);
546
547			try
548			{
549				path = vfs._canonPath(session,path,view);
550
551				buffer.readLock();
552
553				// Only backup once per session
554				if(buffer.getProperty(Buffer.BACKED_UP) == null
555					|| jEdit.getBooleanProperty("backupEverySave"))
556				{
557					vfs._backup(session,path,view);
558					buffer.setBooleanProperty(Buffer.BACKED_UP,true);
559				}
560
561				/* if the VFS supports renaming files, we first
562				 * save to #<filename>#save#, then rename that
563				 * to <filename>, so that if the save fails,
564				 * data will not be lost */
565				String savePath;
566
567				boolean twoStageSave = (vfs.getCapabilities() & VFS.RENAME_CAP) != 0
568					&& jEdit.getBooleanProperty("twoStageSave");
569				if(twoStageSave)
570				{
571					savePath = MiscUtilities.constructPath(
572						vfs.getParentOfPath(path),
573						'#' + vfs.getFileName(path)
574						+ "#save#");
575				}
576				else
577					savePath = path;
578
579				out = vfs._createOutputStream(session,savePath,view);
580				if(out != null)
581				{
582					// Can't use buffer.getName() here because
583					// it is not changed until the save is
584					// complete
585					if(savePath.endsWith(".gz"))
586						buffer.setBooleanProperty(Buffer.GZIPPED,true);
587
588					if(buffer.getBooleanProperty(Buffer.GZIPPED))
589						out = new GZIPOutputStream(out);
590
591					write(buffer,out);
592
593					if(twoStageSave)
594					{
595						if(!vfs._rename(session,savePath,path,view))
596							throw new IOException(path);
597					}
598
599					// We only save markers to VFS's that support deletion.
600					// Otherwise, we will accumilate stale marks files.
601					if((vfs.getCapabilities() & VFS.DELETE_CAP) != 0)
602					{
603						if(jEdit.getBooleanProperty("persistentMarkers")
604							&& buffer.getMarkers().size() != 0)
605						{
606							setStatus(jEdit.getProperty("vfs.status.save-markers",args));
607							setProgressValue(0);
608							out = vfs._createOutputStream(session,markersPath,view);
609							if(out != null)
610								writeMarkers(buffer,out);
611						}
612						else
613							vfs._delete(session,markersPath,view);
614					}
615				}
616				else
617					buffer.setBooleanProperty(ERROR_OCCURRED,true);
618			}
619			catch(IOException io)
620			{
621				Log.log(Log.ERROR,this,io);
622				String[] pp = { io.toString() };
623				VFSManager.error(view,path,"ioerror.write-error",pp);
624
625				buffer.setBooleanProperty(ERROR_OCCURRED,true);
626			}
627			finally
628			{
629				buffer.readUnlock();
630			}
631		}
632		catch(WorkThread.Abort a)
633		{
634			if(out != null)
635			{
636				try
637				{
638					out.close();
639				}
640				catch(IOException io)
641				{
642				}
643			}
644
645			buffer.setBooleanProperty(ERROR_OCCURRED,true);
646		}
647		finally
648		{
649			try
650			{
651				vfs._saveComplete(session,buffer,view);
652				vfs._endVFSSession(session,view);
653			}
654			catch(IOException io)
655			{
656				Log.log(Log.ERROR,this,io);
657				String[] pp = { io.toString() };
658				VFSManager.error(view,path,"ioerror.write-error",pp);
659
660				buffer.setBooleanProperty(ERROR_OCCURRED,true);
661			}
662			catch(WorkThread.Abort a)
663			{
664				buffer.setBooleanProperty(ERROR_OCCURRED,true);
665			}
666		}
667	} //}}}
668
669	//{{{ autosave() method
670	private void autosave()
671	{
672		OutputStream out = null;
673
674		try
675		{
676			String[] args = { vfs.getFileName(path) };
677			setStatus(jEdit.getProperty("vfs.status.autosave",args));
678
679			// the entire save operation can be aborted...
680			setAbortable(true);
681
682			try
683			{
684				//buffer.readLock();
685
686				if(!buffer.isDirty())
687				{
688					// buffer has been saved while we
689					// were waiting.
690					return;
691				}
692
693				out = vfs._createOutputStream(session,path,view);
694				if(out == null)
695					return;
696
697				write(buffer,out);
698			}
699			catch(Exception e)
700			{
701			}
702			finally
703			{
704				//buffer.readUnlock();
705			}
706		}
707		catch(WorkThread.Abort a)
708		{
709			if(out != null)
710			{
711				try
712				{
713					out.close();
714				}
715				catch(IOException io)
716				{
717				}
718			}
719		}
720	} //}}}
721
722	//{{{ write() method
723	private void write(Buffer buffer, OutputStream _out)
724		throws IOException
725	{
726		BufferedWriter out = new BufferedWriter(
727			new OutputStreamWriter(_out,
728				buffer.getStringProperty(Buffer.ENCODING)),
729				IOBUFSIZE);
730		Segment lineSegment = new Segment();
731		String newline = buffer.getStringProperty(Buffer.LINESEP);
732		if(newline == null)
733			newline = System.getProperty("line.separator");
734
735		setProgressMaximum(buffer.getLineCount() / PROGRESS_INTERVAL);
736		setProgressValue(0);
737
738		int i = 0;
739		while(i < buffer.getLineCount())
740		{
741			buffer.getLineText(i,lineSegment);
742			out.write(lineSegment.array,lineSegment.offset,
743				lineSegment.count);
744
745			if(i != buffer.getLineCount() - 1)
746			{
747				out.write(newline);
748			}
749
750			if(++i % PROGRESS_INTERVAL == 0)
751				setProgressValue(i / PROGRESS_INTERVAL);
752		}
753
754		if(jEdit.getBooleanProperty("stripTrailingEOL")
755			&& buffer.getBooleanProperty(Buffer.TRAILING_EOL))
756		{
757			out.write(newline);
758		}
759
760		out.close();
761	} //}}}
762
763	//{{{ writeMarkers() method
764	private void writeMarkers(Buffer buffer, OutputStream out)
765		throws IOException
766	{
767		Writer o = new BufferedWriter(new OutputStreamWriter(out));
768		Vector markers = buffer.getMarkers();
769		for(int i = 0; i < markers.size(); i++)
770		{
771			Marker marker = (Marker)markers.elementAt(i);
772			o.write('!');
773			o.write(marker.getShortcut());
774			o.write(';');
775
776			String pos = String.valueOf(marker.getPosition());
777			o.write(pos);
778			o.write(';');
779			o.write(pos);
780			o.write('\n');
781		}
782		o.close();
783	} //}}}
784
785	//{{{ insert() method
786	private void insert()
787	{
788		InputStream in = null;
789
790		try
791		{
792			try
793			{
794				String[] args = { vfs.getFileName(path) };
795				setStatus(jEdit.getProperty("vfs.status.load",args));
796				setAbortable(true);
797
798				path = vfs._canonPath(session,path,view);
799
800				VFS.DirectoryEntry entry = vfs._getDirectoryEntry(
801					session,path,view);
802				long length;
803				if(entry != null)
804					length = entry.length;
805				else
806					length = 0L;
807
808				in = vfs._createInputStream(session,path,false,view);
809				if(in == null)
810					return;
811
812				if(path.endsWith(".gz"))
813					in = new GZIPInputStream(in);
814
815				read(buffer,in,length);
816			}
817			catch(IOException io)
818			{
819				Log.log(Log.ERROR,this,io);
820				String[] pp = { io.toString() };
821				VFSManager.error(view,path,"ioerror.read-error",pp);
822
823				buffer.setBooleanProperty(ERROR_OCCURRED,true);
824			}
825		}
826		catch(WorkThread.Abort a)
827		{
828			if(in != null)
829			{
830				try
831				{
832					in.close();
833				}
834				catch(IOException io)
835				{
836				}
837			}
838
839			buffer.setBooleanProperty(ERROR_OCCURRED,true);
840		}
841		finally
842		{
843			try
844			{
845				vfs._endVFSSession(session,view);
846			}
847			catch(IOException io)
848			{
849				Log.log(Log.ERROR,this,io);
850				String[] pp = { io.toString() };
851				VFSManager.error(view,path,"ioerror.read-error",pp);
852
853				buffer.setBooleanProperty(ERROR_OCCURRED,true);
854			}
855			catch(WorkThread.Abort a)
856			{
857				buffer.setBooleanProperty(ERROR_OCCURRED,true);
858			}
859		}
860	} //}}}
861
862	//}}}
863}