001: /*
002: * BufferHistory.java - Remembers caret positions
003: * :tabSize=8:indentSize=8:noTabs=false:
004: * :folding=explicit:collapseFolds=1:
005: *
006: * Copyright (C) 2000, 2005 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;
024:
025: //{{{ Imports
026: import java.io.IOException;
027: import java.util.*;
028: import java.util.concurrent.locks.ReentrantReadWriteLock;
029:
030: import org.xml.sax.InputSource;
031: import org.xml.sax.helpers.DefaultHandler;
032:
033: import org.gjt.sp.jedit.msg.DynamicMenuChanged;
034: import org.gjt.sp.jedit.textarea.*;
035: import org.gjt.sp.util.Log;
036: import org.gjt.sp.util.XMLUtilities;
037: import org.gjt.sp.util.IOUtilities;
038:
039: //}}}
040:
041: /**
042: * Recent file list.
043: * @author Slava Pestov
044: * @version $Id: BufferHistory.java 10964 2007-10-29 20:47:22Z k_satoda $
045: */
046: public class BufferHistory {
047: //{{{ Entry class
048: /**
049: * Recent file list entry.
050: */
051: public static class Entry {
052: public String path;
053: public int caret;
054: public String selection;
055: public String encoding;
056: public String mode;
057:
058: public Selection[] getSelection() {
059: return stringToSelection(selection);
060: }
061:
062: public Entry(String path, int caret, String selection,
063: String encoding, String mode) {
064: this .path = path;
065: this .caret = caret;
066: this .selection = selection;
067: this .encoding = encoding;
068: this .mode = mode;
069: }
070:
071: public String toString() {
072: return path + ": " + caret;
073: }
074: } //}}}
075:
076: //{{{ getEntry() method
077: public static Entry getEntry(String path) {
078: historyLock.readLock().lock();
079: try {
080: for (Entry entry : history) {
081: if (MiscUtilities.pathsEqual(entry.path, path))
082: return entry;
083: }
084: } finally {
085: historyLock.readLock().unlock();
086: }
087:
088: return null;
089: } //}}}
090:
091: //{{{ setEntry() method
092: public static void setEntry(String path, int caret,
093: Selection[] selection, String encoding, String mode) {
094: Entry entry = new Entry(path, caret,
095: selectionToString(selection), encoding, mode);
096: historyLock.writeLock().lock();
097: try {
098: removeEntry(path);
099: addEntry(entry);
100: } finally {
101: historyLock.writeLock().unlock();
102: }
103: notifyChange();
104: } //}}}
105:
106: //{{{ clear() method
107: /**
108: * Clear the BufferHistory.
109: * @since 4.3pre6
110: */
111: public static void clear() {
112: historyLock.writeLock().lock();
113: try {
114: history.clear();
115: } finally {
116: historyLock.writeLock().unlock();
117: }
118: notifyChange();
119: } //}}}
120:
121: //{{{ getHistory() method
122: /**
123: * Returns the Buffer list.
124: * @return the buffer history list
125: * @since jEdit 4.2pre2
126: */
127: public static List<Entry> getHistory() {
128: // Returns a snapshot to avoid concurrent access to the
129: // history. This requires O(n) time, but it should be ok
130: // because this method should be used only by external
131: // O(n) operation.
132:
133: historyLock.readLock().lock();
134: try {
135: return (List<Entry>) history.clone();
136: } finally {
137: historyLock.readLock().unlock();
138: }
139: } //}}}
140:
141: //{{{ load() method
142: public static void load() {
143: if (recentXML == null)
144: return;
145:
146: if (!recentXML.fileExists())
147: return;
148:
149: Log.log(Log.MESSAGE, BufferHistory.class, "Loading "
150: + recentXML);
151:
152: RecentHandler handler = new RecentHandler();
153: try {
154: recentXML.load(handler);
155: } catch (IOException e) {
156: Log.log(Log.ERROR, BufferHistory.class, e);
157: }
158: trimToLimit(handler.result);
159: history = handler.result;
160: } //}}}
161:
162: //{{{ save() method
163: public static void save() {
164: if (recentXML == null)
165: return;
166:
167: if (recentXML.hasChangedOnDisk()) {
168: Log.log(Log.WARNING, BufferHistory.class, recentXML
169: + " changed on disk; will not save recent"
170: + " files");
171: return;
172: }
173:
174: Log
175: .log(Log.MESSAGE, BufferHistory.class, "Saving "
176: + recentXML);
177:
178: String lineSep = System.getProperty("line.separator");
179:
180: SettingsXML.Saver out = null;
181:
182: try {
183: out = recentXML.openSaver();
184: out.writeXMLDeclaration();
185:
186: out.write("<!DOCTYPE RECENT SYSTEM \"recent.dtd\">");
187: out.write(lineSep);
188: out.write("<RECENT>");
189: out.write(lineSep);
190:
191: // Make a snapshot to avoid long locking period
192: // which may be required by file I/O.
193: List<Entry> snapshot = getHistory();
194:
195: for (Entry entry : snapshot) {
196: out.write("<ENTRY>");
197: out.write(lineSep);
198:
199: out.write("<PATH>");
200: out.write(XMLUtilities.charsToEntities(entry.path,
201: false));
202: out.write("</PATH>");
203: out.write(lineSep);
204:
205: out.write("<CARET>");
206: out.write(String.valueOf(entry.caret));
207: out.write("</CARET>");
208: out.write(lineSep);
209:
210: if (entry.selection != null
211: && entry.selection.length() > 0) {
212: out.write("<SELECTION>");
213: out.write(entry.selection);
214: out.write("</SELECTION>");
215: out.write(lineSep);
216: }
217:
218: if (entry.encoding != null) {
219: out.write("<ENCODING>");
220: out.write(entry.encoding);
221: out.write("</ENCODING>");
222: out.write(lineSep);
223: }
224:
225: if (entry.mode != null) {
226: out.write("<MODE>");
227: out.write(entry.mode);
228: out.write("</MODE>");
229: out.write(lineSep);
230: }
231:
232: out.write("</ENTRY>");
233: out.write(lineSep);
234: }
235:
236: out.write("</RECENT>");
237: out.write(lineSep);
238:
239: out.finish();
240: } catch (Exception e) {
241: Log.log(Log.ERROR, BufferHistory.class, e);
242: } finally {
243: IOUtilities.closeQuietly(out);
244: }
245: } //}}}
246:
247: //{{{ Private members
248: private static LinkedList<Entry> history;
249: private static ReentrantReadWriteLock historyLock;
250: private static SettingsXML recentXML;
251:
252: //{{{ Class initializer
253: static {
254: history = new LinkedList<Entry>();
255: historyLock = new ReentrantReadWriteLock();
256: String settingsDirectory = jEdit.getSettingsDirectory();
257: if (settingsDirectory != null) {
258: recentXML = new SettingsXML(settingsDirectory, "recent");
259: }
260: } //}}}
261:
262: //{{{ addEntry() method
263: private static void addEntry(Entry entry) {
264: historyLock.writeLock().lock();
265: try {
266: history.addFirst(entry);
267: trimToLimit(history);
268: } finally {
269: historyLock.writeLock().unlock();
270: }
271: } //}}}
272:
273: //{{{ removeEntry() method
274: private static void removeEntry(String path) {
275: historyLock.writeLock().lock();
276: try {
277: Iterator<Entry> iter = history.iterator();
278: while (iter.hasNext()) {
279: Entry entry = iter.next();
280: if (MiscUtilities.pathsEqual(path, entry.path)) {
281: iter.remove();
282: return;
283: }
284: }
285: } finally {
286: historyLock.writeLock().unlock();
287: }
288: } //}}}
289:
290: //{{{ selectionToString() method
291: private static String selectionToString(Selection[] s) {
292: if (s == null)
293: return null;
294:
295: StringBuffer buf = new StringBuffer();
296:
297: for (int i = 0; i < s.length; i++) {
298: if (i != 0)
299: buf.append(' ');
300:
301: Selection sel = s[i];
302: if (sel instanceof Selection.Range)
303: buf.append("range ");
304: else
305: //if(sel instanceof Selection.Rect)
306: buf.append("rect ");
307: buf.append(sel.getStart());
308: buf.append(' ');
309: buf.append(sel.getEnd());
310: }
311:
312: return buf.toString();
313: } //}}}
314:
315: //{{{ stringToSelection() method
316: private static Selection[] stringToSelection(String s) {
317: if (s == null)
318: return null;
319:
320: Vector<Selection> selection = new Vector<Selection>();
321: StringTokenizer st = new StringTokenizer(s);
322:
323: while (st.hasMoreTokens()) {
324: String type = st.nextToken();
325: int start = Integer.parseInt(st.nextToken());
326: int end = Integer.parseInt(st.nextToken());
327: if (end < start) {
328: // I'm not sure when this can happen,
329: // but it does sometimes, witness the
330: // jEdit bug tracker.
331: continue;
332: }
333:
334: Selection sel;
335: if (type.equals("range"))
336: sel = new Selection.Range(start, end);
337: else
338: //if(type.equals("rect"))
339: sel = new Selection.Rect(start, end);
340:
341: selection.add(sel);
342: }
343:
344: Selection[] returnValue = new Selection[selection.size()];
345: selection.copyInto(returnValue);
346: return returnValue;
347: } //}}}
348:
349: //{{{ trimToLimit() method
350: private static void trimToLimit(LinkedList<Entry> list) {
351: int max = jEdit.getIntegerProperty("recentFiles", 50);
352: while (list.size() > max)
353: list.removeLast();
354: } //}}}
355:
356: //{{{ notifyChange() method
357: private static void notifyChange() {
358: EditBus.send(new DynamicMenuChanged("recent-files"));
359: } //}}}
360:
361: //{{{ RecentHandler class
362: private static class RecentHandler extends DefaultHandler {
363: public LinkedList<Entry> result = new LinkedList<Entry>();
364:
365: public InputSource resolveEntity(String publicId,
366: String systemId) {
367: return XMLUtilities.findEntity(systemId, "recent.dtd",
368: getClass());
369: }
370:
371: public void endElement(String uri, String localName, String name) {
372: if (name.equals("ENTRY")) {
373: result.addLast(new Entry(path, caret, selection,
374: encoding, mode));
375: path = null;
376: caret = 0;
377: selection = null;
378: encoding = null;
379: mode = null;
380: } else if (name.equals("PATH"))
381: path = charData.toString();
382: else if (name.equals("CARET"))
383: caret = Integer.parseInt(charData.toString());
384: else if (name.equals("SELECTION"))
385: selection = charData.toString();
386: else if (name.equals("ENCODING"))
387: encoding = charData.toString();
388: else if (name.equals("MODE"))
389: mode = charData.toString();
390: charData.setLength(0);
391: }
392:
393: public void characters(char[] ch, int start, int length) {
394: charData.append(ch, start, length);
395: }
396:
397: // end HandlerBase implementation
398:
399: // private members
400: private String path;
401: private int caret;
402: private String selection;
403: private String encoding;
404: private String mode;
405: private StringBuffer charData = new StringBuffer();
406: } //}}}
407:
408: //}}}
409: }
|