PageRenderTime 177ms CodeModel.GetById 123ms app.highlight 47ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-0-pre3/org/gjt/sp/jedit/io/BufferIORequest.java

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