/jEdit/branches/4.3.x-fix-view-leak/org/gjt/sp/jedit/bufferio/BufferIORequest.java

# · Java · 468 lines · 292 code · 52 blank · 124 comment · 49 complexity · f60d1c9e8963a1341a8ba04c967a2a2d 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, 2004 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.bufferio;
  23. //{{{ Imports
  24. import java.io.BufferedOutputStream;
  25. import java.io.CharConversionException;
  26. import java.io.IOException;
  27. import java.io.InputStream;
  28. import java.io.OutputStream;
  29. import java.io.Reader;
  30. import java.io.Writer;
  31. import java.nio.charset.CharacterCodingException;
  32. import javax.swing.text.Segment;
  33. import org.gjt.sp.jedit.Buffer;
  34. import org.gjt.sp.jedit.MiscUtilities;
  35. import org.gjt.sp.jedit.View;
  36. import org.gjt.sp.jedit.jEdit;
  37. import org.gjt.sp.jedit.buffer.JEditBuffer;
  38. import org.gjt.sp.jedit.io.VFS;
  39. import org.gjt.sp.jedit.io.Encoding;
  40. import org.gjt.sp.jedit.io.EncodingServer;
  41. import org.gjt.sp.util.IntegerArray;
  42. import org.gjt.sp.util.SegmentBuffer;
  43. import org.gjt.sp.util.WorkRequest;
  44. //}}}
  45. /**
  46. * A buffer I/O request.
  47. * @author Slava Pestov
  48. * @version $Id: BufferIORequest.java 16339 2009-10-14 09:59:36Z kpouer $
  49. */
  50. public abstract class BufferIORequest extends WorkRequest
  51. {
  52. //{{{ Constants
  53. /**
  54. * Size of I/O buffers.
  55. */
  56. public static final int IOBUFSIZE = 32768;
  57. /**
  58. * Number of lines per progress increment.
  59. */
  60. public static final int PROGRESS_INTERVAL = 300;
  61. public static final String LOAD_DATA = "BufferIORequest__loadData";
  62. public static final String END_OFFSETS = "BufferIORequest__endOffsets";
  63. public static final String NEW_PATH = "BufferIORequest__newPath";
  64. /**
  65. * Buffer boolean property set when an error occurs.
  66. */
  67. public static final String ERROR_OCCURRED = "BufferIORequest__error";
  68. // These are no longer used but still here only for compatibility.
  69. @Deprecated public static final int UTF8_MAGIC_1 = 0xef;
  70. @Deprecated public static final int UTF8_MAGIC_2 = 0xbb;
  71. @Deprecated public static final int UTF8_MAGIC_3 = 0xbf;
  72. @Deprecated public static final int UNICODE_MAGIC_1 = 0xfe;
  73. @Deprecated public static final int UNICODE_MAGIC_2 = 0xff;
  74. @Deprecated public static final int XML_PI_LENGTH = 50;
  75. @Deprecated public static final int GZIP_MAGIC_1 = 0x1f;
  76. @Deprecated public static final int GZIP_MAGIC_2 = 0x8b;
  77. //}}}
  78. //{{{ Instance variables
  79. protected final View view;
  80. protected final Buffer buffer;
  81. protected final Object session;
  82. protected final VFS vfs;
  83. protected String path;
  84. protected final String markersPath;
  85. //}}}
  86. //{{{ BufferIORequest constructor
  87. /**
  88. * Creates a new buffer I/O request.
  89. * @param view The view
  90. * @param buffer The buffer
  91. * @param session The VFS session
  92. * @param vfs The VFS
  93. * @param path The path
  94. */
  95. protected BufferIORequest(View view, Buffer buffer,
  96. Object session, VFS vfs, String path)
  97. {
  98. this.view = view;
  99. this.buffer = buffer;
  100. this.session = session;
  101. this.vfs = vfs;
  102. this.path = path;
  103. markersPath = Buffer.getMarkersPath(vfs, path);
  104. } //}}}
  105. //{{{ toString() method
  106. public String toString()
  107. {
  108. return getClass().getName() + '[' + buffer + ']';
  109. } //}}}
  110. //{{{ getCharIOBufferSize() method
  111. /**
  112. * Size of character I/O buffers.
  113. */
  114. public static int getCharIOBufferSize()
  115. {
  116. return IOBUFSIZE;
  117. } //}}}
  118. //{{{ getByteIOBufferSize() method
  119. /**
  120. * Size of byte I/O buffers.
  121. */
  122. public static int getByteIOBufferSize()
  123. {
  124. // 2 is sizeof char in byte;
  125. return IOBUFSIZE * 2;
  126. } //}}}
  127. //{{{ autodetect() method
  128. /**
  129. * Tries to detect if the stream is gzipped, and if it has an encoding
  130. * specified with an XML PI.
  131. */
  132. protected Reader autodetect(InputStream in) throws IOException
  133. {
  134. return MiscUtilities.autodetect(in, buffer);
  135. } //}}}
  136. //{{{ read() method
  137. protected SegmentBuffer read(Reader in, long length,
  138. boolean insert) throws IOException
  139. {
  140. /* we guess an initial size for the array */
  141. IntegerArray endOffsets = new IntegerArray(
  142. Math.max(1,(int)(length / 50)));
  143. // only true if the file size is known
  144. boolean trackProgress = !buffer.isTemporary() && length != 0;
  145. if(trackProgress)
  146. {
  147. setMaximum(length);
  148. setValue(0);
  149. }
  150. // if the file size is not known, start with a resonable
  151. // default buffer size
  152. if(length == 0)
  153. length = IOBUFSIZE;
  154. SegmentBuffer seg = new SegmentBuffer((int)length + 1);
  155. char[] buf = new char[IOBUFSIZE];
  156. /* Number of characters in 'buf' array.
  157. InputStream.read() doesn't always fill the
  158. array (eg, the file size is not a multiple of
  159. IOBUFSIZE, or it is a GZipped file, etc) */
  160. int len;
  161. // True if a \n was read after a \r. Usually
  162. // means this is a DOS/Windows file
  163. boolean CRLF = false;
  164. // A \r was read, hence a MacOS file
  165. boolean CROnly = false;
  166. // Was the previous read character a \r?
  167. // If we read a \n and this is true, we assume
  168. // we have a DOS/Windows file
  169. boolean lastWasCR = false;
  170. // Number of lines read. Every 100 lines, we update the
  171. // progress bar
  172. int lineCount = 0;
  173. while((len = in.read(buf,0,buf.length)) != -1)
  174. {
  175. // Offset of previous line, relative to
  176. // the start of the I/O buffer (NOT
  177. // relative to the start of the document)
  178. int lastLine = 0;
  179. for(int i = 0; i < len; i++)
  180. {
  181. // Look for line endings.
  182. switch(buf[i])
  183. {
  184. case '\r':
  185. // If we read a \r and
  186. // lastWasCR is also true,
  187. // it is probably a Mac file
  188. // (\r\r in stream)
  189. if(lastWasCR)
  190. {
  191. CROnly = true;
  192. CRLF = false;
  193. }
  194. // Otherwise set a flag,
  195. // so that \n knows that last
  196. // was a \r
  197. else
  198. {
  199. lastWasCR = true;
  200. }
  201. // Insert a line
  202. seg.append(buf,lastLine,i -
  203. lastLine);
  204. seg.append('\n');
  205. endOffsets.add(seg.count);
  206. if(trackProgress && lineCount++ % PROGRESS_INTERVAL == 0)
  207. setValue(seg.count);
  208. // This is i+1 to take the
  209. // trailing \n into account
  210. lastLine = i + 1;
  211. break;
  212. case '\n':
  213. /* If lastWasCR is true, we just read a \r followed
  214. by a \n. We specify that this is a Windows file,
  215. but take no further action and just ignore the \r. */
  216. if(lastWasCR)
  217. {
  218. CROnly = false;
  219. CRLF = true;
  220. lastWasCR = false;
  221. /* Bump lastLine so that the next line doesn't erronously
  222. pick up the \r */
  223. lastLine = i + 1;
  224. }
  225. /* Otherwise, we found a \n that follows some other
  226. * character, hence we have a Unix file */
  227. else
  228. {
  229. CROnly = false;
  230. CRLF = false;
  231. seg.append(buf,lastLine,
  232. i - lastLine);
  233. seg.append('\n');
  234. endOffsets.add(seg.count);
  235. if(trackProgress && lineCount++ % PROGRESS_INTERVAL == 0)
  236. setValue(seg.count);
  237. lastLine = i + 1;
  238. }
  239. break;
  240. default:
  241. /* If we find some other character that follows
  242. a \r, so it is not a Windows file, and probably
  243. a Mac file */
  244. if(lastWasCR)
  245. {
  246. CROnly = true;
  247. CRLF = false;
  248. lastWasCR = false;
  249. }
  250. break;
  251. }
  252. }
  253. if(trackProgress)
  254. setValue(seg.count);
  255. // Add remaining stuff from buffer
  256. seg.append(buf,lastLine,len - lastLine);
  257. }
  258. setAbortable(false);
  259. String lineSeparator;
  260. if(seg.count == 0)
  261. {
  262. // fix for "[ 865589 ] 0-byte files should open using
  263. // the default line seperator"
  264. lineSeparator = jEdit.getProperty(
  265. "buffer.lineSeparator",
  266. System.getProperty("line.separator"));
  267. }
  268. else if(CRLF)
  269. lineSeparator = "\r\n";
  270. else if(CROnly)
  271. lineSeparator = "\r";
  272. else
  273. lineSeparator = "\n";
  274. // Chop trailing newline and/or ^Z (if any)
  275. int bufferLength = seg.count;
  276. if(bufferLength != 0)
  277. {
  278. char ch = seg.array[bufferLength - 1];
  279. if(ch == 0x1a /* DOS ^Z */)
  280. seg.count--;
  281. }
  282. buffer.setBooleanProperty(Buffer.TRAILING_EOL,false);
  283. if(bufferLength != 0 && jEdit.getBooleanProperty("stripTrailingEOL"))
  284. {
  285. char ch = seg.array[bufferLength - 1];
  286. if(ch == '\n')
  287. {
  288. buffer.setBooleanProperty(Buffer.TRAILING_EOL,true);
  289. seg.count--;
  290. endOffsets.setSize(endOffsets.getSize() - 1);
  291. }
  292. }
  293. // add a line marker at the end for proper offset manager
  294. // operation
  295. endOffsets.add(seg.count + 1);
  296. // to avoid having to deal with read/write locks and such,
  297. // we insert the loaded data into the buffer in the
  298. // post-load cleanup runnable, which runs in the AWT thread.
  299. if(!insert)
  300. {
  301. buffer.setProperty(LOAD_DATA,seg);
  302. buffer.setProperty(END_OFFSETS,endOffsets);
  303. buffer.setProperty(NEW_PATH,path);
  304. if(lineSeparator != null)
  305. buffer.setProperty(JEditBuffer.LINESEP,lineSeparator);
  306. }
  307. // used in insert()
  308. return seg;
  309. } //}}}
  310. //{{{ write() method
  311. protected void write(Buffer buffer, OutputStream out)
  312. throws IOException
  313. {
  314. String encodingName
  315. = buffer.getStringProperty(JEditBuffer.ENCODING);
  316. Encoding encoding = EncodingServer.getEncoding(encodingName);
  317. Writer writer = encoding.getTextWriter(
  318. new BufferedOutputStream(out, getByteIOBufferSize()));
  319. Segment lineSegment = new Segment();
  320. String newline = buffer.getStringProperty(JEditBuffer.LINESEP);
  321. if(newline == null)
  322. newline = System.getProperty("line.separator");
  323. final int bufferLineCount = buffer.getLineCount();
  324. setMaximum(bufferLineCount / PROGRESS_INTERVAL);
  325. setValue(0);
  326. int i = 0;
  327. while(i < bufferLineCount)
  328. {
  329. buffer.getLineText(i,lineSegment);
  330. try
  331. {
  332. writer.write(lineSegment.array,
  333. lineSegment.offset,
  334. lineSegment.count);
  335. if(i < bufferLineCount - 1
  336. || (jEdit.getBooleanProperty("stripTrailingEOL")
  337. && buffer.getBooleanProperty(Buffer.TRAILING_EOL)))
  338. {
  339. writer.write(newline);
  340. }
  341. }
  342. catch(CharacterCodingException e)
  343. {
  344. String message = getWriteEncodingErrorMessage(
  345. encodingName, encoding,
  346. lineSegment, i);
  347. IOException wrapping = new CharConversionException(message);
  348. wrapping.initCause(e);
  349. throw wrapping;
  350. }
  351. if(++i % PROGRESS_INTERVAL == 0)
  352. setValue(i / PROGRESS_INTERVAL);
  353. }
  354. writer.flush();
  355. } //}}}
  356. //{{{ Private members
  357. //{{{ createEncodingErrorMessage() method
  358. private static String getWriteEncodingErrorMessage(
  359. String encodingName, Encoding encoding,
  360. Segment line, int lineIndex)
  361. {
  362. String args[] = {
  363. encodingName,
  364. Integer.toString(lineIndex + 1),
  365. "UNKNOWN", // column
  366. "UNKNOWN" // the character
  367. };
  368. try
  369. {
  370. int charIndex = getFirstGuiltyCharacterIndex(encoding, line);
  371. if(0 <= charIndex && charIndex < line.count)
  372. {
  373. char c = line.array[line.offset + charIndex];
  374. args[2] = Integer.toString(charIndex + 1);
  375. args[3] = "'" + c + "' (U+" + Integer.toHexString(c).toUpperCase() + ")";
  376. }
  377. }
  378. catch(Exception e)
  379. {
  380. // Ignore.
  381. }
  382. return jEdit.getProperty("ioerror.write-encoding-error", args);
  383. } //}}}
  384. //{{{ getFirstGuiltyCharacterIndex() method
  385. // Look for the first character which causes encoding error.
  386. private static int getFirstGuiltyCharacterIndex(Encoding encoding,
  387. Segment line) throws IOException
  388. {
  389. if(line.count < 1)
  390. {
  391. return -1;
  392. }
  393. else if(line.count == 1)
  394. {
  395. return 0;
  396. }
  397. Writer tester = encoding.getTextWriter(
  398. new OutputStream()
  399. {
  400. public void write(int b) {}
  401. });
  402. for(int i = 0; i < line.count; ++i)
  403. {
  404. try
  405. {
  406. tester.write(line.array[line.offset + i]);
  407. }
  408. catch(CharacterCodingException e)
  409. {
  410. return i;
  411. }
  412. }
  413. return -1;
  414. } //}}}
  415. //}}}
  416. }