001: /*
002: * SystemBuffer.java
003: *
004: * Copyright (C) 1998-2003 Peter Graves
005: * $Id: SystemBuffer.java,v 1.15 2003/06/14 17:49:03 piso Exp $
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: */
021:
022: package org.armedbear.j;
023:
024: import java.io.BufferedOutputStream;
025: import java.io.IOException;
026: import java.io.InputStream;
027: import java.io.UnsupportedEncodingException;
028: import java.util.List;
029:
030: // System buffers are NOT linked into the normal buffer ring.
031: public class SystemBuffer implements Constants {
032: public static final int TYPE_SYSTEM = 0;
033: public static final int TYPE_NORMAL = 1;
034: public static final int TYPE_ARCHIVE = 2;
035: public static final int TYPE_DIRECTORY = 3;
036: public static final int TYPE_SHELL = 4;
037: public static final int TYPE_MAN = 5;
038: public static final int TYPE_OUTPUT = 6;
039: public static final int TYPE_IMAGE = 7;
040: public static final int TYPE_MAILBOX = 8;
041: public static final int TYPE_TELNET = 9;
042: public static final int TYPE_SSH = 10;
043: public static final int TYPE_LIST_OCCURRENCES = 11;
044:
045: protected int type = TYPE_SYSTEM;
046: protected boolean readOnly;
047: protected boolean forceReadOnly;
048: protected Mode mode;
049: protected String lineSeparator;
050: protected int lineCount;
051:
052: private boolean isLoaded;
053: private Line firstLine;
054: private Line lastLine;
055: private File file;
056: private String loadEncoding;
057: private List tags;
058:
059: public SystemBuffer() {
060: }
061:
062: public SystemBuffer(File file) {
063: this .file = file;
064: }
065:
066: public final int getType() {
067: return type;
068: }
069:
070: public final synchronized Line getFirstLine() {
071: return firstLine;
072: }
073:
074: public synchronized void setFirstLine(Line line) {
075: firstLine = line;
076: }
077:
078: public final Position getEnd() {
079: Line line = firstLine;
080: if (line == null)
081: return null;
082: while (line.next() != null)
083: line = line.next();
084: return new Position(line, line.length());
085: }
086:
087: public final File getFile() {
088: return file;
089: }
090:
091: public final void setFile(File file) {
092: this .file = file;
093: }
094:
095: public final synchronized boolean isLoaded() {
096: return isLoaded;
097: }
098:
099: public final synchronized void setLoaded(boolean b) {
100: isLoaded = b;
101: }
102:
103: public final Mode getMode() {
104: return mode;
105: }
106:
107: public final int getModeId() {
108: return mode == null ? 0 : mode.getId();
109: }
110:
111: public final String getModeName() {
112: return mode == null ? null : mode.toString();
113: }
114:
115: public synchronized final List getTags() {
116: return tags;
117: }
118:
119: public synchronized final void setTags(List tags) {
120: this .tags = tags;
121: }
122:
123: public final void setForceReadOnly(boolean b) {
124: forceReadOnly = b;
125: }
126:
127: public String getLineSeparator() {
128: return lineSeparator;
129: }
130:
131: public final boolean contains(Line line) {
132: Line l = getFirstLine();
133: while (l != null) {
134: if (l == line)
135: return true;
136: l = l.next();
137: }
138: return false;
139: }
140:
141: public int load() {
142: if (!isLoaded) {
143: try {
144: if (file.isFile()) {
145: InputStream in = file.getInputStream();
146: if (in != null) {
147: load(in, file.getEncoding());
148: in.close();
149: }
150: }
151: if (getFirstLine() == null) {
152: // New or 0-byte file.
153: appendLine("");
154: lineSeparator = System
155: .getProperty("line.separator");
156: }
157: } catch (IOException e) {
158: Log.error(e);
159: }
160: isLoaded = true;
161: }
162: return LOAD_COMPLETED;
163: }
164:
165: public void load(InputStream istream, String encoding) {
166: if (mode != null && mode.getId() == BINARY_MODE) {
167: loadBinary(istream);
168: return;
169: }
170: byte[] buf = new byte[4096];
171: int totalBytes = 0;
172: try {
173: int bytesRead = istream.read(buf);
174: loadProgress(totalBytes = totalBytes + bytesRead);
175: // Detect Unicode.
176: boolean isUnicode = false;
177: boolean isLittleEndian = true;
178: if (bytesRead >= 2) {
179: byte byte1 = buf[0];
180: byte byte2 = buf[1];
181: if (byte1 == (byte) 0xfe && byte2 == (byte) 0xff) {
182: isUnicode = true;
183: isLittleEndian = false;
184: loadEncoding = "UnicodeBig";
185: } else if (byte1 == (byte) 0xff && byte2 == (byte) 0xfe) {
186: isUnicode = true;
187: loadEncoding = "UnicodeLittle";
188: }
189: }
190: boolean skipLF = false;
191: if (isUnicode) {
192: FastStringBuffer sb = new FastStringBuffer(256);
193: int i = 2;
194: while (bytesRead > 0) {
195: while (i < bytesRead - 1) {
196: char c;
197: final byte b1 = buf[i++];
198: final byte b2 = buf[i++];
199: if (isLittleEndian)
200: c = (char) ((b2 << 8) + (b1 & 0xff));
201: else
202: c = (char) ((b1 << 8) + (b2 & 0xff));
203: switch (c) {
204: case '\r':
205: appendLine(sb.toString());
206: sb.setLength(0);
207: skipLF = true;
208: break;
209: case '\n':
210: if (skipLF) {
211: // LF after CR.
212: if (lineSeparator == null)
213: lineSeparator = "\r\n";
214: skipLF = false;
215: } else {
216: // LF without preceding CR.
217: if (lineSeparator == null)
218: lineSeparator = "\n";
219: appendLine(sb.toString());
220: sb.setLength(0);
221: }
222: break;
223: default:
224: // Normal char.
225: if (skipLF) {
226: // Something other than LF after CR. Must be a Mac...
227: if (lineSeparator == null)
228: lineSeparator = "\r";
229: skipLF = false;
230: }
231: sb.append(c);
232: break;
233: }
234: }
235: bytesRead = istream.read(buf);
236: i = 0;
237: }
238: if (sb.length() > 0) {
239: // No line separator at end of file.
240: appendLine(sb.toString());
241: } else {
242: // If there is a line separator at the end of the file, we
243: // need to append an empty line so the line separator will
244: // get written out when the file is saved.
245: appendLine("");
246: }
247: } else {
248: // Not Unicode.
249: if (encoding == null) {
250: encoding = Editor.preferences().getStringProperty(
251: Property.DEFAULT_ENCODING);
252: }
253: loadEncoding = encoding;
254: ByteBuffer bb = new ByteBuffer(256);
255: while (bytesRead > 0) {
256: for (int i = 0; i < bytesRead; i++) {
257: byte b = buf[i];
258: switch (b) {
259: case 13:
260: appendLine(new String(bb.getBytes(), 0, bb
261: .length(), encoding));
262: bb.setLength(0);
263: skipLF = true;
264: break;
265: case 10:
266: if (skipLF) {
267: // LF after CR.
268: if (lineSeparator == null)
269: lineSeparator = "\r\n";
270: skipLF = false;
271: } else {
272: // LF without preceding CR.
273: if (lineSeparator == null)
274: lineSeparator = "\n";
275: appendLine(new String(bb.getBytes(), 0,
276: bb.length(), encoding));
277: bb.setLength(0);
278: }
279: break;
280: default:
281: // Normal char.
282: if (skipLF) {
283: // Something other than LF after CR. Must be a Mac...
284: if (lineSeparator == null)
285: lineSeparator = "\r";
286: skipLF = false;
287: }
288: bb.append(b);
289: break;
290: }
291: }
292: bytesRead = istream.read(buf);
293: if (bytesRead > 0)
294: loadProgress(totalBytes = totalBytes
295: + bytesRead);
296: }
297: if (bb.length() > 0) {
298: // No line separator at end of file.
299: appendLine(new String(bb.getBytes(), 0,
300: bb.length(), encoding));
301: } else {
302: // If there is a line separator at the end of the file, we
303: // need to append an empty line so the line separator will
304: // get written out when the file is saved.
305: appendLine("");
306: }
307: }
308: isLoaded = true;
309: } catch (Exception e) {
310: Log.error(e);
311: }
312: loadFinished(isLoaded);
313: }
314:
315: public final Line getLastLine() {
316: return lastLine;
317: }
318:
319: public final void setLastLine(Line line) {
320: lastLine = line;
321: }
322:
323: protected void appendLine(Line line) {
324: line.setPrevious(lastLine);
325: if (lastLine != null)
326: lastLine.setNext(line);
327: lastLine = line;
328: if (getFirstLine() == null)
329: setFirstLine(line);
330: }
331:
332: public void appendLine(String s) {
333: appendLine(new TextLine(s));
334: }
335:
336: public void append(String s) {
337: int begin = 0;
338: int end = 0;
339: boolean skipLF = false;
340: final int limit = s.length();
341: for (int i = 0; i < limit; i++) {
342: switch (s.charAt(i)) {
343: case '\r':
344: appendLine(s.substring(begin, end));
345: ++end;
346: begin = end;
347: skipLF = true;
348: break;
349: case '\n':
350: if (skipLF) {
351: ++begin;
352: ++end;
353: skipLF = false;
354: } else {
355: appendLine(s.substring(begin, end));
356: ++end;
357: begin = end;
358: }
359: break;
360: default:
361: skipLF = false;
362: ++end;
363: }
364: }
365: if (begin < end)
366: appendLine(s.substring(begin, end));
367: }
368:
369: private void appendBinaryLine(int start, byte[] bytes, int numBytes) {
370: appendLine(new BinaryLine(start, bytes, numBytes));
371: }
372:
373: // Overridden by Buffer.renumber().
374: public void renumber() {
375: for (Line line = getFirstLine(); line != null; line = line
376: .next())
377: line.setLineNumber(lineCount++);
378: }
379:
380: public void writeBuffer() throws SaveException {
381: if (file.isFile() && !file.canWrite()) {
382: Log.error("writeFile: file is not writable: " + file);
383: throw new SaveException(file, file.canonicalPath()
384: + " is not writable");
385: }
386: if (Platform.isPlatformWindows()) {
387: // writeTemporaryFile() throws a SaveException if an error occurs.
388: File tempFile = writeTemporaryFile();
389: if (!makePatchFile()) {
390: if (!Utilities.makeBackup(file, false)) {
391: Log.error("backup failed");
392: throw new SaveException(file,
393: "Unable to write backup file for "
394: + file.canonicalPath());
395: }
396: }
397: if (!Utilities.deleteRename(tempFile, file)) {
398: Log.error("unable to rename "
399: + tempFile.canonicalPath() + " to "
400: + file.canonicalPath());
401: throw new SaveException(file,
402: "Unable to rename temporary file");
403: }
404: } else {
405: // Save in place on Unix to preserve permissions and ownership of
406: // file. Keep original (instead of renaming it) when making backup.
407: if (!makePatchFile()) {
408: if (!Utilities.makeBackup(file, true)) {
409: Log.error("backup failed");
410: throw new SaveException(file,
411: "Unable to write backup file for "
412: .concat(file.canonicalPath()));
413: }
414: }
415: // Write directly to original file.
416: if (!writeFile(file)) {
417: Log.error("writeFile failed");
418: throw new SaveException(file, "Unable to write "
419: .concat(file.canonicalPath()));
420: }
421: }
422: }
423:
424: // Returns true if patch file was created successfully.
425: private final boolean makePatchFile() {
426: if (file.isFile()) {
427: File patchFile = getPatchFile();
428: if (patchFile != null) {
429: if (!patchFile.isFile())
430: return Utilities.copyFile(file, patchFile);
431: }
432: }
433: return false;
434: }
435:
436: // Returns null if "patchmode" preference is not set.
437: // Public for DiffMode.diff().
438: public final File getPatchFile() {
439: String suffix;
440: if (this instanceof Buffer)
441: suffix = ((Buffer) this )
442: .getStringProperty(Property.PATCH_MODE);
443: else if (mode != null)
444: suffix = mode.getStringProperty(Property.PATCH_MODE);
445: else {
446: suffix = Editor.preferences().getStringProperty(
447: Property.PATCH_MODE);
448: }
449: if (suffix != null) {
450: suffix = suffix.trim();
451: if (suffix.length() > 0) {
452: if (suffix.charAt(0) != '.')
453: suffix = ".".concat(suffix);
454: return File.getInstance(file.canonicalPath().concat(
455: suffix));
456: }
457: }
458: return null;
459: }
460:
461: public boolean writeFile(File outputFile) {
462: try {
463: BufferedOutputStream out = new BufferedOutputStream(
464: outputFile.getOutputStream());
465: if (lineSeparator == null)
466: lineSeparator = System.getProperty("line.separator");
467: String encoding = outputFile.getEncoding();
468: if (encoding == null)
469: encoding = getSaveEncoding();
470: Line line = getFirstLine();
471: if (line != null) {
472: final byte[] byteOrderMark = getByteOrderMark(encoding);
473: if (byteOrderMark != null)
474: out.write(byteOrderMark);
475: out.write(line.getBytes(encoding));
476: line = line.next();
477: final byte[] sepBytes = getSeparatorBytes(encoding);
478: while (line != null) {
479: out.write(sepBytes);
480: out.write(line.getBytes(encoding));
481: line = line.next();
482: }
483: }
484: out.flush();
485: out.close();
486: return true;
487: } catch (IOException e) {
488: Log.error(e);
489: return false;
490: }
491: }
492:
493: public String getSaveEncoding() {
494: String encoding = file == null ? null : file.getEncoding();
495: if (encoding == null) {
496: encoding = loadEncoding;
497: if (encoding == null)
498: encoding = Editor.preferences().getStringProperty(
499: Property.DEFAULT_ENCODING);
500: }
501: if (encoding == null)
502: Debug.bug();
503: return encoding;
504: }
505:
506: byte[] getByteOrderMark(String encoding)
507: throws UnsupportedEncodingException {
508: byte[] bytes = "test".getBytes(encoding);
509: if ((bytes[0] == (byte) 0xfe && bytes[1] == (byte) 0xff)
510: || (bytes[0] == (byte) 0xff && bytes[1] == (byte) 0xfe)) {
511: byte[] byteOrderMark = new byte[2];
512: byteOrderMark[0] = bytes[0];
513: byteOrderMark[1] = bytes[1];
514: return byteOrderMark;
515: }
516: return null;
517: }
518:
519: byte[] getSeparatorBytes(String encoding)
520: throws UnsupportedEncodingException {
521: byte[] bytes = lineSeparator.getBytes(encoding);
522: if (bytes.length > 2) {
523: if ((bytes[0] == (byte) 0xfe && bytes[1] == (byte) 0xff)
524: || (bytes[0] == (byte) 0xff && bytes[1] == (byte) 0xfe)) {
525: byte[] sepBytes = new byte[bytes.length - 2];
526: for (int i = 0; i < sepBytes.length; i++)
527: sepBytes[i] = bytes[i + 2];
528: return sepBytes;
529: }
530: }
531: return bytes;
532: }
533:
534: /*package*/synchronized void _empty() {
535: Line line = getFirstLine();
536: while (line != null) {
537: Line nextLine = line.next();
538: line.setPrevious(null);
539: line.setNext(null);
540: line = nextLine;
541: }
542: setFirstLine(null);
543: lastLine = null;
544: isLoaded = false;
545: }
546:
547: protected void loadProgress(int totalBytesRead) {
548: // Default behavior is to do nothing.
549: }
550:
551: protected void loadFinished(boolean success) {
552: // Default behavior is to do nothing.
553: }
554:
555: private void loadBinary(InputStream istream) {
556: byte[] array = readAllBytes(istream);
557: if (array != null) {
558: for (int start = 0; start < array.length; start += 16) {
559: int bytesLeft = array.length - start;
560: int count = bytesLeft >= 16 ? 16 : bytesLeft;
561: appendBinaryLine(start, array, count);
562: }
563: isLoaded = true;
564: }
565: loadFinished(isLoaded);
566: }
567:
568: private byte[] readAllBytes(InputStream in) {
569: final int chunkSize = 0x8000;
570: byte[] array = null;
571: int totalBytes = 0;
572: byte[] chunk = new byte[chunkSize];
573: int bytesRead;
574: try {
575: while ((bytesRead = in.read(chunk, 0, chunk.length)) > 0) {
576: if (array == null) {
577: array = new byte[bytesRead];
578: System.arraycopy(chunk, 0, array, 0, bytesRead);
579: } else {
580: // Allocate new array big enough to hold all the bytes.
581: byte[] newArray = new byte[totalBytes + bytesRead];
582:
583: // Copy old array into new array.
584: if (totalBytes > 0)
585: System.arraycopy(array, 0, newArray, 0,
586: totalBytes);
587:
588: // Append current chunk.
589: System.arraycopy(chunk, 0, newArray, totalBytes,
590: bytesRead);
591:
592: array = newArray;
593: }
594: totalBytes += bytesRead;
595: Debug.assertTrue(array.length == totalBytes);
596: }
597: } catch (IOException e) {
598: Log.error(e);
599: array = null;
600: }
601: return array;
602: }
603:
604: private File writeTemporaryFile() throws SaveException {
605: boolean succeeded = false;
606: // First try to write out a temporary file in the current directory.
607: File tempFile = Utilities.getTempFile(file.getParent());
608: if (tempFile != null)
609: succeeded = writeFile(tempFile);
610: if (!succeeded) {
611: // We were not able to write out the temporary file in the current
612: // directory, possibly because the current directory is not
613: // writable. Try again using j's temp directory.
614: tempFile = Utilities.getTempFile();
615: if (tempFile != null)
616: succeeded = writeFile(tempFile);
617: }
618: if (!succeeded) {
619: throw new SaveException(file,
620: "Unable to write temporary file for ".concat(file
621: .canonicalPath()));
622: }
623: return tempFile;
624: }
625: }
|