//
// 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