001: /*
002: * FileChannel.java --
003: *
004: * Copyright (c) 1997 Sun Microsystems, Inc.
005: *
006: * See the file "license.terms" for information on usage and
007: * redistribution of this file, and for a DISCLAIMER OF ALL
008: * WARRANTIES.
009: *
010: * RCS: @(#) $Id: FileChannel.java,v 1.22 2006/07/11 09:10:44 mdejong Exp $
011: *
012: */
013:
014: package tcl.lang;
015:
016: import java.util.*;
017: import java.io.*;
018:
019: /**
020: * Subclass of the abstract class Channel. It implements all of the
021: * methods to perform read, write, open, close, etc on a file.
022: */
023:
024: class FileChannel extends Channel {
025:
026: /**
027: * The file needs to have a file pointer that can be moved randomly
028: * within the file. The RandomAccessFile is the only java.io class
029: * that allows this behavior.
030: */
031:
032: private RandomAccessFile file = null;
033:
034: /**
035: * Open a file with the read/write permissions determined by modeFlags.
036: * This method must be called before any other methods will function
037: * properly.
038: *
039: * @param interp currrent interpreter.
040: * @param fileName the absolute path or name of file in the current
041: * directory to open
042: * @param modeFlags modes used to open a file for reading, writing, etc
043: * @return the channelId of the file.
044: * @exception TclException is thrown when the modeFlags try to open
045: * a file it does not have permission for or if the
046: * file dosent exist and CREAT wasnt specified.
047: * @exception IOException is thrown when an IO error occurs that was not
048: * correctly tested for. Most cases should be caught.
049: */
050:
051: String open(Interp interp, String fileName, int modeFlags)
052: throws IOException, TclException {
053:
054: mode = modeFlags;
055: File fileObj = FileUtil.getNewFileObj(interp, fileName);
056:
057: // Raise error if file exists and both CREAT and EXCL are set
058:
059: if (((modeFlags & TclIO.CREAT) != 0)
060: && ((modeFlags & TclIO.EXCL) != 0) && fileObj.exists()) {
061: throw new TclException(interp, "couldn't open \""
062: + fileName + "\": file exists");
063: }
064:
065: if (((modeFlags & TclIO.CREAT) != 0) && !fileObj.exists()) {
066: // Creates the file and closes it so it may be
067: // reopened with the correct permissions. (w, w+, a+)
068:
069: file = new RandomAccessFile(fileObj, "rw");
070: file.close();
071: }
072:
073: if ((modeFlags & TclIO.RDWR) != 0) {
074: // Opens file (r+), error if file does not exist.
075:
076: checkFileExists(interp, fileObj);
077: checkReadWritePerm(interp, fileObj, 0);
078:
079: if (fileObj.isDirectory()) {
080: throw new TclException(interp, "couldn't open \""
081: + fileName
082: + "\": illegal operation on a directory");
083: }
084:
085: file = new RandomAccessFile(fileObj, "rw");
086:
087: } else if ((modeFlags & TclIO.RDONLY) != 0) {
088: // Opens file (r), error if file does not exist.
089:
090: checkFileExists(interp, fileObj);
091: checkReadWritePerm(interp, fileObj, -1);
092:
093: if (fileObj.isDirectory()) {
094: throw new TclException(interp, "couldn't open \""
095: + fileName
096: + "\": illegal operation on a directory");
097: }
098:
099: file = new RandomAccessFile(fileObj, "r");
100:
101: } else if ((modeFlags & TclIO.WRONLY) != 0) {
102: // Opens file (a), error if dosent exist.
103:
104: checkFileExists(interp, fileObj);
105: checkReadWritePerm(interp, fileObj, 1);
106:
107: if (fileObj.isDirectory()) {
108: throw new TclException(interp, "couldn't open \""
109: + fileName
110: + "\": illegal operation on a directory");
111: }
112:
113: // Currently there is a limitation in the Java API.
114: // A file can only be opened for read OR read-write.
115: // Therefore if the file is write only, Java cannot
116: // open the file. Throw an error indicating this
117: // limitation.
118:
119: if (!fileObj.canRead()) {
120: throw new TclException(
121: interp,
122: "Java IO limitation: Cannot open a file "
123: + "that has only write permissions set.");
124: }
125: file = new RandomAccessFile(fileObj, "rw");
126:
127: } else {
128: throw new TclRuntimeError(
129: "FileChannel.java: invalid mode value");
130: }
131:
132: // If we are appending, move the file pointer to EOF.
133:
134: if ((modeFlags & TclIO.APPEND) != 0) {
135: file.seek(file.length());
136: }
137:
138: // Truncate file to zero length, this has to be done after
139: // opening the file so that it will not fail even when another
140: // handle to this same file is also open.
141:
142: if ((modeFlags & TclIO.TRUNC) != 0) {
143: java.nio.channels.FileChannel chan = file.getChannel();
144: chan.truncate(0);
145: }
146:
147: // In standard Tcl fashion, set the channelId to be "file" + the
148: // value of the current FileDescriptor.
149:
150: String fName = TclIO.getNextDescriptor(interp, "file");
151: setChanName(fName);
152: return fName;
153: }
154:
155: /**
156: * Close the file. The file MUST be open or a TclRuntimeError
157: * is thrown.
158: */
159:
160: void close() throws IOException {
161: if (file == null) {
162: throw new TclRuntimeError(
163: "FileChannel.close(): null file object");
164: }
165:
166: // Invoke super.close() first since it might write an eof char
167: try {
168: super .close();
169: } finally {
170: file.close();
171: file = null;
172: }
173: }
174:
175: /**
176: * Move the file pointer internal to the RandomAccessFile object.
177: * The file MUST be open or a TclRuntimeError is thrown.
178: *
179: * @param offset The number of bytes to move the file pointer.
180: * @param inmode to begin incrementing the file pointer; beginning,
181: * current, or end of the file.
182: */
183:
184: void seek(Interp interp, long offset, int inmode)
185: throws IOException, TclException {
186:
187: if (file == null) {
188: throw new TclRuntimeError(
189: "FileChannel.seek(): null file object");
190: }
191:
192: //FIXME: Disallow seek on dead channels (raise TclPosixException ??)
193: //if (CheckForDeadChannel(NULL, statePtr)) {
194: // return Tcl_LongAsWide(-1);
195: //}
196:
197: // Compute how much input and output is buffered. If both input and
198: // output is buffered, cannot compute the current position.
199:
200: int inputBuffered = getNumBufferedInputBytes();
201: int outputBuffered = getNumBufferedOutputBytes();
202:
203: if ((inputBuffered != 0) && (outputBuffered != 0)) {
204: throw new TclPosixException(interp,
205: TclPosixException.EFAULT, true,
206: "error during seek on \"" + getChanName() + "\"");
207: }
208:
209: // If we are seeking relative to the current position, compute the
210: // corrected offset taking into account the amount of unread input.
211:
212: if (inmode == TclIO.SEEK_CUR) {
213: offset -= inputBuffered;
214: }
215:
216: // The seekReset method will discard queued input and
217: // reset flags like EOF and BLOCKED.
218:
219: if (input != null) {
220: input.seekReset();
221: }
222:
223: // FIXME: Next block is disabled since non-blocking is not implemented.
224: // If the channel is in asynchronous output mode, switch it back
225: // to synchronous mode and cancel any async flush that may be
226: // scheduled. After the flush, the channel will be put back into
227: // asynchronous output mode.
228:
229: boolean wasAsync = false;
230: if (false && !getBlocking()) {
231: wasAsync = true;
232: setBlocking(true);
233: if (isBgFlushScheduled()) {
234: //scheduleBgFlush();
235: }
236: }
237:
238: // If there is data buffered in curOut then mark the
239: // channel as ready to flush before invoking flushChannel.
240:
241: if (output != null) {
242: output.seekCheckBuferReady();
243: }
244:
245: // If the flush fails we cannot recover the original position. In
246: // that case the seek is not attempted because we do not know where
247: // the access position is - instead we return the error. FlushChannel
248: // has already called Tcl_SetErrno() to report the error upwards.
249: // If the flush succeeds we do the seek also.
250:
251: if (output != null && output.flushChannel(null, false) != 0) {
252: // FIXME: IS this the proper action to take on error?
253: throw new IOException("flush error while seeking");
254: } else {
255: // Now seek to the new position in the channel as requested by the
256: // caller.
257:
258: long actual_offset;
259:
260: switch (inmode) {
261: case TclIO.SEEK_SET: {
262: actual_offset = offset;
263: break;
264: }
265: case TclIO.SEEK_CUR: {
266: actual_offset = file.getFilePointer() + offset;
267: break;
268: }
269: case TclIO.SEEK_END: {
270: actual_offset = file.length() + offset;
271: break;
272: }
273: default: {
274: throw new TclRuntimeError("invalid seek mode");
275: }
276: }
277:
278: // A negative offset to seek() would raise an IOException, but
279: // we want to raise an invalid argument error instead
280:
281: if (actual_offset < 0) {
282: throw new TclPosixException(interp,
283: TclPosixException.EINVAL, true,
284: "error during seek on \"" + getChanName()
285: + "\"");
286: }
287:
288: file.seek(actual_offset);
289: }
290:
291: // Restore to nonblocking mode if that was the previous behavior.
292: //
293: // NOTE: Even if there was an async flush active we do not restore
294: // it now because we already flushed all the queued output, above.
295:
296: if (wasAsync) {
297: setBlocking(false);
298: }
299: }
300:
301: /**
302: * Tcl_Tell -> tell
303: *
304: * Return the current offset of the file pointer in number of bytes from
305: * the beginning of the file. The file MUST be open or a TclRuntimeError
306: * is thrown.
307: *
308: * @return The current value of the file pointer.
309: */
310:
311: long tell() throws IOException {
312: if (file == null) {
313: throw new TclRuntimeError(
314: "FileChannel.tell(): null file object");
315: }
316: int inputBuffered = getNumBufferedInputBytes();
317: int outputBuffered = getNumBufferedOutputBytes();
318:
319: if ((inputBuffered != 0) && (outputBuffered != 0)) {
320: // FIXME: Posix error EFAULT ?
321: return -1;
322: }
323: long curPos = file.getFilePointer();
324: if (curPos == -1) {
325: // FIXME: Set errno here?
326: return -1;
327: }
328: if (inputBuffered != 0) {
329: return curPos - inputBuffered;
330: }
331: return curPos + outputBuffered;
332: }
333:
334: /**
335: * If the file dosent exist then a TclExcpetion is thrown.
336: *
337: * @param interp currrent interpreter.
338: * @param fileObj a java.io.File object of the file for this channel.
339: */
340:
341: private void checkFileExists(Interp interp, File fileObj)
342: throws TclException {
343: if (!fileObj.exists()) {
344: throw new TclPosixException(interp,
345: TclPosixException.ENOENT, true, "couldn't open \""
346: + fileObj.getName() + "\"");
347: }
348: }
349:
350: /**
351: * Checks the read/write permissions on the File object. If inmode is less
352: * than 0 it checks for read permissions, if mode greater than 0 it checks
353: * for write permissions, and if it equals 0 then it checks both.
354: *
355: * @param interp currrent interpreter.
356: * @param fileObj a java.io.File object of the file for this channel.
357: * @param inmode what permissions to check for.
358: */
359:
360: private void checkReadWritePerm(Interp interp, File fileObj,
361: int inmode) throws TclException {
362: boolean error = false;
363:
364: if (inmode <= 0) {
365: if (!fileObj.canRead()) {
366: error = true;
367: }
368: }
369: if (inmode >= 0) {
370: if (!fileObj.canWrite()) {
371: error = true;
372: }
373: }
374: if (error) {
375: throw new TclPosixException(interp,
376: TclPosixException.EACCES, true, "couldn't open \""
377: + fileObj.getName() + "\"");
378: }
379: }
380:
381: String getChanType() {
382: return "file";
383: }
384:
385: protected InputStream getInputStream() throws IOException {
386: return new FileInputStream(file.getFD());
387: }
388:
389: protected OutputStream getOutputStream() throws IOException {
390: return new FileOutputStream(file.getFD());
391: }
392: }
|