001: /*
002: * FileStream.java
003: *
004: * Copyright (C) 2004 Peter Graves
005: * $Id: FileStream.java,v 1.16 2004/09/18 20:28:19 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.lisp;
023:
024: import java.io.File;
025: import java.io.FileNotFoundException;
026: import java.io.IOException;
027: import java.io.RandomAccessFile;
028:
029: public final class FileStream extends Stream {
030: private static final int BUFSIZE = 4096;
031:
032: private final RandomAccessFile raf;
033: private final Pathname pathname;
034: private final int bytesPerUnit;
035: private final byte[] outputBuffer;
036:
037: private int outputBufferOffset;
038:
039: public FileStream(Pathname pathname, String namestring,
040: LispObject elementType, LispObject direction,
041: LispObject ifExists) throws IOException {
042: File file = new File(namestring);
043: String mode = null;
044: if (direction == Keyword.INPUT) {
045: mode = "r";
046: isInputStream = true;
047: } else if (direction == Keyword.OUTPUT) {
048: mode = "rw";
049: isOutputStream = true;
050: } else if (direction == Keyword.IO) {
051: mode = "rw";
052: isInputStream = true;
053: isOutputStream = true;
054: }
055: Debug.assertTrue(mode != null);
056: raf = new RandomAccessFile(file, mode);
057: // ifExists is ignored unless we have an output stream.
058: if (isOutputStream) {
059: if (ifExists == Keyword.OVERWRITE)
060: raf.seek(0);
061: else if (ifExists == Keyword.APPEND)
062: raf.seek(raf.length());
063: else
064: raf.setLength(0);
065: }
066: this .pathname = pathname;
067: this .elementType = elementType;
068: if (elementType == Symbol.CHARACTER
069: || elementType == Symbol.BASE_CHAR) {
070: isCharacterStream = true;
071: bytesPerUnit = 1;
072: } else {
073: isBinaryStream = true;
074: int width;
075: try {
076: width = Fixnum.getValue(elementType.cadr());
077: } catch (ConditionThrowable t) {
078: width = 8;
079: }
080: bytesPerUnit = width / 8;
081: }
082: if (isBinaryStream && isOutputStream && !isInputStream
083: && bytesPerUnit == 1)
084: outputBuffer = new byte[BUFSIZE];
085: else
086: outputBuffer = null;
087: }
088:
089: public LispObject typeOf() {
090: return Symbol.FILE_STREAM;
091: }
092:
093: public LispClass classOf() {
094: return BuiltInClass.FILE_STREAM;
095: }
096:
097: public LispObject typep(LispObject typeSpecifier)
098: throws ConditionThrowable {
099: if (typeSpecifier == Symbol.FILE_STREAM)
100: return T;
101: if (typeSpecifier == BuiltInClass.FILE_STREAM)
102: return T;
103: return super .typep(typeSpecifier);
104: }
105:
106: public Pathname getPathname() {
107: return pathname;
108: }
109:
110: public LispObject listen() throws ConditionThrowable {
111: try {
112: return raf.getFilePointer() < raf.length() ? T : NIL;
113: } catch (IOException e) {
114: signal(new StreamError(this , e));
115: // Not reached.
116: return NIL;
117: }
118: }
119:
120: public LispObject fileLength() throws ConditionThrowable {
121: final long length;
122: if (isOpen()) {
123: try {
124: length = raf.length();
125: } catch (IOException e) {
126: signal(new StreamError(this , e));
127: // Not reached.
128: return NIL;
129: }
130: } else {
131: String namestring = pathname.getNamestring();
132: if (namestring == null)
133: return signal(new SimpleError(
134: "Pathname has no namestring: "
135: + pathname.writeToString()));
136: File file = new File(namestring);
137: length = file.length(); // in 8-bit bytes
138: }
139: if (isCharacterStream)
140: return number(length);
141: // "For a binary file, the length is measured in units of the
142: // element type of the stream."
143: return number(length / bytesPerUnit);
144: }
145:
146: // Returns -1 at end of file.
147: protected int _readChar() throws ConditionThrowable {
148: try {
149: return raf.read();
150: } catch (IOException e) {
151: signal(new StreamError(this , e));
152: // Not reached.
153: return -1;
154: }
155: }
156:
157: protected void _unreadChar(int n) throws ConditionThrowable {
158: try {
159: long pos = raf.getFilePointer();
160: if (pos > 0)
161: raf.seek(pos - 1);
162: } catch (IOException e) {
163: signal(new StreamError(this , e));
164: }
165: }
166:
167: protected boolean _charReady() throws ConditionThrowable {
168: return true;
169: }
170:
171: public void _writeChar(char c) throws ConditionThrowable {
172: try {
173: raf.write((byte) c);
174: if (c == '\n')
175: charPos = 0;
176: else
177: ++charPos;
178: } catch (IOException e) {
179: signal(new StreamError(this , e));
180: }
181: }
182:
183: public void _writeChars(char[] chars, int start, int end)
184: throws ConditionThrowable {
185: _writeString(new String(chars, start, end - start));
186: }
187:
188: public void _writeString(String s) throws ConditionThrowable {
189: try {
190: raf.writeBytes(s);
191: int index = s.lastIndexOf('\n');
192: if (index < 0)
193: charPos += s.length();
194: else
195: charPos = s.length() - (index + 1);
196: } catch (IOException e) {
197: signal(new StreamError(this , e));
198: }
199: }
200:
201: public void _writeLine(String s) throws ConditionThrowable {
202: try {
203: raf.writeBytes(s);
204: raf.write((byte) '\n');
205: charPos = 0;
206: } catch (IOException e) {
207: signal(new StreamError(this , e));
208: }
209: }
210:
211: // Reads an 8-bit byte.
212: public int _readByte() throws ConditionThrowable {
213: try {
214: return raf.read(); // Reads an 8-bit byte.
215: } catch (IOException e) {
216: signal(new StreamError(this , e));
217: // Not reached.
218: return -1;
219: }
220: }
221:
222: // Writes an 8-bit byte.
223: public void _writeByte(int n) throws ConditionThrowable {
224: if (outputBuffer != null) {
225: writeByteToBuffer((byte) n);
226: } else {
227: try {
228: raf.write((byte) n); // Writes an 8-bit byte.
229: } catch (IOException e) {
230: signal(new StreamError(this , e));
231: }
232: }
233: }
234:
235: public void _finishOutput() throws ConditionThrowable {
236: if (outputBuffer != null)
237: flushOutputBuffer();
238: }
239:
240: public void _clearInput() throws ConditionThrowable {
241: try {
242: raf.seek(raf.length());
243: } catch (IOException e) {
244: signal(new StreamError(this , e));
245: }
246: }
247:
248: protected long _getFilePosition() throws ConditionThrowable {
249: if (outputBuffer != null)
250: flushOutputBuffer();
251: try {
252: long pos = raf.getFilePointer();
253: return pos / bytesPerUnit;
254: } catch (IOException e) {
255: signal(new StreamError(this , e));
256: // Not reached.
257: return -1;
258: }
259: }
260:
261: protected boolean _setFilePosition(LispObject arg)
262: throws ConditionThrowable {
263: if (outputBuffer != null)
264: flushOutputBuffer();
265: try {
266: long pos;
267: if (arg == Keyword.START)
268: pos = 0;
269: else if (arg == Keyword.END)
270: pos = raf.length();
271: else {
272: long n = Fixnum.getValue(arg); // FIXME arg might be a bignum
273: pos = n * bytesPerUnit;
274: }
275: raf.seek(pos);
276: } catch (IOException e) {
277: signal(new StreamError(this , e));
278: }
279: return true;
280: }
281:
282: public void _close() throws ConditionThrowable {
283: if (outputBuffer != null)
284: flushOutputBuffer();
285: try {
286: raf.close();
287: setOpen(false);
288: } catch (IOException e) {
289: signal(new StreamError(this , e));
290: }
291: }
292:
293: private void writeByteToBuffer(byte b) throws ConditionThrowable {
294: if (outputBufferOffset == BUFSIZE)
295: flushOutputBuffer();
296: outputBuffer[outputBufferOffset++] = b;
297: }
298:
299: private void flushOutputBuffer() throws ConditionThrowable {
300: if (outputBufferOffset > 0) {
301: try {
302: raf.write(outputBuffer, 0, outputBufferOffset);
303: outputBufferOffset = 0;
304: } catch (IOException e) {
305: signal(new StreamError(this , e));
306: }
307: }
308: }
309:
310: public String toString() {
311: return unreadableString("FILE-STREAM");
312: }
313:
314: // ### make-file-stream pathname element-type direction if-exists => stream
315: private static final Primitive MAKE_FILE_STREAM = new Primitive(
316: "make-file-stream", PACKAGE_SYS, false,
317: "pathname element-type direction if-exists") {
318: public LispObject execute(LispObject first, LispObject second,
319: LispObject third, LispObject fourth)
320: throws ConditionThrowable {
321: Pathname pathname = Pathname.coerceToPathname(first);
322: LispObject elementType = second;
323: LispObject direction = third;
324: LispObject ifExists = fourth;
325: if (direction != Keyword.INPUT
326: && direction != Keyword.OUTPUT
327: && direction != Keyword.IO)
328: signal(new LispError(
329: "Direction must be :INPUT, :OUTPUT, or :IO."));
330: String namestring = pathname.getNamestring();
331: if (namestring == null)
332: return NIL;
333: try {
334: return new FileStream(pathname, namestring,
335: elementType, direction, ifExists);
336: } catch (FileNotFoundException e) {
337: return NIL;
338: } catch (IOException e) {
339: return signal(new StreamError(null, e));
340: }
341: }
342: };
343: }
|