// ********************************************************************** // // <copyright> // // BBN Technologies // 10 Moulton Street // Cambridge, MA 02138 // (617) 873-8000 // // Copyright (C) BBNT Solutions LLC. All rights reserved. // // </copyright> // ********************************************************************** // // $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/io/BinaryFile.java,v $ // $RCSfile: BinaryFile.java,v $ // $Revision: 1.14 $ // $Date: 2009/01/21 01:24:42 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap.io; import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.Vector; import com.bbn.openmap.Environment; import com.bbn.openmap.MoreMath; import com.bbn.openmap.util.Debug; /** * The BinaryFile is the standard object used to access data files. It acts like * a RandomAccessFile, but will work on jar file contents and URLs, too. The * source of the data is isolated through the InputReader interface. */ public class BinaryFile { private static int openCount = 0; private static int classCount = 0; private InputReader inputReader = null; /** * The byte order of the underlying file. (<code>true</code>== MSB-First == * big-endian) */ protected boolean MSBFirst = false; /** * Constructs a new BinaryFile with the specified file as the input. The * default byte-order is LSB first. Reads start at the first byte of the * file. * * @param f the file to be opened for reading * @exception IOException pass-through errors from opening a * RandomAccessFile with f * @see java.io.RandomAccessFile */ public BinaryFile(File f) throws IOException { inputReader = new FileInputReader(f); classCount++; openCount++; } /** * Constructs a new BinaryFile with the specified inputReader as the input. * * @param inputReader the input reader to be opened for reading */ private BinaryFile(InputReader inputReader) { this.inputReader = inputReader; classCount++; openCount++; } /** * Constructs a new BinaryFile with the specified file as the input. The * byte-order is undefined. Reads start at the first byte of the file. This * constructor looks for the file with the string given, and will call the * correct constructor as appropriate. If the string represents a file * available locally, then the BinaryFile will be accessed with a * FileInputReader using a RandomAccessFile. If it's only available as a * resource, then a StreamInputReader will be used. The name should be a * path to a file, or the name of a resource that can be found in the * classpath, or a URL. * * @param name the name of the file to be opened for reading * @exception IOException pass-through errors from opening the file. */ public BinaryFile(String name) throws IOException { boolean showDebug = false; if (Debug.debugging("binaryfile")) { showDebug = true; } if (showDebug) { Debug.output("BinaryFile: trying to figure out how to handle " + name); } try { File file = null; URL url = null; if (!Environment.isApplet()) { file = new File(name); } if (file != null && file.exists()) { // If the string represents a file, then we want to // use the RandomAccessFile aspect of the BinaryFile. setInputReader(new FileInputReader(file)); } else { // see JNLP deploy tip here // http://java.sun.com/javase/6/docs/technotes/guides/jweb/deployment_advice.html#ClassLoader_and_Resources final InputStream resourceStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(name); if (resourceStream != null) { if (showDebug) { Debug.output("BinaryFile: loading " + name + " via getResourceAsStream"); } final String urlName = name; setInputReader(new StreamInputReader() { { this.name = urlName; this.inputStream = resourceStream; } @Override protected void reopen() throws IOException { super.reopen(); inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(name); } }); } else { // If the getResourceAsStream didn't work, go back to the // old way of doing things... // url = ClassLoader.getSystemResource(name); url = Thread.currentThread().getContextClassLoader().getResource(name); // OK, now we want to look around for the file, in the // classpaths, and as a resource. It may be a file in // a classpath, available for direct access. if (url != null) { String newname = url.getFile(); if (showDebug) { Debug.output("BinaryFile: looking for " + newname); } if (!Environment.isApplet()) { file = new File(newname); } if (file != null && file.exists()) { // It's still a file, available directly. // Access it with the RandomAccessFile setInputReader(new FileInputReader(file)); } else { // Need to get it as a resource. Needs // special handling if it's coming in a jar // file. Jar file references have a "!" in // them if (!setJarInputReader(newname)) { if (showDebug) { Debug.output(" trying as url: " + url); } setInputReader(new URLInputReader(url)); } } } else if (Environment.isApplet()) { if (showDebug) { Debug.output(" As applet, checking codebase..."); } // Look in the codebase for applets... URL[] cba = new URL[1]; cba[0] = Environment.getApplet().getCodeBase(); URLClassLoader ucl = URLClassLoader.newInstance(cba); url = ucl.getResource(name); if (url != null) { setInputReader(new URLInputReader(url)); } } } // It's not in the classpath, so try it as a URL. if (inputReader == null) { if (showDebug) { Debug.output(" lastly, trying as URL: " + name); } try { setInputReader(new URLInputReader(new URL(name))); } catch (java.security.AccessControlException ace) { Debug.output("BinaryFile: " + name + " couldn't be accessed."); throw new IOException("AccessControlException trying to fetch " + name + " as a URL"); } } } if (inputReader == null) { throw new FileNotFoundException("BinaryFile can't find: " + name); } classCount++; openCount++; } catch (IOException ioe) { throw ioe; } } /** * Takes a name of a file, and checks to see if it reflects an entry in a * jar file. (Check the filename and see if it looks like * "jarfile!jarfileentry".) If it is, it separates the path, and set the * inputReader to a JarInputReader and returns true. If not, it returns * false. */ protected boolean setJarInputReader(String name) throws IOException { try { int index = name.indexOf("!"); if (index != -1) { // Used to be this, modified by Erik Sanders to work // with jdk 1.4 plugin // String jarFileName = // name.substring(name.indexOf(":") + 1, index); // changed to this... String jarFileName; if (name.startsWith("file:")) { // java-plugin 1.3 returns local file: strip file: // from string jarFileName = name.substring(name.indexOf(":") + 1, index); } else { // java-plugin 1.4 returns reference to server, so // leave http:// part // Used to start the substring from 1, but changed // to 0 thanks to DGK jarFileName = name.substring(0, index); } // skip !/ " String jarEntryName = name.substring(index + 2); if (Debug.debugging("binaryfile")) { Debug.output(" got: \n" + jarFileName + "\n" + jarEntryName); } // If the jar doesn't exist, should return something // that indicates this. Should check the performance // implications of this call, though, at some point. // DGK added File f = new File(jarFileName); if (f.exists() == false) { return false; } setInputReader(new JarInputReader(jarFileName, jarEntryName)); return true; } } catch (java.security.AccessControlException ace) { if (Debug.debugging("binaryfile")) { Debug.output("BinaryFile.setJarInputFile: AccessControlException for " + name); } } return false; } /** * A simple test method to determine if a file or directory, represented by * a string, can be found by the current Java environment. Uses the same * tests as BinaryFile constructor for tracking down a file. * * @param name A path to a file, a URL, or a path to a jar file entry. * @return true if the file can be found */ public static boolean exists(String name) { boolean exists = false; try { File file = null; URL url = null; if (!Environment.isApplet()) { file = new File(name); } if (file != null && file.exists()) { exists = true; } else { // Could be a binary file stored in a jar file try { url = new URL(name); return true; } catch (MalformedURLException e) { // Do nothing, try other url types url = null; } // url = ClassLoader.getSystemResource(name); url = Thread.currentThread().getContextClassLoader().getResource(name); // OK, now we want to look around for the file, in the // classpaths, and as a resource. It may be a file in // a classpath, available for direct access. if (url != null) { exists = true; } else if (Environment.isApplet()) { if (Debug.debugging("binaryfile")) { Debug.output(" As applet, checking codebase..."); } // Look in the codebase for applets... URL[] cba = new URL[1]; cba[0] = Environment.getApplet().getCodeBase(); URLClassLoader ucl = URLClassLoader.newInstance(cba); if (ucl.getResource(name) != null) { exists = true; // This has been commented out because the // AppletDataNugget has been deprecated, and // is not needed. // } else { // url = AppletDataNugget.findResource(name); // if (url != null) { // exists = true; // } } } // It's not in the classpath, so try it as a URL to a // webserver. if (!exists && name.indexOf("http:") != -1) { try { InputStream stream = new URL(name).openStream(); stream.close(); exists = true; } catch (java.security.AccessControlException ace) { exists = false; } } } } catch (IOException ioe) { Debug.message("binaryfile", "BinaryFile.exists() caught IOException"); exists = false; } if (Debug.debugging("binaryfile")) { Debug.output("BinaryFile.exists(" + name + ") = " + exists); } return exists; } /** * Get the source name from the input reader. */ public String getName() { if (inputReader != null) { return inputReader.getName(); } return null; } /** * Get the inputReader used for accessing the file, for querying purposes. * Don't use it to get data, or the file pointers may get messed up. */ public InputReader getInputReader() { return inputReader; } /** * Set the input reader used by the BinaryFile. Make sure it's initialized * properly. */ public void setInputReader(InputReader reader) { if (Debug.debugging("binaryfile")) { Debug.output("Setting inputReader"); } inputReader = reader; } /** * Set the byte-ordering used to read shorts, int, etc. * * @param msbfirst <code>true</code>= MSB first, <code>false</code>= LSB * first */ public void byteOrder(boolean msbfirst) { MSBFirst = msbfirst; } /** * Accessor for the byte ordering used to read multibyte types. * * @return byte ordering, true means MSB first. */ public boolean byteOrder() { return MSBFirst; } /** * Skip over n bytes in the input file * * @param n the number of bytes to skip * @return the actual number of bytes skipped. annoying, isn't it? * @exception IOException Any IO errors that occur in skipping bytes in the * underlying file */ public long skipBytes(long n) throws IOException { return inputReader.skipBytes(n); } /** * Get the index of the next character to be read * * @return the index * @exception IOException Any IO errors that occur in accessing the * underlying file */ public long getFilePointer() throws IOException { return inputReader.getFilePointer(); } /** * Set the index of the next character to be read. * * @param pos the position to seek to. * @exception IOException Any IO Errors that occur in seeking the underlying * file. */ public void seek(long pos) throws IOException { inputReader.seek(pos); } /** * The length of the InputReader source. */ public long length() throws IOException { return inputReader.length(); } /** * Return how many bytes left to be read in the file. * * @return the number of bytes remaining to be read (counted in bytes) * @exception IOException Any IO errors encountered in accessing the file */ public long available() throws IOException { return inputReader.available(); } /** * Closes the underlying file, but with a chance for re-opening if accessed * again. * * @exception IOException Any IO errors encountered in accessing the file */ public void close() throws IOException { if (inputReader != null) { inputReader.close(); openCount--; } } /** * Closes underlying file, get rid of resources and knowledge of file. To be * called when you don't need the file any more. * * @throws IOException */ public void dispose() throws IOException { close(); inputReader = null; } /** * Read from the file. * * @return one byte from the file. -1 for EOF * @exception IOException Any IO errors encountered in reading from the file */ public int read() throws IOException { return inputReader.read(); } /** * Read from the file * * @param b The byte array to read into * @param off the first array position to read into * @param len the number of bytes to read * @return the number of bytes read * @exception IOException Any IO errors encountered in reading from the file */ public int read(byte b[], int off, int len) throws IOException { return inputReader.read(b, off, len); } /** * Read from the file. * * @param b the byte array to read into. Equivalent to * <code>read(b, 0, b.length)</code> * @return the number of bytes read * @exception IOException Any IO errors encountered in reading from the file * @see java.io.RandomAccessFile#read(byte[]) */ public int read(byte b[]) throws IOException { return inputReader.read(b); } /** * Read from the file. * * @param howmany the number of bytes to read * @param allowless if we can return fewer bytes than requested * @return the array of bytes read. * @exception FormatException Any IO Exceptions, plus an end-of-file * encountered after reading some, but now enough, bytes when * allowless was <code>false</code> * @exception EOFException Encountered an end-of-file while allowless was * <code>false</code>, but NO bytes had been read. */ public byte[] readBytes(int howmany, boolean allowless) throws EOFException, FormatException { return inputReader.readBytes(howmany, allowless); } /** * Reads and returns a single byte, cast to a char * * @return the byte read from the file, cast to a char * @exception EOFException the end-of-file has been reached, so no chars * where available * @exception FormatException a rethrown IOException */ public char readChar() throws EOFException, FormatException { try { int retv = inputReader.read(); if (retv == -1) { throw new EOFException("Error in ReadChar, EOF reached"); } return (char) retv; } catch (IOException i) { throw new FormatException("readChar IOException: " + i.getMessage()); } } /** * Read a byte from the file, return an unsigned integer. * * @return one byte from the file. -1 for EOF causes EOFException * @exception IOException Any IO errors encountered in reading from the file */ public int readUnsigned() throws IOException, EOFException { byte b = (byte) read(); if (b == -1) { throw new EOFException(); } return MoreMath.signedToInt(b); } /** * Reads and returns a short. * * @return the 2 bytes merged into a short, according to the current byte * ordering * @exception EOFException there were less than 2 bytes left in the file * @exception FormatException rethrow of IOExceptions encountered while * reading the bytes for the short * @see #read(byte[]) */ public short readShort() throws EOFException, FormatException { // MSBFirst must be set when we are called return MoreMath.BuildShort(readBytes(2, false), MSBFirst); } /** * Code for reading shorts that are two-byte integers, high order first, and * negatives are signed magnitude. Users may have to switch the bytes and * convert negatives to the complement they use. This can be done by putting * the low order byte first, then turning off bit 15 (the high order bit), * and then multiplying by -1." Basically they are encoded as positive * numbers, but bit 15 is set to 1. * * @return 2 bytes merged into a short * @throws EOFException * @throws FormatException */ public short readShortData() throws EOFException, FormatException { // read in the two bytes byte[] bytevec = readBytes(2, false); // check for negative values - bit 7 of byte 0 if (bytevec[0] < 0) { // mask bit 7 bytevec[0] &= 0x7f; // create the short and multiply the result by -1 return ((short) (MoreMath.BuildShort(bytevec, true) * -1)); } return MoreMath.BuildShort(bytevec, true); } /** * Reads and returns a integer from 2 bytes. * * @return the 2 bytes merged into a short, according to the current byte * ordering, and then unsigned to int. * @exception EOFException there were less than 2 bytes left in the file * @exception FormatException rethrow of IOExceptions encountered while * reading the bytes for the short * @see #read(byte[]) */ public int readUnsignedShort() throws EOFException, FormatException { // MSBFirst must be set when we are called return MoreMath.signedToInt(readShort()); } /** * Reads and returns a long * * @return the 4 bytes merged into a long, according to the current byte * ordering * @exception EOFException there were less than 4 bytes left in the file * @exception FormatException rethrow of IOExceptions encountered while * reading the bytes for the integer * @see #read(byte[]) */ public int readInteger() throws EOFException, FormatException { // MSBFirst must be set when we are called return MoreMath.BuildInteger(readBytes(4, false), MSBFirst); } public void readIntegerArray(int vec[], int offset, int len) throws EOFException, FormatException { for (int i = 0; i < len; i++) { vec[offset++] = readInteger(); } } /** * Reads and returns a long * * @return the 8 bytes merged into a long, according to the current byte * ordering * @exception EOFException there were less than 8 bytes left in the file * @exception FormatException rethrow of IOExceptions encountered while * reading the bytes for the long * @see #read(byte[]) */ public long readLong() throws EOFException, FormatException { return MoreMath.BuildLong(readBytes(8, false), MSBFirst); } /** * Reads and returns a float * * @return the 4 bytes merged into a float, according to the current byte * ordering * @exception EOFException there were less than 4 bytes left in the file * @exception FormatException rethrow of IOExceptions encountered while * reading the bytes for the float * @see #read(byte[]) */ public float readFloat() throws EOFException, FormatException { return Float.intBitsToFloat(readInteger()); } public void readFloatArray(float vec[], int offset, int len) throws EOFException, FormatException { for (int i = 0; i < len; i++) { vec[offset++] = readFloat(); } } public void readFloatArray(double vec[], int offset, int len) throws EOFException, FormatException { for (int i = 0; i < len; i++) { vec[offset++] = readFloat(); } } /** * Reads and returns a double * * @return the 8 bytes merged into a double, according to the current byte * ordering * @exception EOFException there were less than 8 bytes left in the file * @exception FormatException rethrow of IOExceptions encountered while * reading the bytes for the short * @see #read(byte[]) */ public double readDouble() throws EOFException, FormatException { return Double.longBitsToDouble(readLong()); } /** * Reads <code>length</code> bytes and returns a string composed of the * bytes cast to chars * * @param length the number of bytes to read into the string * @return the composed string * @exception EOFException there were less than <code>length</code> bytes * left in the file * @exception FormatException rethrow of IOExceptions encountered while * reading the bytes for the short */ public String readFixedLengthString(int length) throws EOFException, FormatException { byte foo[] = readBytes(length, false); return new String(foo, 0, length); } /** * Read a bytes and throw an InvalidCharException if it doesn't match * <code>expected</code> * * @param expected what the next char is claimed to be * @exception EOFException there wasn't a byte, so we can't check for a * match * @exception InvalidCharException throws when the character read doesn't * match <code>expected</code> The .c member of the thrown * exception is the actual char read * @exception FormatException some other error from reading the file */ public void assertChar(char expected) throws EOFException, FormatException { char c = readChar(); if (c != expected) { throw new InvalidCharException("AssertChar: expected " + expected + " got " + c, c); } } /** * Reads a string until the specified delimiter or EOF is encountered * * @param delim the end-of-string delimiter * @return the string that was read * @exception FormatException rethrow of IOExceptions from the read methods */ public String readToDelimiter(char delim) throws FormatException { StringBuffer buildretval = new StringBuffer(); char tmp; try { while ((tmp = readChar()) != delim) buildretval.append(tmp); } catch (EOFException e) { // allowable } catch (FormatException fe) { if (buildretval.length() == 0) { throw fe; } } return buildretval.toString(); } /** * Makes sure that the file has been closed. * * @exception Throwable what it throws. */ protected void finalize() throws Throwable { close(); classCount--; } /** * Maintains a list of objects that can be closed so that other files can be * opened. */ private static Vector<WeakReference<Closable>> closableList = new Vector<WeakReference<Closable>>(); /** * Add an object that can be closed if needed. Duplicates are allowed. Only * holds a WeakReference, so that the object can still be garbage-collected. * * @param it the object that can be closed */ public static synchronized void addClosable(Closable it) { closableList.addElement(new WeakReference<Closable>(it)); } /** * Remove an object from the closable list. * * @param it the object to remove */ public static synchronized void removeClosable(Closable it) { for (int i = 0; i < closableList.size(); i++) { Object o = closableList.elementAt(i).get(); if ((o == it) || (o == null)) { closableList.removeElementAt(i); i--; // in case its in the list more than once } } } public static synchronized void closeClosable() { System.out.println("closeClosable " + closableList.size()); for (int i = 0; i < closableList.size(); i++) { Closable c = (Closable) ((WeakReference<Closable>) closableList.elementAt(i)).get(); if ((c == null) || !c.close(false)) { closableList.removeElementAt(i); i--; } } } /** * Read the {@link BinaryFile} into memory and return a new * {@link BinaryFile} instance working on that in-memory version of the * file. * * @return BinaryFile object pointing to memory version. * @throws IOException */ public BinaryFile readFully() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buf = new byte[4096]; seek(0); int len; while (available() > 0) { len = read(buf); baos.write(buf, 0, len); } return new BinaryFile(new ByteArrayInputReader(baos.toByteArray())); } }