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