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