PageRenderTime 101ms CodeModel.GetById 62ms app.highlight 32ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-5-pre1/org/gjt/sp/jedit/BufferHistory.java

#
Java | 484 lines | 356 code | 60 blank | 68 comment | 44 complexity | 73d73ff78bc47f3e517964eaef3055ca MD5 | raw file
  1/*
  2 * BufferHistory.java - Remembers caret positions
  3 * :tabSize=8:indentSize=8:noTabs=false:
  4 * :folding=explicit:collapseFolds=1:
  5 *
  6 * Copyright (C) 2000, 2005 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;
 24
 25//{{{ Imports
 26import java.io.IOException;
 27import java.util.*;
 28import java.util.concurrent.locks.ReentrantReadWriteLock;
 29
 30import org.xml.sax.InputSource;
 31import org.xml.sax.helpers.DefaultHandler;
 32
 33import org.gjt.sp.jedit.msg.DynamicMenuChanged;
 34import org.gjt.sp.jedit.textarea.*;
 35import org.gjt.sp.util.Log;
 36import org.gjt.sp.util.XMLUtilities;
 37import org.gjt.sp.util.IOUtilities;
 38//}}}
 39
 40/**
 41 * Recent file list.
 42 * @author Slava Pestov
 43 * @version $Id: BufferHistory.java 19833 2011-08-25 12:01:54Z kpouer $
 44 */
 45public class BufferHistory
 46{
 47	//{{{ Entry class
 48	/**
 49	 * Recent file list entry.
 50	 */
 51	public static class Entry
 52	{
 53		public String path;
 54		public int caret;
 55		public String selection;
 56		public String encoding;
 57		public String mode;
 58
 59		public Selection[] getSelection()
 60		{
 61			return stringToSelection(selection);
 62		}
 63
 64		public Entry(String path, int caret, String selection, String encoding, String mode)
 65		{
 66			this.path = path;
 67			this.caret = caret;
 68			this.selection = selection;
 69			this.encoding = encoding;
 70			this.mode = mode;
 71		}
 72
 73		public String toString()
 74		{
 75			return path + ": " + caret;
 76		}
 77	} //}}}
 78
 79	//{{{ getEntry() method
 80	public static Entry getEntry(String path)
 81	{
 82		historyLock.readLock().lock();
 83		try
 84		{
 85			for (Entry entry : history)
 86			{
 87				if(MiscUtilities.pathsEqual(entry.path,path))
 88					return entry;
 89			}
 90		}
 91		finally
 92		{
 93			historyLock.readLock().unlock();
 94		}
 95
 96		return null;
 97	} //}}}
 98
 99	//{{{ setEntry() method
100	public static void setEntry(String path, int caret, Selection[] selection,
101		String encoding, String mode)
102	{
103		Entry entry = new Entry(path,caret,
104			selectionToString(selection), encoding, mode);
105		historyLock.writeLock().lock();
106		try
107		{
108			removeEntry(path);
109			addEntry(entry);
110		}
111		finally
112		{
113			historyLock.writeLock().unlock();
114		}
115		notifyChange();
116	} //}}}
117
118	//{{{ clear() method
119	/**
120	 * Clear the BufferHistory.
121	 * @since 4.3pre6
122	 */
123	public static void clear()
124	{
125		historyLock.writeLock().lock();
126		try
127		{
128			history.clear();
129		}
130		finally
131		{
132			historyLock.writeLock().unlock();
133		}
134		notifyChange();
135	} //}}}
136
137	//{{{ getHistory() method
138	/**
139	 * Returns the Buffer list.
140	 * @return the buffer history list
141	 * @since jEdit 4.2pre2
142	 */
143	public static List<Entry> getHistory()
144	{
145		// Returns a snapshot to avoid concurrent access to the
146		// history. This requires O(n) time, but it should be ok
147		// because this method should be used only by external
148		// O(n) operation.
149
150		historyLock.readLock().lock();
151		try
152		{
153			return (List<Entry>)history.clone();
154		}
155		finally
156		{
157			historyLock.readLock().unlock();
158		}
159	} //}}}
160
161	//{{{ load() method
162	public static void load()
163	{
164		if(recentXML == null)
165			return;
166
167		if(!recentXML.fileExists())
168			return;
169
170		Log.log(Log.MESSAGE,BufferHistory.class,"Loading " + recentXML);
171
172		RecentHandler handler = new RecentHandler();
173		try
174		{
175			recentXML.load(handler);
176		}
177		catch(IOException e)
178		{
179			Log.log(Log.ERROR,BufferHistory.class,e);
180		}
181		trimToLimit(handler.result);
182		history = handler.result;
183	} //}}}
184
185	//{{{ save() method
186	public static void save()
187	{
188		if(recentXML == null)
189			return;
190
191		if(recentXML.hasChangedOnDisk())
192		{
193			Log.log(Log.WARNING,BufferHistory.class,recentXML
194				+ " changed on disk; will not save recent"
195				+ " files");
196			return;
197		}
198
199		Log.log(Log.MESSAGE,BufferHistory.class,"Saving " + recentXML);
200
201		String lineSep = System.getProperty("line.separator");
202
203		SettingsXML.Saver out = null;
204
205		try
206		{
207			out = recentXML.openSaver();
208			out.writeXMLDeclaration();
209
210			out.write("<!DOCTYPE RECENT SYSTEM \"recent.dtd\">");
211			out.write(lineSep);
212			out.write("<RECENT>");
213			out.write(lineSep);
214
215			// Make a snapshot to avoid long locking period
216			// which may be required by file I/O.
217			List<Entry> snapshot = getHistory();
218
219			for (Entry entry : snapshot)
220			{
221				out.write("<ENTRY>");
222				out.write(lineSep);
223
224				out.write("<PATH>");
225				out.write(XMLUtilities.charsToEntities(entry.path,false));
226				out.write("</PATH>");
227				out.write(lineSep);
228
229				out.write("<CARET>");
230				out.write(String.valueOf(entry.caret));
231				out.write("</CARET>");
232				out.write(lineSep);
233
234				if(entry.selection != null
235					&& entry.selection.length() > 0)
236				{
237					out.write("<SELECTION>");
238					out.write(entry.selection);
239					out.write("</SELECTION>");
240					out.write(lineSep);
241				}
242
243				if(entry.encoding != null)
244				{
245					out.write("<ENCODING>");
246					out.write(entry.encoding);
247					out.write("</ENCODING>");
248					out.write(lineSep);
249				}
250
251				if (entry.mode != null)
252				{
253					out.write("<MODE>");
254					out.write(entry.mode);
255					out.write("</MODE>");
256					out.write(lineSep);
257				}
258
259				out.write("</ENTRY>");
260				out.write(lineSep);
261			}
262
263			out.write("</RECENT>");
264			out.write(lineSep);
265
266			out.finish();
267		}
268		catch(Exception e)
269		{
270			Log.log(Log.ERROR,BufferHistory.class,e);
271		}
272		finally
273		{
274			IOUtilities.closeQuietly(out);
275		}
276	} //}}}
277
278	//{{{ Private members
279	private static LinkedList<Entry> history;
280	private static final ReentrantReadWriteLock historyLock;
281	private static SettingsXML recentXML;
282
283	//{{{ Class initializer
284	static
285	{
286		history = new LinkedList<Entry>();
287		historyLock = new ReentrantReadWriteLock();
288		String settingsDirectory = jEdit.getSettingsDirectory();
289		if(settingsDirectory != null)
290		{
291			recentXML = new SettingsXML(settingsDirectory, "recent");
292		}
293	} //}}}
294
295	//{{{ addEntry() method
296	private static void addEntry(Entry entry)
297	{
298		historyLock.writeLock().lock();
299		try
300		{
301			history.addFirst(entry);
302			trimToLimit(history);
303		}
304		finally
305		{
306			historyLock.writeLock().unlock();
307		}
308	} //}}}
309
310	//{{{ removeEntry() method
311	private static void removeEntry(String path)
312	{
313		historyLock.writeLock().lock();
314		try
315		{
316			Iterator<Entry> iter = history.iterator();
317			while(iter.hasNext())
318			{
319				Entry entry = iter.next();
320				if(MiscUtilities.pathsEqual(path,entry.path))
321				{
322					iter.remove();
323					return;
324				}
325			}
326		}
327		finally
328		{
329			historyLock.writeLock().unlock();
330		}
331	} //}}}
332
333	//{{{ selectionToString() method
334	private static String selectionToString(Selection[] s)
335	{
336		if(s == null)
337			return null;
338
339		StringBuilder buf = new StringBuilder();
340
341		for(int i = 0; i < s.length; i++)
342		{
343			if(i != 0)
344				buf.append(' ');
345
346			Selection sel = s[i];
347			if(sel instanceof Selection.Range)
348				buf.append("range ");
349			else //if(sel instanceof Selection.Rect)
350				buf.append("rect ");
351			buf.append(sel.getStart());
352			buf.append(' ');
353			buf.append(sel.getEnd());
354		}
355
356		return buf.toString();
357	} //}}}
358
359	//{{{ stringToSelection() method
360	private static Selection[] stringToSelection(String s)
361	{
362		if(s == null)
363			return null;
364
365		List<Selection> selection = new ArrayList<Selection>();
366		StringTokenizer st = new StringTokenizer(s);
367
368		while(st.hasMoreTokens())
369		{
370			String type = st.nextToken();
371			int start = Integer.parseInt(st.nextToken());
372			int end = Integer.parseInt(st.nextToken());
373			if(end < start)
374			{
375				// I'm not sure when this can happen,
376				// but it does sometimes, witness the
377				// jEdit bug tracker.
378				continue;
379			}
380
381			Selection sel;
382			if("range".equals(type))
383				sel = new Selection.Range(start,end);
384			else //if(type.equals("rect"))
385				sel = new Selection.Rect(start,end);
386
387			selection.add(sel);
388		}
389
390		Selection[] returnValue = new Selection[selection.size()];
391		returnValue = selection.toArray(returnValue);
392		return returnValue;
393	} //}}}
394
395	//{{{ trimToLimit() method
396	private static void trimToLimit(Deque<Entry> list)
397	{
398		int max = jEdit.getIntegerProperty("recentFiles",50);
399		while(list.size() > max)
400			list.removeLast();
401	} //}}}
402
403	//{{{ notifyChange() method
404	private static void notifyChange()
405	{
406		EditBus.send(new DynamicMenuChanged("recent-files"));
407	} //}}}
408
409	//{{{ RecentHandler class
410	private static class RecentHandler extends DefaultHandler
411	{
412		public LinkedList<Entry> result = new LinkedList<Entry>();
413
414		@Override
415		public InputSource resolveEntity(String publicId, String systemId)
416		{
417			return XMLUtilities.findEntity(systemId, "recent.dtd", getClass());
418		}
419
420		@Override
421		public void endElement(String uri, String localName, String name)
422		{
423			if("ENTRY".equals(name))
424			{
425				result.addLast(new Entry(
426					path,caret,selection,
427					encoding,
428					mode));
429				path = null;
430				caret = 0;
431				selection = null;
432				encoding = null;
433				mode = null;
434			}
435			else if("PATH".equals(name))
436				path = charData.toString();
437			else if("CARET".equals(name))
438			{
439				try
440				{
441					String s = charData.toString().trim();
442
443					if (s.length() != charData.length())
444					{
445						Log.log(Log.WARNING, this,
446							"The caret position in recent.xml was wrong: '"+
447							charData + "', fixing it");
448					}
449					caret = Integer.parseInt(s);
450				}
451				catch (NumberFormatException e)
452				{
453					Log.log(Log.ERROR, this, "Unable to parse caret position " +
454						charData);
455				}
456			}
457			else if("SELECTION".equals(name))
458				selection = charData.toString();
459			else if("ENCODING".equals(name))
460				encoding = charData.toString();
461			else if("MODE".equals(name))
462				mode = charData.toString();
463			charData.setLength(0);
464		}
465
466		@Override
467		public void characters(char[] ch, int start, int length)
468		{
469			charData.append(ch,start,length);
470		}
471
472		// end HandlerBase implementation
473
474		// private members
475		private String path;
476		private int caret;
477		private String selection;
478		private String encoding;
479		private String mode;
480		private final StringBuilder charData = new StringBuilder();
481	} //}}}
482
483	//}}}
484}