Context: New patches: [initial import adam@megacz.com**20040708095720] { adddir ./src adddir ./src/org adddir ./src/org/ibex adddir ./src/org/ibex/util addfile ./src/org/ibex/util/AccessibleCharArrayWriter.java addfile ./src/org/ibex/util/BalancedTree.java addfile ./src/org/ibex/util/CAB.java addfile ./src/org/ibex/util/Cache.java addfile ./src/org/ibex/util/CachedInputStream.java addfile ./src/org/ibex/util/Callback.java addfile ./src/org/ibex/util/CounterEnumeration.java addfile ./src/org/ibex/util/DirtyList.java addfile ./src/org/ibex/util/EjAlbertBrowserLauncher.java addfile ./src/org/ibex/util/FileNameEncoder.java addfile ./src/org/ibex/util/Grammar.java addfile ./src/org/ibex/util/Hash.java addfile ./src/org/ibex/util/InputStreamToByteArray.java addfile ./src/org/ibex/util/KnownLength.java addfile ./src/org/ibex/util/LineReader.java addfile ./src/org/ibex/util/Log.java addfile ./src/org/ibex/util/MSPack.c addfile ./src/org/ibex/util/MSPack.java addfile ./src/org/ibex/util/NanoGoat.java addfile ./src/org/ibex/util/PackBytesIntoString.java addfile ./src/org/ibex/util/Preprocessor.java addfile ./src/org/ibex/util/Queue.java addfile ./src/org/ibex/util/Scheduler.java addfile ./src/org/ibex/util/Semaphore.java addfile ./src/org/ibex/util/Simplex.java addfile ./src/org/ibex/util/Task.java addfile ./src/org/ibex/util/Vec.java addfile ./src/org/ibex/util/XML.java hunk ./src/org/ibex/util/AccessibleCharArrayWriter.java 1 +package org.ibex.util; + +import java.io.*; + +public class AccessibleCharArrayWriter extends CharArrayWriter { + public char[] getBuf() { return buf; } + public AccessibleCharArrayWriter(int i) { super(i); } +} + hunk ./src/org/ibex/util/BalancedTree.java 1 +// Copyright (C) 2003 Adam Megacz all rights reserved. +// +// You may modify, copy, and redistribute this code under the terms of +// the GNU Library Public License version 2.1, with the exception of +// the portion of clause 6a after the semicolon (aka the "obnoxious +// relink clause") + +package org.ibex.util; + +// FEATURE: private void intersection() { } +// FEATURE: private void union() { } +// FEATURE: private void subset() { } +// FEATURE: grow if we run out of slots + +/** a weight-balanced tree with fake leaves */ +public class BalancedTree { + + + // Instance Variables /////////////////////////////////////////////////////////////////// + + private int root = 0; ///< the slot of the root element + + private int cached_index = -1; + private int cached_slot = -1; + + // Public API ////////////////////////////////////////////////////////////////////////// + + /** the number of elements in the tree */ + public final int treeSize() { + synchronized(BalancedTree.class) { + return root == 0 ? 0 : size[root]; + } + } + + /** clamps index to [0..treeSize()] and inserts object o *before* the specified index */ + public final void insertNode(int index, Object o) { + synchronized(BalancedTree.class) { + if(o == null) throw new Error("can't insert nulls in the balanced tree"); + cached_slot = cached_index = -1; + if (index < 0) index = 0; + if (index > treeSize()) index = treeSize(); + int arg = allocateSlot(o); + if (root != 0) { + insert(index, arg, root, 0, false, false); + } else { + root = arg; + left[arg] = right[arg] = parent[arg] = 0; + size[arg] = 1; + } + } + } + + /** clamps index to [0..treeSize()-1] and replaces the object at that index with object o */ + public final void replaceNode(int index, Object o) { + synchronized(BalancedTree.class) { + if(o == null) throw new Error("can't insert nulls in the balanced tree"); + cached_slot = cached_index = -1; + if(root == 0) throw new Error("called replaceNode() on an empty tree"); + if (index < 0) index = 0; + if (index >= treeSize()) index = treeSize() - 1; + int arg = allocateSlot(o); + insert(index, arg, root, 0, true, false); + } + } + + /** returns the index of o; runs in O((log n)^2) time unless cache hit */ + public final int indexNode(Object o) { + synchronized(BalancedTree.class) { + if(o == null) return -1; + if (cached_slot != -1 && objects[cached_slot] == o) return cached_index; + + int slot = getSlot(o); + if(slot == -1) return -1; + + int index = 0; + while(true) { + // everything to the left is before us so add that to the index + index += sizeof(left[slot]); + // we are before anything on the right + while(left[parent[slot]] == slot) slot = parent[slot]; + // we end of the first node who isn't on the left, go to the node that has as its child + slot = parent[slot]; + // if we just processed the root we're done + if(slot == 0) break; + // count the node we're currently on towards the index + index++; + } + return index; + } + } + + /** returns the object at index; runs in O(log n) time unless cache hit */ + public final Object getNode(int index) { + synchronized(BalancedTree.class) { + if (index == cached_index) return objects[cached_slot]; + + if (cached_index != -1) { + int distance = Math.abs(index - cached_index); + // if the in-order distance between the cached node and the + // target node is less than log(n), it's probably faster to + // search directly. + if ((distance < 16) && ((2 << distance) < treeSize())) { + while(cached_index > index) { cached_slot = prev(cached_slot); cached_index--; } + while(cached_index < index) { cached_slot = next(cached_slot); cached_index++; } + return objects[cached_slot]; + } + } + /* + cached_index = index; + cached_slot = get(index, root); + return objects[cached_slot]; + */ + return objects[get(index, root)]; + } + } + + /** deletes the object at index, returning the deleted object */ + public final Object deleteNode(int index) { + synchronized(BalancedTree.class) { + cached_slot = cached_index = -1; + // FIXME: left[], right[], size[], and parent[] aren't getting cleared properly somewhere in here where a node had two children + int del = delete(index, root, 0); + left[del] = right[del] = size[del] = parent[del] = 0; + Object ret = objects[del]; + objects[del] = null; + return ret; + } + } + + public final void clear() { + synchronized(BalancedTree.class) { + if(root == 0) return; + int i = leftmost(root); + do { + int next = next(i); + objects[i] = null; + left[i] = right[i] = size[i] = parent[i] = 0; + i = next; + } while(i != 0); + root = 0; + } + } + + protected void finalize() { clear(); } + + + // Node Data ///////////////////////////////////////////////////////////////////////// + + private final static int NUM_SLOTS = 64 * 1024; + // FEATURE: GROW - private final static int MAX_SLOT_DISTANCE = 32; + + /** + * Every object inserted into *any* tree gets a "slot" in this + * array. The slot is determined by hashcode modulo the length of + * the array, with quadradic probing to resolve collisions. NOTE + * that the "slot" of a node is NOT the same as its index. + * Furthermore, if an object is inserted into multiple trees, that + * object will have multiple slots. + */ + private static Object[] objects = new Object[NUM_SLOTS]; + + /// These two arrays hold the left and right children of each + /// slot; in other words, left[x] is the *slot* of the left child + /// of the node in slot x. + /// + /// If x has no left child, then left[x] is -1 multiplied by the + /// slot of the node that precedes x; if x is the first node, then + /// left[x] is 0. The right[] array works the same way. + /// + private static int[] left = new int[NUM_SLOTS]; + private static int[] right = new int[NUM_SLOTS]; + + /// The parent of this node (0 if it is the root node) + private static int[] parent = new int[NUM_SLOTS]; + + ///< the number of descendants of this node *including the node itself* + private static int[] size = new int[NUM_SLOTS]; + + + // Slot Management ////////////////////////////////////////////////////////////////////// + + /** if alloc == false returns the slot holding object o. if alloc is true returns a new slot for obejct o */ + private int getSlot(Object o, boolean alloc) { + // we XOR with our own hashcode so that we don't get tons of + // collisions when a single Object is inserted into multiple + // trees + int dest = Math.abs(o.hashCode() ^ this.hashCode()) % objects.length; + Object search = alloc ? null : o; + int odest = dest; + boolean plus = true; + int tries = 1; + if(dest == 0) dest=1; + while (objects[dest] != search || !(alloc || root(dest) == root)) { + dest = Math.abs((odest + (plus ? 1 : -1) * tries * tries) % objects.length); + if (dest == 0) dest=1; + if (plus) tries++; + plus = !plus; + // FEATURE: GROW - if(tries > MAX_SLOT_DISTANCE) return -1; + } + return dest; + } + + /** returns the slots holding object o */ + private int getSlot(Object o) { return getSlot(o,false); } + + /** allocates a new slot holding object o*/ + private int allocateSlot(Object o) { + int slot = getSlot(o, true); + // FEATURE: GROW - if(slot == -1) throw new Error("out of slots"); + objects[slot] = o; + return slot; + } + + + + // Helpers ///////////////////////////////////////////////////////////////////////// + + private final int leftmost(int slot) { return left[slot] <= 0 ? slot : leftmost(left[slot]); } + private final int rightmost(int slot) { return right[slot] <= 0 ? slot : rightmost(right[slot]); } + private final int next(int slot) { return right[slot] <= 0 ? -1 * right[slot] : leftmost(right[slot]); } + private final int prev(int slot) { return left[slot] <= 0 ? -1 * left[slot] : rightmost(left[slot]); } + private final int sizeof(int slot) { return slot <= 0 ? 0 : size[slot]; } + private final int root(int slot) { return parent[slot] == 0 ? slot : root(parent[slot]); } + + + // Rotation and Balancing ///////////////////////////////////////////////////////////// + + // p p + // | | + // b d + // / \ / \ + // a d < == > b e + // / \ / \ + // c e a c + // FIXME might be doing too much work here + private void rotate(boolean toTheLeft, int b, int p) { + int[] left = toTheLeft ? BalancedTree.left : BalancedTree.right; + int[] right = toTheLeft ? BalancedTree.right : BalancedTree.left; + int d = right[b]; + int c = left[d]; + if (d <= 0) throw new Error("rotation error"); + left[d] = b; + right[b] = c <= 0 ? -d : c; + + parent[b] = d; + parent[d] = p; + if(c > 0) parent[c] = b; + if (p == 0) root = d; + else if (left[p] == b) left[p] = d; + else if (right[p] == b) right[p] = d; + else throw new Error("rotate called with invalid parent"); + size[b] = 1 + sizeof(left[b]) + sizeof(right[b]); + size[d] = 1 + sizeof(left[d]) + sizeof(right[d]); + } + + private void balance(int slot, int p) { + if (slot <= 0) return; + size[slot] = 1 + sizeof(left[slot]) + sizeof(right[slot]); + if (sizeof(left[slot]) - 1 > 2 * sizeof(right[slot])) rotate(false, slot, p); + else if (sizeof(left[slot]) * 2 < sizeof(right[slot]) - 1) rotate(true, slot, p); + } + + + + // Insert ///////////////////////////////////////////////////////////////////////// + + private void insert(int index, int arg, int slot, int p, boolean replace, boolean wentLeft) { + int diff = slot <= 0 ? 0 : index - sizeof(left[slot]); + if (slot > 0 && diff != 0) { + if (diff < 0) insert(index, arg, left[slot], slot, replace, true); + else insert(index - sizeof(left[slot]) - 1, arg, right[slot], slot, replace, false); + balance(slot, p); + return; + } + + if (size[arg] != 0) throw new Error("double insertion"); + + // we are replacing an existing node + if (replace) { + if (diff != 0) throw new Error("this should never happen"); // since we already clamped the index + if (p == 0) root = arg; + else if (left[p] == slot) left[p] = arg; + else if (right[p] == slot) right[p] = arg; + left[arg] = left[slot]; + right[arg] = right[slot]; + size[arg] = size[slot]; + parent[arg] = parent[slot]; + if(left[slot] > 0) parent[left[slot]] = arg; + if(right[slot] > 0) parent[right[slot]] = arg; + objects[slot] = null; + left[slot] = right[slot] = size[slot] = parent[slot] = 0; + + // we become the child of a former leaf + } else if (slot <= 0) { + int[] left = wentLeft ? BalancedTree.left : BalancedTree.right; + int[] right = wentLeft ? BalancedTree.right : BalancedTree.left; + left[arg] = slot; + left[p] = arg; + right[arg] = -1 * p; + parent[arg] = p; + balance(arg, p); + + // we take the place of a preexisting node + } else { + left[arg] = left[slot]; // steal slot's left subtree + left[slot] = -1 * arg; + right[arg] = slot; // make slot our right subtree + parent[arg] = parent[slot]; + parent[slot] = arg; + if (slot == root) { + root = arg; + balance(slot, arg); + balance(arg, 0); + } else { + if (left[p] == slot) left[p] = arg; + else if (right[p] == slot) right[p] = arg; + else throw new Error("should never happen"); + balance(slot, arg); + balance(arg, p); + } + } + } + + + // Retrieval ////////////////////////////////////////////////////////////////////// + + private int get(int index, int slot) { + int diff = index - sizeof(left[slot]); + if (diff > 0) return get(diff - 1, right[slot]); + else if (diff < 0) return get(index, left[slot]); + else return slot; + } + + + // Deletion ////////////////////////////////////////////////////////////////////// + + private int delete(int index, int slot, int p) { + int diff = index - sizeof(left[slot]); + if (diff < 0) { + int ret = delete(index, left[slot], slot); + balance(slot, p); + return ret; + + } else if (diff > 0) { + int ret = delete(diff - 1, right[slot], slot); + balance(slot, p); + return ret; + + // we found the node to delete + } else { + + // fast path: it has no children + if (left[slot] <= 0 && right[slot] <= 0) { + if (p == 0) root = 0; + else { + int[] side = left[p] == slot ? left : right; + side[p] = side[slot]; // fix parent's pointer + } + + // fast path: it has no left child, so we replace it with its right child + } else if (left[slot] <= 0) { + if (p == 0) root = right[slot]; + else (left[p] == slot ? left : right)[p] = right[slot]; // fix parent's pointer + parent[right[slot]] = p; + left[leftmost(right[slot])] = left[slot]; // fix our successor-leaf's fake right ptr + balance(right[slot], p); + + // fast path; it has no right child, so we replace it with its left child + } else if (right[slot] <= 0) { + if (p == 0) root = left[slot]; + else (left[p] == slot ? left : right)[p] = left[slot]; // fix parent's pointer + parent[left[slot]] = p; + right[rightmost(left[slot])] = right[slot]; // fix our successor-leaf's fake right ptr + balance(left[slot], p); + + // node to be deleted has two children, so we replace it with its left child's rightmost descendant + } else { + int left_childs_rightmost = delete(sizeof(left[slot]) - 1, left[slot], slot); + left[left_childs_rightmost] = left[slot]; + right[left_childs_rightmost] = right[slot]; + if(left[slot] > 0) parent[left[slot]] = left_childs_rightmost; + if(right[slot] > 0) parent[right[slot]] = left_childs_rightmost; + parent[left_childs_rightmost] = parent[slot]; + if (p == 0) root = left_childs_rightmost; + else (left[p] == slot ? left : right)[p] = left_childs_rightmost; // fix parent's pointer + balance(left_childs_rightmost, p); + } + + return slot; + } + } + + // Debugging /////////////////////////////////////////////////////////////////////////// + + public void printTree() { + if(root == 0) System.err.println("Tree is empty"); + else printTree(root,0,false); + } + private void printTree(int node,int indent,boolean l) { + for(int i=0;i all rights reserved. +// +// You may modify, copy, and redistribute this code under the terms of +// the GNU Library Public License version 2.1, with the exception of +// the portion of clause 6a after the semicolon (aka the "obnoxious +// relink clause") + +package org.ibex.util; + +import java.io.*; +import java.util.*; +import java.util.zip.*; + +/** Reads a CAB file structure */ +public class CAB { + + /** reads a CAB file, parses it, and returns an InputStream representing the named file */ + public static InputStream getFileInputStream(InputStream is, String fileName) throws IOException, EOFException { + return getFileInputStream(is, 0, fileName); + } + + public static InputStream getFileInputStream(InputStream is, int skipHeaders, String fileName) throws IOException, EOFException { + DataInputStream dis = new DataInputStream(is); + CFHEADER h = new CFHEADER(); + + while (skipHeaders > 0) { CFHEADER.seekMSCF(dis); skipHeaders--; } + + try { + h.read(dis); + } catch (CFHEADER.BogusHeaderException bhe) { + throw new EOFException(); + } + + for(int i=0; i limit) len = limit; + if (limit == 0) return -1; + int ret = super.read(b, off, len); + limit -= ret; + return ret; + } + } + + /** Encapsulates a CFHEADER entry */ + public static class CFHEADER { + byte[] reserved1 = new byte[4]; // reserved + int fileSize = 0; // size of this cabinet file in bytes + byte[] reserved2 = new byte[4]; // reserved + int offsetOfFirstCFFILEEntry; // offset of the first CFFILE entry + byte[] reserved3 = new byte[4]; // reserved + byte versionMinor = 3; // cabinet file format version, minor + byte versionMajor = 1; // cabinet file format version, major + boolean prevCAB = false; // true iff there is a cabinet before this one in a sequence + boolean nextCAB = false; // true iff there is a cabinet after this one in a sequence + boolean hasReserved = false; // true iff the cab has per-{cabinet, folder, block} reserved areas + int setID = 0; // must be the same for all cabinets in a set + int indexInCabinetSet = 0; // number of this cabinet file in a set + byte perCFFOLDERReservedSize = 0; // (optional) size of per-folder reserved area + byte perDatablockReservedSize = 0; // (optional) size of per-datablock reserved area + byte[] perCabinetReservedArea = null; // per-cabinet reserved area + String previousCabinet = null; // name of previous cabinet file in a set + String previousDisk = null; // name of previous disk in a set + String nextCabinet = null; // name of next cabinet in a set + String nextDisk = null; // name of next disk in a set + + CFFOLDER[] folders = new CFFOLDER[0]; + CFFILE[] files = new CFFILE[0]; + + int readCFFOLDERs = 0; // the number of folders read in so far + int readCFFILEs = 0; // the number of folders read in so far + + public void print(PrintStream ps) { + ps.println("CAB CFFILE CFHEADER v" + ((int)versionMajor) + "." + ((int)versionMinor)); + ps.println(" total file size = " + fileSize); + ps.println(" offset of first file = " + offsetOfFirstCFFILEEntry); + ps.println(" total folders = " + folders.length); + ps.println(" total files = " + files.length); + ps.println(" flags = 0x" + + Integer.toString((prevCAB ? 0x1 : 0x0) | + (nextCAB ? 0x2 : 0x0) | + (hasReserved ? 0x4 : 0x0), 16) + " [ " + + (prevCAB ? "prev " : "") + + (nextCAB ? "next " : "") + + (hasReserved ? "reserve_present " : "") + "]"); + ps.println(" set id = " + setID); + ps.println(" index in set = " + indexInCabinetSet); + ps.println(" header reserved area #1 =" + + " 0x" + Integer.toString(reserved1[0], 16) + + " 0x" + Integer.toString(reserved1[1], 16) + + " 0x" + Integer.toString(reserved1[2], 16) + + " 0x" + Integer.toString(reserved1[3], 16)); + ps.println(" header reserved area #2 =" + + " 0x" + Integer.toString(reserved2[0], 16) + + " 0x" + Integer.toString(reserved2[1], 16) + + " 0x" + Integer.toString(reserved2[2], 16) + + " 0x" + Integer.toString(reserved2[3], 16)); + ps.println(" header reserved area #3 =" + + " 0x" + Integer.toString(reserved3[0], 16) + + " 0x" + Integer.toString(reserved3[1], 16) + + " 0x" + Integer.toString(reserved3[2], 16) + + " 0x" + Integer.toString(reserved3[3], 16)); + if (hasReserved) { + if (perCabinetReservedArea != null) { + ps.print(" per-cabinet reserved area = "); + for(int i=0; i 0) + dis.readFully(perCabinetReservedArea); + } + + try { + if (prevCAB) { + previousCabinet = readZeroTerminatedString(dis); + previousDisk = readZeroTerminatedString(dis); + } + if (nextCAB) { + nextCabinet = readZeroTerminatedString(dis); + nextDisk = readZeroTerminatedString(dis); + } + } catch (ArrayIndexOutOfBoundsException e) { + throw new BogusHeaderException(); + } + } + + public static void seekMSCF(DataInputStream dis) throws EOFException, IOException + { + int state; + // skip up to and including the 'MSCF' signature + state = 0; + while (state != 4) { + // M + while (state == 0 && dis.readByte() != 0x4D) { } + state = 1; + // S + switch (dis.readByte()) { + case 0x53 : state = 2; break; + case 0x4D : state = 1; continue; + default : state = 0; continue; + } + // C + if (dis.readByte() == 0x43) { state = 3; } + else { state = 0; continue; } + // F + if (dis.readByte() == 0x46) { state = 4; } + else { state = 0; } + } + } + + public static class BogusHeaderException extends IOException {} + } + + /** Encapsulates a CFFOLDER entry */ + public static class CFFOLDER { + public static final int COMPRESSION_NONE = 0; + public static final int COMPRESSION_MSZIP = 1; + public static final int COMPRESSION_QUANTUM = 2; + public static final int COMPRESSION_LZX = 3; + + int firstBlockOffset = 0; // offset of first data block within this folder + int numBlocks = 0; // number of data blocks + int compressionType = 0; // compression type for this folder + byte[] reservedArea = null; // per-folder reserved area + int indexInCFHEADER = 0; // our index in CFHEADER.folders + Vector files = new Vector(); + + private CFHEADER header = null; + + public CFFOLDER(CFHEADER header) { this.header = header; } + + public String toString() { + return "[ CAB CFFOLDER, " + numBlocks + " data blocks, compression type " + + compressionName(compressionType) + + ", " + reservedArea.length + " bytes of reserved data ]"; + } + + public void read(DataInputStream dis) throws IOException, UnsupportedCompressionTypeException { + firstBlockOffset = readLittleInt(dis); + numBlocks = readLittleShort(dis); + compressionType = readLittleShort(dis) & 0x000F; + if (compressionType != COMPRESSION_MSZIP) { + throw new UnsupportedCompressionTypeException(compressionType); + } + reservedArea = new byte[header.perCFFOLDERReservedSize]; + if (reservedArea.length > 0) dis.readFully(reservedArea); + indexInCFHEADER = header.readCFFOLDERs++; + header.folders[indexInCFHEADER] = this; + } + + public static String compressionName(int type) { + switch (type) { + case COMPRESSION_NONE: + return "NONE"; + case COMPRESSION_MSZIP: + return "MSZIP"; + case COMPRESSION_QUANTUM: + return "QUANTUM"; + case COMPRESSION_LZX: + return "LZX"; + default: + return ""; + } + } + + public static class UnsupportedCompressionTypeException extends IOException { + private int compressionType; + + UnsupportedCompressionTypeException(int type) { + compressionType = type; + } + public String toString() { + return "UnsupportedCompressionTypeException: no support for compression type " + compressionName(compressionType); + } + } + } + + /** Encapsulates a CFFILE entry */ + public static class CFFILE { + int fileSize = 0; // size of this file + int uncompressedOffsetInCFFOLDER = 0; // offset of this file within the folder, not accounting for compression + int folderIndex = 0; // index of the CFFOLDER we belong to + Date date = null; // modification date + int attrs = 0; // attrs + boolean readOnly = false; // read-only flag + boolean hidden = false; // hidden flag + boolean system = false; // system flag + boolean arch = false; // archive flag + boolean runAfterExec = false; // true if file should be run during extraction + boolean UTFfileName = false; // true if filename is UTF-encoded + String fileName = null; // filename + int indexInCFHEADER = 0; // our index in CFHEADER.files + CFFOLDER folder = null; // the folder we belong to + private CFHEADER header = null; + File myFile; + + public CFFILE(CFHEADER header) { this.header = header; } + + public CFFILE(File f, String pathName) throws IOException { + fileSize = (int)f.length(); + folderIndex = 0; + date = new java.util.Date(f.lastModified()); + fileName = pathName; + myFile = f; + } + + public String toString() { + return "[ CAB CFFILE: " + fileName + ", " + fileSize + " bytes [ " + + (readOnly ? "readonly " : "") + + (system ? "system " : "") + + (hidden ? "hidden " : "") + + (arch ? "arch " : "") + + (runAfterExec ? "run_after_exec " : "") + + (UTFfileName ? "UTF_filename " : "") + + "]"; + } + + public void read(DataInputStream dis) throws IOException { + fileSize = readLittleInt(dis); + uncompressedOffsetInCFFOLDER = readLittleInt(dis); + folderIndex = readLittleShort(dis); + readLittleShort(dis); // date + readLittleShort(dis); // time + attrs = readLittleShort(dis); + readOnly = (attrs & 0x1) != 0; + hidden = (attrs & 0x2) != 0; + system = (attrs & 0x4) != 0; + arch = (attrs & 0x20) != 0; + runAfterExec = (attrs & 0x40) != 0; + UTFfileName = (attrs & 0x80) != 0; + fileName = readZeroTerminatedString(dis); + + indexInCFHEADER = header.readCFFILEs++; + header.files[indexInCFHEADER] = this; + folder = header.folders[folderIndex]; + folder.files.addElement(this); + } + } + + + + + // Compressing Input and Output Streams /////////////////////////////////////////////// + + /** an InputStream that decodes CFDATA blocks belonging to a CFFOLDER */ + private static class CFFOLDERInputStream extends InputStream { + CFFOLDER folder; + DataInputStream dis; + InputStream iis = null; + + byte[] compressed = new byte[128 * 1024]; + byte[] uncompressed = new byte[256 * 1024]; + + public CFFOLDERInputStream(CFFOLDER f, DataInputStream dis) { + this.folder = f; + this.dis = dis; + } + + InputStream readBlock() throws IOException { + int compressedBytes = readLittleShort(dis); + int unCompressedBytes = readLittleShort(dis); + byte[] reserved = new byte[/*folder.header.perDatablockReservedSize*/0]; + if (reserved.length > 0) dis.readFully(reserved); + if (dis.readByte() != 0x43) throw new CABException("malformed block header"); + if (dis.readByte() != 0x4B) throw new CABException("malformed block header"); + + dis.readFully(compressed, 0, compressedBytes - 2); + + Inflater i = new Inflater(true); + i.setInput(compressed, 0, compressedBytes - 2); + + if (unCompressedBytes > uncompressed.length) uncompressed = new byte[unCompressedBytes]; + try { i.inflate(uncompressed, 0, uncompressed.length); + } catch (DataFormatException dfe) { + dfe.printStackTrace(); + throw new CABException(dfe.toString()); + } + return new ByteArrayInputStream(uncompressed, 0, unCompressedBytes); + } + + public int available() throws IOException { return iis == null ? 0 : iis.available(); } + public void close() throws IOException { iis.close(); } + public void mark(int i) { } + public boolean markSupported() { return false; } + public void reset() { } + + public long skip(long l) throws IOException { + if (iis == null) iis = readBlock(); + int ret = 0; + while (l > ret) { + long numread = iis.skip(l - ret); + if (numread == 0 || numread == -1) iis = readBlock(); + else ret += numread; + } + return ret; + } + + public int read(byte[] b, int off, int len) throws IOException { + if (iis == null) iis = readBlock(); + int ret = 0; + while (len > ret) { + int numread = iis.read(b, off + ret, len - ret); + if (numread == 0 || numread == -1) iis = readBlock(); + else ret += numread; + } + return ret; + } + + public int read() throws IOException { + if (iis == null) iis = readBlock(); + int ret = iis.read(); + if (ret == -1) { + iis = readBlock(); + ret = iis.read(); + } + return ret; + } + } + + + + // Misc Stuff ////////////////////////////////////////////////////////////// + + public static String readZeroTerminatedString(DataInputStream dis) throws IOException { + int numBytes = 0; + byte[] b = new byte[256]; + while(true) { + byte next = dis.readByte(); + if (next == 0x0) return new String(b, 0, numBytes); + b[numBytes++] = next; + } + } + + public static int readLittleInt(DataInputStream dis) throws IOException { + int lowest = (int)(dis.readByte() & 0xff); + int low = (int)(dis.readByte() & 0xff); + int high = (int)(dis.readByte() & 0xff); + int highest = (int)(dis.readByte() & 0xff); + return (highest << 24) | (high << 16) | (low << 8) | lowest; + } + + public static int readLittleShort(DataInputStream dis) throws IOException { + int low = (int)(dis.readByte() & 0xff); + int high = (int)(dis.readByte() & 0xff); + return (high << 8) | low; + } + + public static class CABException extends IOException { + public CABException(String s) { super(s); } + } + + + /** scratch space for isToByteArray() */ + static byte[] workspace = new byte[16 * 1024]; + + /** Trivial method to completely read an InputStream */ + public static synchronized byte[] isToByteArray(InputStream is) throws IOException { + int pos = 0; + while (true) { + int numread = is.read(workspace, pos, workspace.length - pos); + if (numread == -1) break; + else if (pos + numread < workspace.length) pos += numread; + else { + pos += numread; + byte[] temp = new byte[workspace.length * 2]; + System.arraycopy(workspace, 0, temp, 0, workspace.length); + workspace = temp; + } + } + byte[] ret = new byte[pos]; + System.arraycopy(workspace, 0, ret, 0, pos); + return ret; + } + + +} + hunk ./src/org/ibex/util/Cache.java 1 +// Copyright (C) 2003 Adam Megacz all rights reserved. +// +// You may modify, copy, and redistribute this code under the terms of +// the GNU Library Public License version 2.1, with the exception of +// the portion of clause 6a after the semicolon (aka the "obnoxious +// relink clause") + +/* + +Bug report from a user: + +I looked at your Cache.java and tried to make good use of it, but I was +out of luck - it wouldn't run here. Digging deeper into the code, I came +across something that might be considered a bug. But maybe it's just a +feature :-) + + +Starting with an empty cache, Cache.put() immediately followed by +Cache.get() on same keys / same object will set Node lru back to null in +Node.remove() which is called in get(). + +Assuming this put()-get() sequence is fixed, it will fill the cache, but +lru will remain null. + +When cache is filled 100%, we have, at the end of the get(), where +size>maxSize is checked, a state that mru == lru == n (from +if(lru==null) thus deleteting the newly inserted object. Oops. + + +Hope I made this clear enough. Maybe it's not a problem in xwt due to a +different usage scheme of the cache. + + + +*/ + +package org.ibex.util; + +// FIXME needs to be a weak hash + +/** + * A Hash table with a fixed size; drops extraneous elements. Uses + * LRU strategy. + */ +public class Cache extends Hash { + + /** head of list is the mru; tail is the lru */ + Node mru = null; + Node lru = null; + + private int maxSize; + private Cache() { } + public Cache(int maxSize) { + super(maxSize * 2, 3); + this.maxSize = maxSize; + } + + /** A doubly-linked list */ + private class Node { + final Object val; + final Object k1; + final Object k2; + public Node(Object k1, Object k2, Object val) { this.k1 = k1; this.k2 = k2; this.val = val; } + Node next = null; + Node prev = null; + void remove() { + if (this == lru) lru = prev; + if (this == mru) mru = next; + if (next != null) next.prev = prev; + if (prev != null) prev.next = next; + next = prev = null; + } + void placeAfter(Node n) { + remove(); + if (n == null) return; + next = n.next; + if (n.next != null) n.next.prev = this; + n.next = this; + prev = n; + } + void placeBefore(Node n) { + remove(); + if (n == null) return; + next = n; + prev = n.prev; + n.prev = this; + if (prev != null) prev.next = this; + } + } + + public void clear() { + lru = null; + super.clear(); + } + + public void remove(Object k1, Object k2) { + Object o = super.get(k1, k2); + if (o != null) ((Node)o).remove(); + super.remove(k1, k2); + } + + public Object get(Object k1, Object k2) { + Node n = (Node)super.get(k1, k2); + if (n == null) return null; + n.remove(); + n.placeBefore(mru); + mru = n; + return n.val; + } + + public void put(Object k1, Object k2, Object v) { + Node n = new Node(k1, k2, v); + if (lru == null) { + lru = mru = n; + } else { + n.placeBefore(mru); + mru = n; + } + if (super.get(k1, k2) != null) remove(k1, k2); + super.put(k1, k2, n); + if (size > maxSize) remove(lru.k1, lru.k2); + } + +} + + hunk ./src/org/ibex/util/CachedInputStream.java 1 +// Copyright (C) 2003 Adam Megacz all rights reserved. +// +// You may modify, copy, and redistribute this code under the terms of +// the GNU Library Public License version 2.1, with the exception of +// the portion of clause 6a after the semicolon (aka the "obnoxious +// relink clause") + +package org.ibex.util; +import java.io.*; + +// FEATURE: don't use a byte[] if we have a diskCache file +/** + * Wraps around an InputStream, caching the stream in a byte[] as it + * is read and permitting multiple simultaneous readers + */ +public class CachedInputStream { + + boolean filling = false; ///< true iff some thread is blocked on us waiting for input + boolean eof = false; ///< true iff end of stream has been reached + byte[] cache = new byte[1024 * 128]; + int size = 0; + final InputStream is; + File diskCache; + + public CachedInputStream(InputStream is) { this(is, null); } + public CachedInputStream(InputStream is, File diskCache) { + this.is = is; + this.diskCache = diskCache; + } + public InputStream getInputStream() throws IOException { + if (diskCache != null && diskCache.exists()) return new FileInputStream(diskCache); + return new SubStream(); + } + + public void grow(int newLength) { + if (newLength < cache.length) return; + byte[] newCache = new byte[cache.length + 2 * (newLength - cache.length)]; + System.arraycopy(cache, 0, newCache, 0, size); + cache = newCache; + } + + synchronized void fillCache(int howMuch) throws IOException { + if (filling) { try { wait(); } catch (InterruptedException e) { }; return; } + filling = true; + grow(size + howMuch); + int ret = is.read(cache, size, howMuch); + if (ret == -1) { + eof = true; + // FIXME: probably a race here + if (diskCache != null && !diskCache.exists()) + try { + File cacheFile = new File(diskCache + ".incomplete"); + FileOutputStream cacheFileStream = new FileOutputStream(cacheFile); + cacheFileStream.write(cache, 0, size); + cacheFileStream.close(); + cacheFile.renameTo(diskCache); + } catch (IOException e) { + Log.info(this, "exception thrown while writing disk cache"); + Log.info(this, e); + } + } + else size += ret; + filling = false; + notifyAll(); + } + + private class SubStream extends InputStream implements KnownLength { + int pos = 0; + public int available() { return Math.max(0, size - pos); } + public long skip(long n) throws IOException { pos += (int)n; return n; } // FEATURE: don't skip past EOF + public int getLength() { return eof ? size : is instanceof KnownLength ? ((KnownLength)is).getLength() : 0; } + public int read() throws IOException { // FEATURE: be smarter here + byte[] b = new byte[1]; + int ret = read(b, 0, 1); + return ret == -1 ? -1 : b[0]&0xff; + } + public int read(byte[] b, int off, int len) throws IOException { + synchronized(CachedInputStream.this) { + while (pos >= size && !eof) fillCache(pos + len - size); + if (eof && pos == size) return -1; + int count = Math.min(size - pos, len); + System.arraycopy(cache, pos, b, off, count); + pos += count; + return count; + } + } + } +} hunk ./src/org/ibex/util/Callback.java 1 +// Copyright (C) 2003 Adam Megacz all rights reserved. +// +// You may modify, copy, and redistribute this code under the terms of +// the GNU Library Public License version 2.1, with the exception of +// the portion of clause 6a after the semicolon (aka the "obnoxious +// relink clause") + +package org.ibex.util; + +/** a simple interface for callbacks*/ +public interface Callback { + + public abstract Object call(Object arg); + +} hunk ./src/org/ibex/util/CounterEnumeration.java 1 +// Copyright (C) 2004 Adam Megacz all rights reserved. +// +// You may modify, copy, and redistribute this code under the terms of +// the GNU Library Public License version 2.1, with the exception of +// the portion of clause 6a after the semicolon (aka the "obnoxious +// relink clause") +package org.ibex.util; +import java.util.*; + +public class CounterEnumeration implements Enumeration { + public final int max; + private int cur = 0; + public CounterEnumeration(int i) { max = i; } + public void reset() { cur = 0; } + public boolean hasMoreElements() { return cur < max; } + public Object nextElement() { return new Integer(cur++); } +} hunk ./src/org/ibex/util/DirtyList.java 1 +// Copyright (C) 2003 Adam Megacz all rights reserved. +// +// You may modify, copy, and redistribute this code under the terms of +// the GNU Library Public License version 2.1, with the exception of +// the portion of clause 6a after the semicolon (aka the "obnoxious +// relink clause") + +package org.ibex.util; + +/** + * A general-purpose data structure for holding a list of rectangular + * regions that need to be repainted, with intelligent coalescing. + * + * DirtyList will unify two regions A and B if the smallest rectangle + * enclosing both A and B occupies no more than epsilon + Area_A + + * Area_B. Failing this, if two corners of A fall within B, A will be + * shrunk to exclude the union of A and B. + */ +public class DirtyList { + + /** The dirty regions (each one is an int[4]). */ + private int[][] dirties = new int[10][]; + + /** The number of dirty regions */ + private int numdirties = 0; + + /** See class comment */ + private static final int epsilon = 50 * 50; + + public int num() { return numdirties; } + + /** grows the array */ + private void grow() { + int[][] newdirties = new int[dirties.length * 2][]; + System.arraycopy(dirties, 0, newdirties, 0, numdirties); + dirties = newdirties; + } + + /** Add a new rectangle to the dirty list; returns false if the + * region fell completely within an existing rectangle or set of + * rectangles (ie did not expand the dirty area) + */ + public synchronized boolean dirty(int x, int y, int w, int h) { + if (numdirties == dirties.length) grow(); + + // we attempt the "lossless" combinations first + for(int i=0; i= cur[0] && y >= cur[1] && x + w <= cur[0] + cur[2] && y + h <= cur[1] + cur[3]) { + return false; + + // existing region falls completely within new region + } else if (x <= cur[0] && y <= cur[1] && x + w >= cur[0] + cur[2] && y + h >= cur[1] + cur[3]) { + dirties[i][2] = 0; + dirties[i][3] = 0; + + // left end of new region falls within existing region + } else if (x >= cur[0] && x < cur[0] + cur[2] && y >= cur[1] && y + h <= cur[1] + cur[3]) { + w = x + w - (cur[0] + cur[2]); + x = cur[0] + cur[2]; + i = -1; continue; + + // right end of new region falls within existing region + } else if (x + w > cur[0] && x + w <= cur[0] + cur[2] && y >= cur[1] && y + h <= cur[1] + cur[3]) { + w = cur[0] - x; + i = -1; continue; + + // top end of new region falls within existing region + } else if (x >= cur[0] && x + w <= cur[0] + cur[2] && y >= cur[1] && y < cur[1] + cur[3]) { + h = y + h - (cur[1] + cur[3]); + y = cur[1] + cur[3]; + i = -1; continue; + + // bottom end of new region falls within existing region + } else if (x >= cur[0] && x + w <= cur[0] + cur[2] && y + h > cur[1] && y + h <= cur[1] + cur[3]) { + h = cur[1] - y; + i = -1; continue; + + // left end of existing region falls within new region + } else if (dirties[i][0] >= x && dirties[i][0] < x + w && dirties[i][1] >= y && dirties[i][1] + dirties[i][3] <= y + h) { + dirties[i][2] = dirties[i][2] - (x + w - dirties[i][0]); + dirties[i][0] = x + w; + i = -1; continue; + + // right end of existing region falls within new region + } else if (dirties[i][0] + dirties[i][2] > x && dirties[i][0] + dirties[i][2] <= x + w && + dirties[i][1] >= y && dirties[i][1] + dirties[i][3] <= y + h) { + dirties[i][2] = x - dirties[i][0]; + i = -1; continue; + + // top end of existing region falls within new region + } else if (dirties[i][0] >= x && dirties[i][0] + dirties[i][2] <= x + w && dirties[i][1] >= y && dirties[i][1] < y + h) { + dirties[i][3] = dirties[i][3] - (y + h - dirties[i][1]); + dirties[i][1] = y + h; + i = -1; continue; + + // bottom end of existing region falls within new region + } else if (dirties[i][0] >= x && dirties[i][0] + dirties[i][2] <= x + w && + dirties[i][1] + dirties[i][3] > y && dirties[i][1] + dirties[i][3] <= y + h) { + dirties[i][3] = y - dirties[i][1]; + i = -1; continue; + } + + } + + // then we attempt the "lossy" combinations + for(int i=0; i 0 && h > 0 && cur[2] > 0 && cur[3] > 0 && + ((max(x + w, cur[0] + cur[2]) - min(x, cur[0])) * + (max(y + h, cur[1] + cur[3]) - min(y, cur[1])) < + w * h + cur[2] * cur[3] + epsilon)) { + int a = min(cur[0], x); + int b = min(cur[1], y); + int c = max(x + w, cur[0] + cur[2]) - min(cur[0], x); + int d = max(y + h, cur[1] + cur[3]) - min(cur[1], y); + dirties[i][2] = 0; + dirties[i][3] = 0; + return dirty(a, b, c, d); + } + } + + dirties[numdirties++] = new int[] { x, y, w, h }; + return true; + } + + /** Returns true if there are no regions that need repainting */ + public boolean empty() { return (numdirties == 0); } + + /** + * Atomically returns the list of dirty rectangles as an array of + * four-int arrays and clears the internal dirty-rectangle + * list. Note that some of the regions returned may be null, or + * may have zero height or zero width, and do not need to be + * repainted. + */ + public synchronized int[][] flush() { + if (numdirties == 0) return null; + int[][] ret = dirties; + for(int i=numdirties; ib) return a; + else return b; + } + + /** included here so that it can be inlined */ + private static final int min(int a, int b, int c) { + if (a<=b && a<=c) return a; + else if (b<=c && b<=a) return b; + else return c; + } + + /** included here so that it can be inlined */ + private static final int max(int a, int b, int c) { + if (a>=b && a>=c) return a; + else if (b>=c && b>=a) return b; + else return c; + } + + /** included here so that it can be inlined */ + private static final int bound(int a, int b, int c) { + if (a > b) return a; + if (c < b) return c; + return b; + } + +} hunk ./src/org/ibex/util/EjAlbertBrowserLauncher.java 1 +package org.ibex.util; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * BrowserLauncher is a class that provides one static method, openURL, which opens the default + * web browser for the current user of the system to the given URL. It may support other + * protocols depending on the system -- mailto, ftp, etc. -- but that has not been rigorously + * tested and is not guaranteed to work. + *

+ * Yes, this is platform-specific code, and yes, it may rely on classes on certain platforms + * that are not part of the standard JDK. What we're trying to do, though, is to take something + * that's frequently desirable but inherently platform-specific -- opening a default browser -- + * and allow programmers (you, for example) to do so without worrying about dropping into native + * code or doing anything else similarly evil. + *

+ * Anyway, this code is completely in Java and will run on all JDK 1.1-compliant systems without + * modification or a need for additional libraries. All classes that are required on certain + * platforms to allow this to run are dynamically loaded at runtime via reflection and, if not + * found, will not cause this to do anything other than returning an error when opening the + * browser. + *

+ * There are certain system requirements for this class, as it's running through Runtime.exec(), + * which is Java's way of making a native system call. Currently, this requires that a Macintosh + * have a Finder which supports the GURL event, which is true for Mac OS 8.0 and 8.1 systems that + * have the Internet Scripting AppleScript dictionary installed in the Scripting Additions folder + * in the Extensions folder (which is installed by default as far as I know under Mac OS 8.0 and + * 8.1), and for all Mac OS 8.5 and later systems. On Windows, it only runs under Win32 systems + * (Windows 95, 98, and NT 4.0, as well as later versions of all). On other systems, this drops + * back from the inherently platform-sensitive concept of a default browser and simply attempts + * to launch Netscape via a shell command. + *

+ * This code is Copyright 1999-2001 by Eric Albert (ejalbert@cs.stanford.edu) and may be + * redistributed or modified in any form without restrictions as long as the portion of this + * comment from this paragraph through the end of the comment is not removed. The author + * requests that he be notified of any application, applet, or other binary that makes use of + * this code, but that's more out of curiosity than anything and is not required. This software + * includes no warranty. The author is not repsonsible for any loss of data or functionality + * or any adverse or unexpected effects of using this software. + *

+ * Credits: + *
Steven Spencer, JavaWorld magazine (Java Tip 66) + *
Thanks also to Ron B. Yeh, Eric Shapiro, Ben Engber, Paul Teitlebaum, Andrea Cantatore, + * Larry Barowski, Trevor Bedzek, Frank Miedrich, and Ron Rabakukk + * + * @author Eric Albert (ejalbert@cs.stanford.edu) + * @version 1.4b1 (Released June 20, 2001) + */ +public class EjAlbertBrowserLauncher { + + /** + * The Java virtual machine that we are running on. Actually, in most cases we only care + * about the operating system, but some operating systems require us to switch on the VM. */ + private static int jvm; + + /** The browser for the system */ + private static Object browser; + + /** + * Caches whether any classes, methods, and fields that are not part of the JDK and need to + * be dynamically loaded at runtime loaded successfully. + *

+ * Note that if this is false, openURL() will always return an + * IOException. + */ + private static boolean loadedWithoutErrors; + + /** The com.apple.mrj.MRJFileUtils class */ + private static Class mrjFileUtilsClass; + + /** The com.apple.mrj.MRJOSType class */ + private static Class mrjOSTypeClass; + + /** The com.apple.MacOS.AEDesc class */ + private static Class aeDescClass; + + /** The (int) method of com.apple.MacOS.AETarget */ + private static Constructor aeTargetConstructor; + + /** The (int, int, int) method of com.apple.MacOS.AppleEvent */ + private static Constructor appleEventConstructor; + + /** The (String) method of com.apple.MacOS.AEDesc */ + private static Constructor aeDescConstructor; + + /** The findFolder method of com.apple.mrj.MRJFileUtils */ + private static Method findFolder; + + /** The getFileCreator method of com.apple.mrj.MRJFileUtils */ + private static Method getFileCreator; + + /** The getFileType method of com.apple.mrj.MRJFileUtils */ + private static Method getFileType; + + /** The openURL method of com.apple.mrj.MRJFileUtils */ + private static Method openURLm; + + /** The makeOSType method of com.apple.MacOS.OSUtils */ + private static Method makeOSType; + + /** The putParameter method of com.apple.MacOS.AppleEvent */ + private static Method putParameter; + + /** The sendNoReply method of com.apple.MacOS.AppleEvent */ + private static Method sendNoReply; + + /** Actually an MRJOSType pointing to the System Folder on a Macintosh */ + private static Object kSystemFolderType; + + /** The keyDirectObject AppleEvent parameter type */ + private static Integer keyDirectObject; + + /** The kAutoGenerateReturnID AppleEvent code */ + private static Integer kAutoGenerateReturnID; + + /** The kAnyTransactionID AppleEvent code */ + private static Integer kAnyTransactionID; + + /** The linkage object required for JDirect 3 on Mac OS X. */ + private static Object linkage; + + /** The framework to reference on Mac OS X */ + private static final String JDirect_MacOSX = "/System/Library/Frameworks/Carbon.framework/Frameworks/HIToolbox.framework/HIToolbox"; + + /** JVM constant for MRJ 2.0 */ + private static final int MRJ_2_0 = 0; + + /** JVM constant for MRJ 2.1 or later */ + private static final int MRJ_2_1 = 1; + + /** JVM constant for Java on Mac OS X 10.0 (MRJ 3.0) */ + private static final int MRJ_3_0 = 3; + + /** JVM constant for MRJ 3.1 */ + private static final int MRJ_3_1 = 4; + + /** JVM constant for any Windows NT JVM */ + private static final int WINDOWS_NT = 5; + + /** JVM constant for any Windows 9x JVM */ + private static final int WINDOWS_9x = 6; + + /** JVM constant for any other platform */ + private static final int OTHER = -1; + + /** + * The file type of the Finder on a Macintosh. Hardcoding "Finder" would keep non-U.S. English + * systems from working properly. + */ + private static final String FINDER_TYPE = "FNDR"; + + /** + * The creator code of the Finder on a Macintosh, which is needed to send AppleEvents to the + * application. + */ + private static final String FINDER_CREATOR = "MACS"; + + /** The name for the AppleEvent type corresponding to a GetURL event. */ + private static final String GURL_EVENT = "GURL"; + + /** + * The first parameter that needs to be passed into Runtime.exec() to open the default web + * browser on Windows. + */ + private static final String FIRST_WINDOWS_PARAMETER = "/c"; + + /** The second parameter for Runtime.exec() on Windows. */ + private static final String SECOND_WINDOWS_PARAMETER = "start"; + + /** + * The third parameter for Runtime.exec() on Windows. This is a "title" + * parameter that the command line expects. Setting this parameter allows + * URLs containing spaces to work. + */ + private static final String THIRD_WINDOWS_PARAMETER = "\"\""; + + /** + * The shell parameters for Netscape that opens a given URL in an already-open copy of Netscape + * on many command-line systems. + */ + private static final String NETSCAPE_REMOTE_PARAMETER = "-remote"; + private static final String NETSCAPE_OPEN_PARAMETER_START = "'openURL("; + private static final String NETSCAPE_OPEN_PARAMETER_END = ")'"; + + /** + * The message from any exception thrown throughout the initialization process. + */ + private static String errorMessage; + + /** + * An initialization block that determines the operating system and loads the necessary + * runtime data. + */ + static { + loadedWithoutErrors = true; + String osName = System.getProperty("os.name"); + if (osName.startsWith("Mac OS")) { + String mrjVersion = System.getProperty("mrj.version"); + String majorMRJVersion = mrjVersion.substring(0, 3); + try { + double version = Double.valueOf(majorMRJVersion).doubleValue(); + if (version == 2) { + jvm = MRJ_2_0; + } else if (version >= 2.1 && version < 3) { + // Assume that all 2.x versions of MRJ work the same. MRJ 2.1 actually + // works via Runtime.exec() and 2.2 supports that but has an openURL() method + // as well that we currently ignore. + jvm = MRJ_2_1; + } else if (version == 3.0) { + jvm = MRJ_3_0; + } else if (version >= 3.1) { + // Assume that all 3.1 and later versions of MRJ work the same. + jvm = MRJ_3_1; + } else { + loadedWithoutErrors = false; + errorMessage = "Unsupported MRJ version: " + version; + } + } catch (NumberFormatException nfe) { + loadedWithoutErrors = false; + errorMessage = "Invalid MRJ version: " + mrjVersion; + } + } else if (osName.startsWith("Windows")) { + if (osName.indexOf("9") != -1) { + jvm = WINDOWS_9x; + } else { + jvm = WINDOWS_NT; + } + } else { + jvm = OTHER; + } + + if (loadedWithoutErrors) { // if we haven't hit any errors yet + loadedWithoutErrors = loadClasses(); + } + } + + /** + * This class should be never be instantiated; this just ensures so. + */ + private EjAlbertBrowserLauncher() { } + + /** + * Called by a static initializer to load any classes, fields, and methods required at runtime + * to locate the user's web browser. + * @return true if all intialization succeeded + * false if any portion of the initialization failed + */ + private static boolean loadClasses() { + switch (jvm) { + case MRJ_2_0: + try { + Class aeTargetClass = Class.forName("com.apple.MacOS.AETarget"); + Class osUtilsClass = Class.forName("com.apple.MacOS.OSUtils"); + Class appleEventClass = Class.forName("com.apple.MacOS.AppleEvent"); + Class aeClass = Class.forName("com.apple.MacOS.ae"); + aeDescClass = Class.forName("com.apple.MacOS.AEDesc"); + + aeTargetConstructor = aeTargetClass.getDeclaredConstructor(new Class [] { int.class }); + appleEventConstructor = appleEventClass.getDeclaredConstructor(new Class[] { int.class, int.class, aeTargetClass, int.class, int.class }); + aeDescConstructor = aeDescClass.getDeclaredConstructor(new Class[] { String.class }); + + makeOSType = osUtilsClass.getDeclaredMethod("makeOSType", new Class [] { String.class }); + putParameter = appleEventClass.getDeclaredMethod("putParameter", new Class[] { int.class, aeDescClass }); + sendNoReply = appleEventClass.getDeclaredMethod("sendNoReply", new Class[] { }); + + Field keyDirectObjectField = aeClass.getDeclaredField("keyDirectObject"); + keyDirectObject = (Integer) keyDirectObjectField.get(null); + Field autoGenerateReturnIDField = appleEventClass.getDeclaredField("kAutoGenerateReturnID"); + kAutoGenerateReturnID = (Integer) autoGenerateReturnIDField.get(null); + Field anyTransactionIDField = appleEventClass.getDeclaredField("kAnyTransactionID"); + kAnyTransactionID = (Integer) anyTransactionIDField.get(null); + } catch (ClassNotFoundException cnfe) { + errorMessage = cnfe.getMessage(); + return false; + } catch (NoSuchMethodException nsme) { + errorMessage = nsme.getMessage(); + return false; + } catch (NoSuchFieldException nsfe) { + errorMessage = nsfe.getMessage(); + return false; + } catch (IllegalAccessException iae) { + errorMessage = iae.getMessage(); + return false; + } + break; + case MRJ_2_1: + try { + mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils"); + mrjOSTypeClass = Class.forName("com.apple.mrj.MRJOSType"); + Field systemFolderField = mrjFileUtilsClass.getDeclaredField("kSystemFolderType"); + kSystemFolderType = systemFolderField.get(null); + findFolder = mrjFileUtilsClass.getDeclaredMethod("findFolder", new Class[] { mrjOSTypeClass }); + getFileCreator = mrjFileUtilsClass.getDeclaredMethod("getFileCreator", new Class[] { File.class }); + getFileType = mrjFileUtilsClass.getDeclaredMethod("getFileType", new Class[] { File.class }); + } catch (ClassNotFoundException cnfe) { + errorMessage = cnfe.getMessage(); + return false; + } catch (NoSuchFieldException nsfe) { + errorMessage = nsfe.getMessage(); + return false; + } catch (NoSuchMethodException nsme) { + errorMessage = nsme.getMessage(); + return false; + } catch (SecurityException se) { + errorMessage = se.getMessage(); + return false; + } catch (IllegalAccessException iae) { + errorMessage = iae.getMessage(); + return false; + } + break; + case MRJ_3_0: + try { + Class linker = Class.forName("com.apple.mrj.jdirect.Linker"); + Constructor constructor = linker.getConstructor(new Class[]{ Class.class }); + linkage = constructor.newInstance(new Object[] { EjAlbertBrowserLauncher.class }); + } catch (ClassNotFoundException cnfe) { + errorMessage = cnfe.getMessage(); + return false; + } catch (NoSuchMethodException nsme) { + errorMessage = nsme.getMessage(); + return false; + } catch (InvocationTargetException ite) { + errorMessage = ite.getMessage(); + return false; + } catch (InstantiationException ie) { + errorMessage = ie.getMessage(); + return false; + } catch (IllegalAccessException iae) { + errorMessage = iae.getMessage(); + return false; + } + break; + case MRJ_3_1: + try { + mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils"); + openURLm = mrjFileUtilsClass.getDeclaredMethod("openURL", new Class[] { String.class }); + } catch (ClassNotFoundException cnfe) { + errorMessage = cnfe.getMessage(); + return false; + } catch (NoSuchMethodException nsme) { + errorMessage = nsme.getMessage(); + return false; + } + break; + default: + break; + } + return true; + } + + /** + * Attempts to locate the default web browser on the local system. Caches results so it + * only locates the browser once for each use of this class per JVM instance. + * @return The browser for the system. Note that this may not be what you would consider + * to be a standard web browser; instead, it's the application that gets called to + * open the default web browser. In some cases, this will be a non-String object + * that provides the means of calling the default browser. + */ + private static Object locateBrowser() { + if (browser != null) { + return browser; + } + switch (jvm) { + case MRJ_2_0: + try { + Integer finderCreatorCode = (Integer) makeOSType.invoke(null, new Object[] { FINDER_CREATOR }); + Object aeTarget = aeTargetConstructor.newInstance(new Object[] { finderCreatorCode }); + Integer gurlType = (Integer) makeOSType.invoke(null, new Object[] { GURL_EVENT }); + Object appleEvent = appleEventConstructor.newInstance(new Object[] { gurlType, gurlType, aeTarget, kAutoGenerateReturnID, kAnyTransactionID }); + // Don't set browser = appleEvent because then the next time we call + // locateBrowser(), we'll get the same AppleEvent, to which we'll already have + // added the relevant parameter. Instead, regenerate the AppleEvent every time. + // There's probably a way to do this better; if any has any ideas, please let + // me know. + return appleEvent; + } catch (IllegalAccessException iae) { + browser = null; + errorMessage = iae.getMessage(); + return browser; + } catch (InstantiationException ie) { + browser = null; + errorMessage = ie.getMessage(); + return browser; + } catch (InvocationTargetException ite) { + browser = null; + errorMessage = ite.getMessage(); + return browser; + } + case MRJ_2_1: + File systemFolder; + try { + systemFolder = (File) findFolder.invoke(null, new Object[] { kSystemFolderType }); + } catch (IllegalArgumentException iare) { + browser = null; + errorMessage = iare.getMessage(); + return browser; + } catch (IllegalAccessException iae) { + browser = null; + errorMessage = iae.getMessage(); + return browser; + } catch (InvocationTargetException ite) { + browser = null; + errorMessage = ite.getTargetException().getClass() + ": " + ite.getTargetException().getMessage(); + return browser; + } + String[] systemFolderFiles = systemFolder.list(); + // Avoid a FilenameFilter because that can't be stopped mid-list + for(int i = 0; i < systemFolderFiles.length; i++) { + try { + File file = new File(systemFolder, systemFolderFiles[i]); + if (!file.isFile()) { + continue; + } + // We're looking for a file with a creator code of 'MACS' and + // a type of 'FNDR'. Only requiring the type results in non-Finder + // applications being picked up on certain Mac OS 9 systems, + // especially German ones, and sending a GURL event to those + // applications results in a logout under Multiple Users. + Object fileType = getFileType.invoke(null, new Object[] { file }); + if (FINDER_TYPE.equals(fileType.toString())) { + Object fileCreator = getFileCreator.invoke(null, new Object[] { file }); + if (FINDER_CREATOR.equals(fileCreator.toString())) { + browser = file.toString(); // Actually the Finder, but that's OK + return browser; + } + } + } catch (IllegalArgumentException iare) { + browser = browser; + errorMessage = iare.getMessage(); + return null; + } catch (IllegalAccessException iae) { + browser = null; + errorMessage = iae.getMessage(); + return browser; + } catch (InvocationTargetException ite) { + browser = null; + errorMessage = ite.getTargetException().getClass() + ": " + ite.getTargetException().getMessage(); + return browser; + } + } + browser = null; + break; + case MRJ_3_0: + case MRJ_3_1: + browser = ""; // Return something non-null + break; + case WINDOWS_NT: + browser = "cmd.exe"; + break; + case WINDOWS_9x: + browser = "command.com"; + break; + case OTHER: + default: + browser = "netscape"; + break; + } + return browser; + } + + /** + * Attempts to open the default web browser to the given URL. + * @param url The URL to open + * @throws IOException If the web browser could not be located or does not run + */ + public static void openURL(String url) throws IOException { + if (!loadedWithoutErrors) { + throw new IOException("Exception in finding browser: " + errorMessage); + } + Object browser = locateBrowser(); + if (browser == null) { + throw new IOException("Unable to locate browser: " + errorMessage); + } + + switch (jvm) { + case MRJ_2_0: + Object aeDesc = null; + try { + aeDesc = aeDescConstructor.newInstance(new Object[] { url }); + putParameter.invoke(browser, new Object[] { keyDirectObject, aeDesc }); + sendNoReply.invoke(browser, new Object[] { }); + } catch (InvocationTargetException ite) { + throw new IOException("InvocationTargetException while creating AEDesc: " + ite.getMessage()); + } catch (IllegalAccessException iae) { + throw new IOException("IllegalAccessException while building AppleEvent: " + iae.getMessage()); + } catch (InstantiationException ie) { + throw new IOException("InstantiationException while creating AEDesc: " + ie.getMessage()); + } finally { + aeDesc = null; // Encourage it to get disposed if it was created + browser = null; // Ditto + } + break; + case MRJ_2_1: + Runtime.getRuntime().exec(new String[] { (String) browser, url } ); + break; + case MRJ_3_0: + int[] instance = new int[1]; + int result = ICStart(instance, 0); + if (result == 0) { + int[] selectionStart = new int[] { 0 }; + byte[] urlBytes = url.getBytes(); + int[] selectionEnd = new int[] { urlBytes.length }; + result = ICLaunchURL(instance[0], new byte[] { 0 }, urlBytes, + urlBytes.length, selectionStart, + selectionEnd); + if (result == 0) { + // Ignore the return value; the URL was launched successfully + // regardless of what happens here. + ICStop(instance); + } else { + throw new IOException("Unable to launch URL: " + result); + } + } else { + throw new IOException("Unable to create an Internet Config instance: " + result); + } + break; + case MRJ_3_1: + try { + openURLm.invoke(null, new Object[] { url }); + } catch (InvocationTargetException ite) { + throw new IOException("InvocationTargetException while calling openURL: " + ite.getMessage()); + } catch (IllegalAccessException iae) { + throw new IOException("IllegalAccessException while calling openURL: " + iae.getMessage()); + } + break; + case WINDOWS_NT: + case WINDOWS_9x: + // Add quotes around the URL to allow ampersands and other special + // characters to work. + Process process = Runtime.getRuntime().exec(new String[] { (String) browser, + FIRST_WINDOWS_PARAMETER, + SECOND_WINDOWS_PARAMETER, + THIRD_WINDOWS_PARAMETER, + '"' + url + '"' }); + // This avoids a memory leak on some versions of Java on Windows. + // That's hinted at in . + try { + process.waitFor(); + process.exitValue(); + } catch (InterruptedException ie) { + throw new IOException("InterruptedException while launching browser: " + ie.getMessage()); + } + break; + case OTHER: + // Assume that we're on Unix and that Netscape is installed + + // First, attempt to open the URL in a currently running session of Netscape + process = Runtime.getRuntime().exec(new String[] { (String) browser, + NETSCAPE_REMOTE_PARAMETER, + NETSCAPE_OPEN_PARAMETER_START + + url + + NETSCAPE_OPEN_PARAMETER_END }); + try { + int exitCode = process.waitFor(); + if (exitCode != 0) { // if Netscape was not open + Runtime.getRuntime().exec(new String[] { (String) browser, url }); + } + } catch (InterruptedException ie) { + throw new IOException("InterruptedException while launching browser: " + ie.getMessage()); + } + break; + default: + // This should never occur, but if it does, we'll try the simplest thing possible + Runtime.getRuntime().exec(new String[] { (String) browser, url }); + break; + } + } + + /** + * Methods required for Mac OS X. The presence of native methods does not cause + * any problems on other platforms. + */ + /* + private native static int ICStart(int[] instance, int signature); + private native static int ICStop(int[] instance); + private native static int ICLaunchURL(int instance, byte[] hint, byte[] data, int len, + int[] selectionStart, int[] selectionEnd); + */ + private static int ICStart(int[] instance, int signature) { return 0; } + private static int ICStop(int[] instance) { return 0; } + private static int ICLaunchURL(int instance, byte[] hint, byte[] data, int len, + int[] selectionStart, int[] selectionEnd) { return 0; } +} hunk ./src/org/ibex/util/FileNameEncoder.java 1 +// Copyright (C) 2003 Adam Megacz all rights reserved. +// +// You may modify, copy, and redistribute this code under the terms of +// the GNU Library Public License version 2.1, with the exception of +// the portion of clause 6a after the semicolon (aka the "obnoxious +// relink clause") + +package org.ibex.util; +import java.util.*; +import java.io.*; + +/** provides commands to urlencode and urldecode strings, making sure + * to escape sequences which have special meanings in filenames */ +public class FileNameEncoder { + + private static final char[] hex = + new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + public static String encode(String s) { + StringBuffer sb = new StringBuffer(); + try { + byte[] b = s.getBytes("UTF-8"); + for(int i=0; i 126 || c == '%' || (i == 0 && c == '.')) + sb.append("%" + hex[(b[i] & 0xf0) >> 8] + hex[b[i] & 0xf]); + else sb.append(c); + } + return sb.toString(); + } catch (UnsupportedEncodingException uee) { + throw new Error("this should never happen; Java spec mandates UTF-8 support"); + } + } + + public static String decode(String s) { + StringBuffer sb = new StringBuffer(); + byte[] b = new byte[s.length() * 2]; + int bytes = 0; + for(int i=0; i= min && s.charAt(start) <= max)) return -1; + return start + 1; + } + } + + public static class Reference extends Grammar { + String key; + public Reference(String key) { this.key = key; } + public int match(String s, int start, Hash v, JSScope scope) throws JSExn { + return ((Grammar)scope.get(key)).matchAndWrite(s, start, v, scope, key); + } + } +} hunk ./src/org/ibex/util/Hash.java 1 +// Copyright (C) 2003 Adam Megacz all rights reserved. +// +// You may modify, copy, and redistribute this code under the terms of +// the GNU Library Public License version 2.1, with the exception of +// the portion of clause 6a after the semicolon (aka the "obnoxious +// relink clause") + +package org.ibex.util; + +import java.util.*; + +/** Implementation of an unsynchronized hash table, with one or two + * keys, using Radke's quadradic residue linear probing instead of + * buckets to minimize object count (less allocations, faster GC). + * See C. Radke, Communications of the ACM, 1970, 103-105 + * + * Not threadsafe. + */ +public class Hash { + /** this object is inserted as key in a slot when the + * corresponding value is removed -- this ensures that the + * probing sequence for any given key remains the same even if + * other keys are removed. + */ + private static Object placeholder = new Object(); + + /** the number of entries with at least one non-null key */ + private int usedslots = 0; + + /** the number of entries with non-null values */ + protected int size = 0; + + /** when num_slots < loadFactor * size, rehash into a bigger table */ + private final int loadFactor; + + /** primary keys */ + private Object[] keys1 = null; + + /** secondary keys; null if no secondary key has ever been added */ + private Object[] keys2 = null; + + /** the values for the table */ + private Object[] vals = null; + + /** the number of entries with a non-null value */ + public int size() { return size; } + + /** empties the table */ + public void clear() { + size = 0; + usedslots = 0; + for(int i=0; i vals.length) rehash(); + int hash = (k1 == null ? 0 : k1.hashCode()) ^ (k2 == null ? 0 : k2.hashCode()); + int dest = Math.abs(hash) % vals.length; + int odest = dest; + boolean plus = true; + int tries = 1; + while (true) { + Object hk1 = keys1[dest]; + Object hk2 = keys2 == null ? null : keys2[dest]; + if (hk1 == null && hk2 == null) { // empty slot + if (v == null) return; + size++; + usedslots++; + break; + } + + if ((k1 == hk1 || (k1 != null && hk1 != null && k1.equals(hk1))) && // replacing former entry + (k2 == hk2 || (k2 != null && hk2 != null && k2.equals(hk2)))) { + + // we don't actually remove things from the table; rather, we insert a placeholder + if (v == null) { + k1 = placeholder; + k2 = null; + size--; + } + break; + } + + dest = Math.abs((odest + (plus ? 1 : -1 ) * tries * tries) % vals.length); + if (plus) tries++; + plus = !plus; + } + + keys1[dest] = k1; + if (k2 != null && keys2 == null) keys2 = new Object[keys1.length]; + if (keys2 != null) keys2[dest] = k2; + vals[dest] = v; + } + + private class HashEnum implements java.util.Enumeration { + private int iterator = 0; + private int found = 0; + + public boolean hasMoreElements() { + return found < usedslots; + } + + public Object nextElement() { + if (!hasMoreElements()) throw new java.util.NoSuchElementException(); + + Object o = null; + while (o == null) o = keys1[iterator++]; + if (o == null) throw new IllegalStateException("Didn't find an element, when I should have."); + found++; + + return o; + } + } +} + + hunk ./src/org/ibex/util/InputStreamToByteArray.java 1 +// Copyright (C) 2003 Adam Megacz all rights reserved. +// +// You may modify, copy, and redistribute this code under the terms of +// the GNU Library Public License version 2.1, with the exception of +// the portion of clause 6a after the semicolon (aka the "obnoxious +// relink clause") + +package org.ibex.util; +import java.io.*; + +public class InputStreamToByteArray { + + /** scratch space for isToByteArray() */ + private static byte[] workspace = new byte[16 * 1024]; + + /** Trivial method to completely read an InputStream */ + public static synchronized byte[] convert(InputStream is) throws IOException { + int pos = 0; + while (true) { + int numread = is.read(workspace, pos, workspace.length - pos); + if (numread == -1) break; + else if (pos + numread < workspace.length) pos += numread; + else { + pos += numread; + byte[] temp = new byte[workspace.length * 2]; + System.arraycopy(workspace, 0, temp, 0, workspace.length); + workspace = temp; + } + } + byte[] ret = new byte[pos]; + System.arraycopy(workspace, 0, ret, 0, pos); + return ret; + } + +} hunk ./src/org/ibex/util/KnownLength.java 1 +// Copyright (C) 2003 Adam Megacz all rights reserved. +// +// You may modify, copy, and redistribute this code under the terms of +// the GNU Library Public License version 2.1, with the exception of +// the portion of clause 6a after the semicolon (aka the "obnoxious +// relink clause") + +package org.ibex.util; +import java.io.*; + +/** a generic interface for things that "know" their length */ +public interface KnownLength { + + public abstract int getLength(); + + public static class KnownLengthInputStream extends FilterInputStream implements KnownLength { + int length; + public int getLength() { return length; } + public KnownLengthInputStream(java.io.InputStream parent, int length) { + super(parent); + this.length = length; + } + } + +} hunk ./src/org/ibex/util/LineReader.java 1 +package org.ibex.util; +import java.io.*; + +public class LineReader { + + private int MAXBUF = 1024 * 16; + char[] buf = new char[MAXBUF]; + int buflen = 0; + Reader r; + Vec pushback = new Vec(); + + public LineReader(Reader r) { this.r = r; } + + public void pushback(String s) { pushback.push(s); } + + public String readLine() throws IOException { + while(true) { + if (pushback.size() > 0) return (String)pushback.pop(); + for(int i=0; i all rights reserved. +// +// You may modify, copy, and redistribute this code under the terms of +// the GNU Library Public License version 2.1, with the exception of +// the portion of clause 6a after the semicolon (aka the "obnoxious +// relink clause") + +package org.ibex.util; +import org.ibex.js.*; +import java.io.*; +import java.util.*; +import java.net.*; + +/** easy to use logger */ +public class Log { + + public static boolean on = System.getProperty("ibex.log.on", "true").equals("true"); + public static boolean color = System.getProperty("ibex.log.color", "true").equals("true"); + public static boolean verbose = System.getProperty("ibex.log.verbose", "false").equals("true"); + public static boolean logDates = System.getProperty("ibex.log.dates", "false").equals("true"); + public static boolean notes = System.getProperty("ibex.log.notes.on", "true").equals("true"); + public static int maximumNoteLength = Integer.parseInt(System.getProperty("ibex.log.notes.maximumLength", (1024 * 32)+"")); + public static boolean rpc = false; + public static Date lastDate = null; + + public static PrintStream logstream = System.err; + + public static void flush() { logstream.flush(); } + public static void email(String address) { throw new Error("FIXME not supported"); } + public static void file(String filename) throws IOException { + // FIXME security + logstream = new PrintStream(new FileOutputStream(filename)); + } + public static void tcp(String host, int port) throws IOException { + // FIXME security + logstream = new PrintStream(new Socket(InetAddress.getByName(host), port).getOutputStream()); + } + + private static Hashtable threadAnnotations = new Hashtable(); + public static void setThreadAnnotation(String s) { threadAnnotations.put(Thread.currentThread(), s); } + + /** + * Notes can be used to attach log messages to the current thread + * if you're not sure you want them in the log just yet. + * Originally designed for retroactively logging socket-level + * conversations only if an error is encountered + */ + public static void note(String s) { + if (!notes) return; + StringBuffer notebuf = notebuf(); + notebuf.append(s); + if (notebuf.length() > maximumNoteLength) { + notebuf.reverse(); + notebuf.setLength(maximumNoteLength * 3 / 4); + notebuf.reverse(); + } + } + public static void clearnotes() { if (!notes) return; notebuf().setLength(0); } + private static Hashtable notebufs = new Hashtable(); + private static StringBuffer notebuf() { + StringBuffer ret = (StringBuffer)notebufs.get(Thread.currentThread()); + if (ret == null) { + ret = new StringBuffer(16 * 1024); + notebufs.put(Thread.currentThread(), ret); + } + return ret; + } + + /** true iff nothing has yet been logged */ + public static boolean firstMessage = true; + + /** message can be a String or a Throwable */ + public static synchronized void echo(Object o, Object message) { log(o, message, ECHO); } + public static synchronized void diag(Object o, Object message) { log(o, message, DIAGNOSTIC); } + public static synchronized void debug(Object o, Object message) { log(o, message, DEBUG); } + public static synchronized void info(Object o, Object message) { log(o, message, INFO); } + public static synchronized void warn(Object o, Object message) { log(o, message, WARN); } + public static synchronized void error(Object o, Object message) { log(o, message, ERROR); } + + // these two logging levels serve ONLY to change the color; semantically they are the same as DEBUG + private static final int DIAGNOSTIC = -2; + private static final int ECHO = -1; + + // the usual log4j levels, minus FAIL (we just throw an Error in that case) + public static final int DEBUG = 0; + public static final int INFO = 1; + public static final int WARN = 2; + public static final int ERROR = 3; + public static final int SILENT = Integer.MAX_VALUE; + public static int level = INFO; + + private static final int BLUE = 34; + private static final int GREEN = 32; + private static final int CYAN = 36; + private static final int RED = 31; + private static final int PURPLE = 35; + private static final int BROWN = 33; + private static final int GRAY = 37; + + private static String colorize(int color, boolean bright, String s) { + if (!Log.color) return s; + return + "\033[40;" + (bright?"1;":"") + color + "m" + + s + + "\033[0m"; + } + + private static String lastClassName = null; + private static synchronized void log(Object o, Object message, int level) { + if (level < Log.level) return; + if (firstMessage && !logDates) { + firstMessage = false; + logstream.println(colorize(GREEN, false, "===========================================================================")); + + // FIXME later: causes problems with method pruning + //diag(Log.class, "Logging enabled at " + new java.util.Date()); + + if (color) diag(Log.class, "logging messages in " + + colorize(BLUE, true, "c") + + colorize(RED, true, "o") + + colorize(CYAN, true, "l") + + colorize(GREEN, true, "o") + + colorize(PURPLE, true, "r")); + } + + String classname; + if (o instanceof Class) { + classname = ((Class)o).getName(); + if (classname.indexOf('.') != -1) classname = classname.substring(classname.lastIndexOf('.') + 1); + } + else if (o instanceof String) classname = (String)o; + else classname = o.getClass().getName(); + + if (classname.equals(lastClassName)) classname = ""; + else lastClassName = classname; + + if (classname.length() > (logDates ? 14 : 20)) classname = classname.substring(0, (logDates ? 14 : 20)); + while (classname.length() < (logDates ? 14 : 20)) classname = " " + classname; + classname = classname + (classname.trim().length() == 0 ? " " : ": "); + classname = colorize(GRAY, true, classname); + classname = classname.replace('$', '.'); + + if (logDates) { + Date d = new Date(); + if (lastDate == null || d.getYear() != lastDate.getYear() || d.getMonth() != lastDate.getMonth() || d.getDay() != lastDate.getDay()) { + String now = new java.text.SimpleDateFormat("EEE dd MMM yyyy").format(d); + logstream.println(); + logstream.println(colorize(GRAY, false, "=== " + now + " ==========================================================")); + } + java.text.DateFormat df = new java.text.SimpleDateFormat("[EEE HH:mm:ss] "); + classname = df.format(d) + classname; + lastDate = d; + } + + String annot = (String)threadAnnotations.get(Thread.currentThread()); + if (annot != null) classname += annot; + + if (message instanceof Throwable) { + if (level < ERROR) level = WARN; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ((Throwable)message).printStackTrace(new PrintStream(baos)); + if (notes && notebuf().length() > 0) { + PrintWriter pw = new PrintWriter(baos); + pw.println(); + pw.println("Thread notes:"); + pw.println(notebuf().toString()); + clearnotes(); + pw.flush(); + } + byte[] b = baos.toByteArray(); + BufferedReader br = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(b))); + String s = null; + try { + String m = ""; + while((s = br.readLine()) != null) m += s + "\n"; + if (m.length() > 0) log(o, m.substring(0, m.length() - 1), level); + } catch (IOException e) { + // FEATURE: use org.ibex.io.Stream's here + logstream.println(colorize(RED, true, "Logger: exception thrown by ByteArrayInputStream; this should not happen")); + } + lastClassName = ""; + return; + } + + String str = message.toString(); + if (str.indexOf('\n') != -1) lastClassName = ""; + while(str.indexOf('\t') != -1) + str = str.substring(0, str.indexOf('\t')) + " " + str.substring(str.indexOf('\t') + 1); + + classname = colorize(GRAY, false, classname); + int levelcolor = GRAY; + boolean bright = true; + switch (level) { + case DIAGNOSTIC: levelcolor = GREEN; bright = false; break; + case ECHO: levelcolor = BLUE; bright = true; break; + case DEBUG: levelcolor = BROWN; bright = true; break; + case INFO: levelcolor = GRAY; bright = false; break; + case WARN: levelcolor = BROWN; bright = false; break; + case ERROR: levelcolor = RED; bright = true; break; + } + + while(str.indexOf('\n') != -1) { + logstream.println(classname + colorize(levelcolor, bright, str.substring(0, str.indexOf('\n')))); + classname = logDates ? " " : " "; + classname = colorize(GRAY,false,classname); + str = str.substring(str.indexOf('\n') + 1); + } + logstream.println(classname + colorize(levelcolor, bright, str)); + } + + public static void recursiveLog(String indent, String name, Object o) throws JSExn { + if (!name.equals("")) name += " : "; + + if (o == null) { + JS.log(indent + name + ""); + + } else if (o instanceof JSArray) { + JS.log(indent + name + ""); + JSArray na = (JSArray)o; + for(int i=0; i"); + JS s = (JS)o; + Enumeration e = s.keys(); + while(e.hasMoreElements()) { + Object key = e.nextElement(); + if (key != null) + recursiveLog(indent + " ", key.toString(), + (key instanceof Integer) ? + s.get(((Integer)key)) : s.get(key.toString())); + } + } else { + JS.log(indent + name + o); + + } + } + +} hunk ./src/org/ibex/util/MSPack.c 1 +/* +UserInfo: + On start: + 0: Addr of CAB/EXE + 1: Length of CAB/EXE + On Edit: + 2: Addr of output_table array + +Exit codes: + 0: Success + 1: Internal Error + 2: Invalid CAB +*/ + +#include +#include +#include +#include +#include + +#include "mspack.h" + +#define MAX(a,b) (((a)>(b))?(a):(b)) +#define MIN(a,b) (((a)<(b))?(a):(b)) +#define MAX_MEMBERS 64 + +char *xstrdup(const char *s) { + char *ret = strdup(s); + if(ret == NULL) exit(1); + return ret; +} + +typedef struct { + char *addr; + int pos; + int size; + int length; + int writable; +} mem_buf_t; + +static mem_buf_t *cab_mem_buf = NULL; + +static void mem_buf_grow(mem_buf_t *buf,size_t newsize) { + size_t new_len; + char *p; + if(buf->length < 0) exit(1); + if(newsize <= buf->length) return; + new_len = MAX(buf->length ? buf->length*2 : 65536,newsize); + p = realloc(buf->addr,new_len); + if(p == NULL) exit(1); + buf->addr = p; + buf->length = new_len; +} + +static struct { + char *filename; + mem_buf_t buf; +} write_buf_table[MAX_MEMBERS]; + +static struct { + char *filename; + char *data; + int length; +} output_table[MAX_MEMBERS+1]; + +static struct mspack_file *my_open(struct mspack_system *sys, char *filename, int mode) { + mem_buf_t *buf = NULL; + int i; + if(strcmp(filename,"/dev/cab")==0) { + if(mode != MSPACK_SYS_OPEN_READ) return NULL; + buf = cab_mem_buf; + } else { + if(mode != MSPACK_SYS_OPEN_WRITE) return NULL; + + for(i=0;iwritable = 1; + break; + } + } + } + + return (struct mspack_file *) buf; +} + +static void my_close(struct mspack_file *buf_) { + mem_buf_t *buf = (mem_buf_t*) buf_; + /* NO OP */ +} + +static int my_read(struct mspack_file *buf_, void *out, int count) { + mem_buf_t *buf = (mem_buf_t*) buf_; + count = MIN(buf->size - buf->pos, count); + memcpy(out,buf->addr + buf->pos,count); + buf->pos += count; + return count; +} + +static int my_write(struct mspack_file *buf_, void *in, int count) { + mem_buf_t *buf = (mem_buf_t*) buf_; + if(!buf->writable) return -1; + if(buf->length < buf->pos + count) mem_buf_grow(buf,buf->pos + count); + memcpy(buf->addr+buf->pos,in,count); + buf->pos += count; + buf->size = MAX(buf->size,buf->pos); + return count; +} + +static int my_seek(struct mspack_file *buf_, off_t off, int mode) { + mem_buf_t *buf = (mem_buf_t*) buf_; + int newpos; + switch(mode) { + case MSPACK_SYS_SEEK_START: newpos = off; break; + case MSPACK_SYS_SEEK_CUR: newpos = buf->pos + off; break; + case MSPACK_SYS_SEEK_END: newpos = buf->size - off; break; + default: return -1; + } + if(newpos < 0) return -1; + if(newpos > buf->size) { + if(!buf->writable) return -1; + if(newpos > buf->length) + mem_buf_grow(buf,newpos); + } + buf->pos = newpos; + return 0; +} + +static off_t my_tell(struct mspack_file *buf_) { + mem_buf_t *buf = (mem_buf_t*) buf_; + return buf ? buf->pos : 0; +} + +// FEATURE: Remove this to possibly avoid pulling in stdio from libc +// (it may be getting pulled in anyway from malloc or something) +static void my_message(struct mspack_file *file, char *format, ...) { + va_list ap; + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); + fputc((int) '\n', stderr); + fflush(stderr); +} + +static void *my_alloc(struct mspack_system *sys, size_t size) { return malloc(size); } +static void my_free(void *p) { free(p); } +static void my_copy(void *src, void *dest, size_t bytes) { memcpy(dest, src, bytes); } + +static struct mspack_system my_system = { + &my_open, + &my_close, + &my_read, + &my_write, + &my_seek, + &my_tell, + &my_message, + &my_alloc, + &my_free, + &my_copy, + NULL +}; + +extern char *user_info[1024]; + +int mspack_main() { + struct mscab_decompressor *decomp; + struct mscabd_cabinet *cab; + struct mscabd_file *file; + mem_buf_t mem_buf; + size_t size = (size_t)user_info[1]; + int i; + + mem_buf.addr = user_info[0]; + mem_buf.pos = mem_buf.writable = 0; + mem_buf.length = -1; + mem_buf.size = size; + + cab_mem_buf = &mem_buf; + + decomp = mspack_create_cab_decompressor(&my_system); + if(!decomp) exit(1); + + cab = decomp->search(decomp,"/dev/cab"); + if(!cab) exit(2); + + for(file = cab->files;file;file=file->next) + decomp->extract(decomp,file,file->filename); + + decomp->close(decomp,cab); + mspack_destroy_cab_decompressor(decomp); + + for(i=0;i")) + isconstructed = true; + + // we can only prune static fields (to avoid altering object layout, which is hardcoded into + // CNI code), but that's okay since instance fields don't contribute to binary size + Field[] fields = clazz.getFields(); + for(int i=0; i", Type.VOID, Type.NO_ARGS, Constants.INVOKESPECIAL)); + il.append(InstructionConstants.ATHROW); + mg.setMaxStack(); + mg.setMaxLocals(); + mg.removeExceptions(); + mg.removeLocalVariables(); + mg.removeExceptionHandlers(); + mg.removeLineNumbers(); + cg.replaceMethod(methods[i], mg.getMethod()); + il.dispose(); + } + } + } + + // FIXME: chain up to superclass' ... that might remove the need for this hack + // FIXME: gcj compiling in jar-at-a-time mode can't be convinced to let classes outside the jar override + // the ones inside the jar + good = true; + + if (!good && !clazz.isAbstract() && !clazz.isInterface()) { + System.out.println("DROPPING " + clazz.getClassName()); + JavaClass[] ifaces = clazz.getInterfaces(); + String[] ifacestrings = new String[ifaces.length]; + for(int i=0; i 0 && (sig.charAt(0) == 'L' || sig.charAt(0) == '[')) { + if (sig.charAt(0) == 'L') sig = sig.substring(1, sig.length() - 1); + else if (sig.charAt(0) == '[') sig = sig.substring(1, sig.length()); + } + if (sig.length() <= 1) return null; + if (sig.equals("")) return null; + if (sig.startsWith(" 0 && (sig.charAt(0) == 'L' || sig.charAt(0) == '[')) { + if (sig.charAt(0) == 'L') sig = sig.substring(1, sig.length() - 1); + else if (sig.charAt(0) == '[') sig = sig.substring(1, sig.length()); + } + if (sig.length() <= 1) return; + if (sig.equals("")) return; + if (sig.startsWith("