Artifact 8047f2acefadcb59e793be6e957d75b377a4eaa01d5f16344137e9a2574cfcdc:
- Executable file
r37/lisp/csl/jlisp/PDS.java
— part of check-in
[f2fda60abd]
at
2011-09-02 18:13:33
on branch master
— Some historical releases purely for archival purposes
git-svn-id: https://svn.code.sf.net/p/reduce-algebra/code/trunk/historical@1375 2bfe0521-f11c-4a00-b80e-6202646ff360 (user: arthurcnorman@users.sourceforge.net, size: 16672) [annotate] [blame] [check-ins using] [more...]
// // This file is part of the Jlisp implementation of Standard Lisp // Copyright \u00a9 (C) Codemist Ltd, 1998-2000. // import java.io.*; import java.util.*; // This class (and PDSInputStream & PDSOutputStream) support a crude // version of a file-system-within-a-file. No sub-directoried are // allowed. Allocation is strictly sequential. There are potentially // SEVERE constraints on having access to multiple members of the // one file. These rules are // (a) All access must be strictly nested. If a member x is open // for either reading or writing and one opens a different member // y then you may not read from or write to x again until y has // been closed. // (b) Only one member may be open for writing at any one time. // Adherence to these constraints my not be fully checked, and violating // them might cause arbitrary confusion. // // Failure to close a member after opening it for writing will leave // that member visible but with zero length. // Exiting from the entire system without closing a PDS may leave it // containing old and unwanted material. // Replacing members of a PDS may cause the file to grow by a large // amount until it is closed. // Three bits of serious re-work are needed here and in related places: // (a) When I delete or replace a PDS member at present the space // that had been used is left vacant and wasted. The PDS should be // compacted sometime. // (b) Right now my code probably only supports a single image file, // and functions to get module dates, copy and rename modules etc are // not in place. They need to be provided. // // It might also be better to have two sub-classes of PDS for the versions // that read from a file and from in-store data? class PDS { String name; boolean writeable; boolean untidy; RandomAccessFile f; Vector data; HashMap directory; private static final int bufferShift = 12; private static final int bufferSize = 1<<bufferShift; private static final int bufferMask = bufferSize-1; private byte [] buffer; private long bufferPos, pos; private boolean bufferValid; void seek(long n) throws IOException { if (f != null) f.seek(n); else if (data != null) { pos = n; long newBufferPos = n & ~bufferMask; if (newBufferPos != bufferPos) { bufferPos = newBufferPos; bufferValid = false; } } } int readCount = 0; int read() throws IOException { int c; if (f != null) return f.read(); else if (data != null) { if (!bufferValid) { // unpack a new chunk of data... buffer = (byte [])data.get((int)(bufferPos >> bufferShift)); bufferValid = true; } int n = (int)(pos++ & bufferMask); c = buffer[n] & 0xff; if (n == bufferMask) { bufferPos += bufferSize; bufferValid = false; } return c; } else return -1; } int read(byte [] b, int off, int len) throws IOException { int c; if (f != null) return f.read(b, off, len); else if (data != null) { if (!bufferValid) { // unpack a new chunk of data... buffer = (byte [])data.get((int)(bufferPos >> bufferShift)); bufferValid = true; } int p = (int)(pos & bufferMask); int n = bufferSize - p; // bytes left in buffer if (n < len) len = n; // trim request for (int i=0; i<len; i++) b[off+i] = buffer[p+i]; pos += len; p += len; if (p == bufferSize) { bufferPos += bufferSize; bufferValid = false; } return len; } else return -1; } long getFilePointer() throws IOException { if (f != null) return f.getFilePointer(); else return pos; } long length() throws IOException { if (f != null) return f.length(); else if (data != null) return bufferSize*data.size(); else return 0; } int memberData, memberStart; void print() // print to Java standard output (for debugging) { Jlisp.println("PDS " + this + " " + name + " W=" + writeable + " U=" + untidy); Jlisp.println("f = " + f + " memberData = " + memberData); if (directory != null) { Vector v = new Vector(10, 10); for (Iterator k = directory.keySet().iterator(); k.hasNext();) { Object key = k.next(); PDSEntry val = (PDSEntry)directory.get(key); v.add(val); } Object [] v1 = v.toArray(); PDSEntry.ordering = PDSEntry.orderName; Arrays.sort(v1); for (int k = 0; k<v1.length; k++) { PDSEntry val = (PDSEntry)(v1[k]); StringBuffer sb = new StringBuffer(val.name); while (sb.length() < 24) sb.append(" "); sb.append("pos: " + val.loc); while (sb.length() < 38) sb.append(" "); sb.append("len: " + val.len); while (sb.length() < 50) sb.append(" "); sb.append(LispStream.dFormat.format(new Date(val.date))); Jlisp.println(sb.toString()); } } Jlisp.println("----"); } LispObject members() { LispObject r = Jlisp.nil; if (directory != null) { for (Iterator k = directory.keySet().iterator(); k.hasNext();) { Object key = k.next(); r = new Cons(new LispString((String)key), r); } } return r; } PDS(InputStream is) throws IOException { name = "Resource image data"; data = new Vector(500, 500); int k = 0; for (;;) { byte [] b = new byte[bufferSize]; int p = 0; while (p != bufferSize) { int n = is.read(b, p, bufferSize-p); if (n < 1) break; p += n; } data.add(b); k++; if (p < bufferSize) break; } is.close(); pos = 0; bufferValid = false; f = null; writeable = false; untidy = false; memberData = 0; memberStart = 0; directory = new HashMap(); try { readDirectory(); } catch (IOException e) { data = null; directory = null; throw e; } } PDS(String name, boolean writeable) throws IOException { this.name = name; this.writeable = writeable; data = null; untidy = false; memberData = 0; memberStart = 0; directory = new HashMap(); if (writeable) { File ff = new File(name); if (ff.exists()) { Jlisp.lispErr.println("File exists already"); if (ff.canWrite()) { Jlisp.lispErr.println("Can write to file OK"); f = new RandomAccessFile(ff, "rw"); Jlisp.lispErr.println("f = " + f); } else { f = new RandomAccessFile(ff, "r"); Jlisp.lispErr.println("Falling back to read-only access"); writeable = false; // fall back to read-only access } try { readDirectory(); Jlisp.lispErr.println("index read"); } catch (IOException e) { Jlisp.lispErr.println("IO error reading index " + e.getMessage()); if (writeable) { try { f.close(); } catch (IOException e1) { } f = null; // create it later if really needed } else { f = null; directory = null; throw e; } } } else f = null; // must create it on demand! } else { f = new RandomAccessFile(name, "r"); try { readDirectory(); } catch (IOException e) { f = null; directory = null; throw e; } } } void close() throws IOException { Jlisp.lispErr.println("Closing the PDS"); writeable = false; if (f != null) { RandomAccessFile f1 = f; f = null; f1.close(); } data = null; } // The format of a PDS is as follows: // // The first B-byte block is an index block. The contents of an index // block are shown soon, but a point is that each can contain a chaining // word that refers to the next index block. Although these blocks are // all B bytes long that may not occus at B-byte offsets in the file. // // An index block has the characters "JL" in its first two bytes as a // minimal magic number. The next 4 bytes either hold 0 or the (byte) // address of the next index block. // Following that (ie starting at byte 6) are a succession of records. Each // has a length byte (n). If n=0 we have reached the end of what is in this // block. Otherwise there follow n bytes of characters that name a member, // then 4+4+8 bytes for the location, length and date of that item. static final int DirectoryBlockSize = 2048; void readDirectory() throws IOException { byte [] buffer = new byte[DirectoryBlockSize]; long p = 0; int i; do { seek(p); for (i=0; i<buffer.length; i++) buffer[i] = (byte)read(); // The first two bytes of any index block are expected to contain the // characters JL as at least minimal identification. if ((buffer[0] & 0xff) != ('J' & 0xff) || (buffer[1] & 0xff) != ('L' & 0xff)) throw new IOException("Not a Jlisp image file (header)"); // Next each index block has an 4-byte number which is either 0 or the // position in the file of a subsequent chunk of index information. Each // index chunk is DirectoryBlockSize bytes but they do not have to be // positioned at DirectoryBlockSize-byte aligned positions within the file. // Having an 4-byte offset here is not enough for a general offset (which // would use 8 bytes) and it limits the size of a PDS to 2 Gbytes. p = 0; for (i=0; i<4; i++) p = (p<<8) + (buffer[i+2] & 0xff); // The chaining should refer to a place within the file! if (p > length()-buffer.length) throw new IOException("Not a Jlisp image file (chaining)"); int n = 6, l; for (;;) { l = buffer[n++] & 0xff; // length code if (l == 0) break; // end of what is packed into this chunk if ((n + l + 16) > buffer.length) throw new IOException("Not a Jlisp image file (name length)"); byte [] name = new byte[l]; for (i=0; i<l; i++) name[i] = buffer[n++]; int loc = 0; for (i=0; i<4; i++) loc = (loc<<8) + (buffer[n++] & 0xff); int len = 0; for (i=0; i<4; i++) len = (len<<8) + (buffer[n++] & 0xff); long date = 0; for (i=0; i<8; i++) date = (date<<8) + (buffer[n++] & 0xff); String nn = new String(name, "UTF8"); directory.put(nn, new PDSEntry(nn, loc, len, date)); } } while (p != 0); } void addToDirectory(String member) throws IOException { if (!writeable) throw new IOException("Attempt to update a read-only image file"); Jlisp.println("Image member " + member + " being created"); long p = 0; int n, i; byte [] buffer = new byte[DirectoryBlockSize]; if (f == null) { f = new RandomAccessFile(name, "rw"); // create it now f.write('J'); f.write('L'); for (i=2; i<buffer.length; i++) f.write(0); // empty directory. } long loc = f.length(); // end of the file - where new module will go if (directory.get(member) != null) { // Need to replace an existing member. Must set p to the address in the // RandomAccessFile for the block of directory involved, and n to the // offset within that block (pre-loaded into buffer) where the address // information must go. boolean found = false; do { long p1; seek(p); for (i=0; i<buffer.length; i++) buffer[i] = (byte)read(); // The first two bytes of any index block are expected to contain the // characters JL as at least minimal identification. if ((buffer[0] & 0xff) != ('J' & 0xff) || (buffer[1] & 0xff) != ('L' & 0xff)) throw new IOException("Not a Jlisp image file (header)"); p1 = 0; for (i=0; i<4; i++) p1 = (p1<<8) + (buffer[i+2] & 0xff); // The chaining should refer to a place within the file! if (p1 > f.length()-buffer.length) throw new IOException("Not a Jlisp image file (chaining)"); n = 6; int l; for (;;) { l = buffer[n++] & 0xff; // length code if (l == 0) break; // end of what is packed into this chunk if ((n + l + 16) > buffer.length) throw new IOException( "Not a Jlisp image file (name length)"); byte [] name = new byte[l]; for (i=0; i<l; i++) name[i] = buffer[n++]; String nn = new String(name, "UTF8"); if (member.equals(nn)) { found = true; break; } n += 16; // skip position, length and date info } if (found) break; p = p1; } while (p != 0); if (!found) throw new IOException("member not found in PDS"); untidy = true; } else { // I have a genuine new member to add. // First I search to find the final index block. for (int kk=0;;) { seek(p); Jlisp.println("Seeking to " + p); if (kk++ > 100) { Jlisp.println("Looping..."); throw new IOException("Sorry - mangled image file"); } for (i=0; i<buffer.length; i++) buffer[i] = (byte)read(); int p1 = 0; for (i=0; i<4; i++) p1 = (p1 << 8) + (buffer[i+2] & 0xff); if (p1==0) break; // have found last block p = p1; } // Now scan down the block to find the last entry in it. n = 6; int l; for (;;) { l = buffer[n++] & 0xff; if (l == 0) break; // end of data in this chunk n += l + 16; } Jlisp.println("Found the end"); // Next check to see if there is space in the block to add the entry that // is wanted here. byte [] nb = member.getBytes("UTF8"); int nbl = nb.length; // Two things limit the length of a module name: firstly I want a one-byte // length code to suffice (eg 256 bytes). Secondly (and less stringently) // I require that a name can fit within a single index block. NB the 256 // byte limit is on the byte-length of the data, not the number of // characters (inclusion of non-ASCII chars may call for more than 1 byte // being needed per char). if (nbl >= 0x100) throw new IOException("Name " + member + " for PSD member is too long"); if (n + nbl + 16 >= buffer.length) // Would overflow this chunk, so chain on another one. { long end = loc; loc = loc + DirectoryBlockSize; // allow for the index block Jlisp.println("end = " + end); f.seek(p+2); f.write((int)(end >> 24)); f.write((int)(end >> 16)); f.write((int)(end >> 8)); f.write((int)end); p = end; for (i=0; i<buffer.length; i++) buffer[i] = 0; buffer[0] = (byte)'J'; buffer[1] = (byte)'L'; // bytes 2,3,4,5 are now all zero n = 7; // as if the termination has just been read } // Now append the new entry within the current chunk. buffer[n-1] = (byte)nbl; for (i=0; i<nbl; i++) buffer[n++] = nb[i]; // copy name across } // new chunk will go on end of file memberData = (int)(p + n); // fill data in when member is closed Jlisp.println("p=" + p + " n = " + n + " mD = " + memberData); memberStart = (int)loc; // suppose that 32-bit offsets are enough for (int j=0; j<16; j++) // put in zeros for data (at this stage) buffer[n++] = 0; // Write the updated block back into the file. f.seek(p); for (i=0; i<buffer.length; i++) f.write(buffer[i]); f.seek(f.length()); } LispObject modulep(String s) { Object d = directory.get(s); if (d == null) return Jlisp.nil; long date = ((PDSEntry)d).date; if (date == 0) return Jlisp.nil; return new LispString(LispStream.dFormat.format(new Date(date))); } } // end of PDS.java