Remove Most of dx-tests

Strip out the dx-tests that no longer compile. The build doesn't
break, because the compilation occurs in a script. These tests
haven't been running as part of CTS and are not maintained at all.

Keep the cfassembler module since that is needed by the vm-tests
which are still maintained. Rename the dx-tests directory to
cfassembler, since that is all that is in it now.

Change-Id: I038054ad0703bdd00af3b7b1012ea9ad3eabd8a6
diff --git a/tools/cfassembler/Android.mk b/tools/cfassembler/Android.mk
new file mode 100644
index 0000000..8e0f351
--- /dev/null
+++ b/tools/cfassembler/Android.mk
@@ -0,0 +1,47 @@
+# Copyright (C) 2008 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+# cfassembler host module
+#============================================================
+
+include $(CLEAR_VARS)
+
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE := cfassembler
+
+include $(BUILD_SYSTEM)/base_rules.mk
+
+$(LOCAL_BUILT_MODULE): $(HOST_OUT_JAVA_LIBRARIES)/cfassembler$(COMMON_JAVA_PACKAGE_SUFFIX)
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/etc/cfassembler | $(ACP)
+	@echo "Copy: $(PRIVATE_MODULE) ($@)"
+	$(copy-file-to-new-target)
+	$(hide) chmod 755 $@
+
+INTERNAL_DALVIK_MODULES += $(LOCAL_INSTALLED_MODULE)
+
+# cfassembler java library
+# ============================================================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := src/dxconvext/ClassFileAssembler.java src/dxconvext/util/FileUtils.java
+LOCAL_JAR_MANIFEST := etc/cfassembler_manifest.txt
+
+LOCAL_MODULE:= cfassembler
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+INTERNAL_DALVIK_MODULES += $(LOCAL_INSTALLED_MODULE)
diff --git a/tools/cfassembler/etc/cfassembler b/tools/cfassembler/etc/cfassembler
new file mode 100644
index 0000000..e05f4ae
--- /dev/null
+++ b/tools/cfassembler/etc/cfassembler
@@ -0,0 +1,66 @@
+#!/bin/bash
+#
+# Copyright (C) 2008 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Set up prog to be the path of this script, including following symlinks,
+# and set up progdir to be the fully-qualified pathname of its directory.
+prog="$0"
+while [ -h "${prog}" ]; do
+    newProg=`/bin/ls -ld "${prog}"`
+    newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
+    if expr "x${newProg}" : 'x/' >/dev/null; then
+        prog="${newProg}"
+    else
+        progdir=`dirname "${prog}"`
+        prog="${progdir}/${newProg}"
+    fi
+done
+oldwd=`pwd`
+progdir=`dirname "${prog}"`
+cd "${progdir}"
+progdir=`pwd`
+prog="${progdir}"/`basename "${prog}"`
+cd "${oldwd}"
+
+jarfile=cfassembler.jar
+libdir="$progdir"
+if [ ! -r "$libdir/$jarfile" ]
+then
+    libdir=`dirname "$progdir"`/tools/lib
+fi
+if [ ! -r "$libdir/$jarfile" ]
+then
+    libdir=`dirname "$progdir"`/framework
+fi
+if [ ! -r "$libdir/$jarfile" ]
+then
+    echo `basename "$prog"`": can't find $jarfile"
+    exit 1
+fi
+
+javaOpts=""
+while expr "x$1" : 'x-J' >/dev/null; do
+    opt=`expr "$1" : '-J\(.*\)'`
+    javaOpts="${javaOpts} -${opt}"
+    shift
+done
+
+if [ "$OSTYPE" = "cygwin" ] ; then
+    jarpath=`cygpath -w  "$libdir/$jarfile"`
+else
+    jarpath="$libdir/$jarfile"
+fi
+
+exec java $javaOpts -jar "$jarpath" "$@"
diff --git a/tools/cfassembler/etc/cfassembler_manifest.txt b/tools/cfassembler/etc/cfassembler_manifest.txt
new file mode 100644
index 0000000..382adc6
--- /dev/null
+++ b/tools/cfassembler/etc/cfassembler_manifest.txt
@@ -0,0 +1 @@
+Main-Class: dxconvext.ClassFileAssembler
diff --git a/tools/cfassembler/src/dxconvext/ClassFileAssembler.java b/tools/cfassembler/src/dxconvext/ClassFileAssembler.java
new file mode 100644
index 0000000..09eec27
--- /dev/null
+++ b/tools/cfassembler/src/dxconvext/ClassFileAssembler.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dxconvext;
+
+import dxconvext.util.FileUtils;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.security.DigestException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.zip.Adler32;
+
+public class ClassFileAssembler {
+
+    /**
+     * @param args
+     */
+    public static void main(String[] args) {
+        ClassFileAssembler cfa = new ClassFileAssembler();
+        cfa.run(args);
+    }
+
+    private void run(String[] args) {
+        // this class can be used to generate .class files that are somehow
+        // damaged in order to test the dalvik vm verifier.
+        // The input is a .cfh (class file hex) file.
+        // The output is a java vm .class file.
+        // The .cfh files can be generated as follows:
+        // 1. create the initial .cfh file from an existing .class files by using
+        //    the ClassFileParser
+        // 2. modify some bytes to damage the structure of the .class file in a 
+        //    way that would not be possible with e.g. jasmin (otherwise you are
+        //    better off using jasmin).
+        //    Uncomment the original bytes, and write "MOD:" meaning a modified 
+        // entry (with the original commented out)
+        //
+        // Use the ClassFileAssembler to generate the .class file.
+        // this class here simply takes all non-comment lines from the .cfh
+        // file, parses them as hex values and writes the bytes to the class file
+        File cfhF = new File(args[0]);
+        if (!cfhF.getName().endsWith(".cfh") &&
+            !cfhF.getName().endsWith(".dfh")) {
+            System.out.println("file must be a .cfh or .dfh file, and its filename end with .cfh or .dfh");
+            return;
+        }
+        
+        String outBase = args[1];
+        
+        boolean isDex = cfhF.getName().endsWith(".dfh");
+        
+        byte[] cfhbytes = FileUtils.readFile(cfhF);
+        ByteArrayInputStream bais = new ByteArrayInputStream(cfhbytes);
+        // encoding should not matter, since we are skipping comment lines and parsing
+        try {
+            // get the package name
+            BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(cfhF)));
+            String firstLine = br.readLine();
+            br.close();
+            String classHdr = "//@class:";
+            String dexHdr = "// Processing '";
+            String hdr;
+            if(isDex)
+                hdr = dexHdr;
+            else
+                hdr = classHdr;
+            
+            if (!firstLine.startsWith(hdr)) throw new RuntimeException("wrong format:"+firstLine +" isDex=" + isDex);
+            String tFile;
+            if(isDex) {
+                tFile = outBase + "/classes.dex";
+            } else {
+                String classO = firstLine.substring(hdr.length()).trim();
+                tFile = outBase +"/"+classO+".class";
+            }
+            File outFile = new File(tFile);
+            System.out.println("outfile:" + outFile);
+            String mkdir = tFile.substring(0, tFile.lastIndexOf("/"));
+            new File(mkdir).mkdirs();
+            
+            Reader r = new InputStreamReader(bais,"utf-8");
+            OutputStream os = new FileOutputStream(outFile);
+            BufferedOutputStream bos = new BufferedOutputStream(os);
+            writeClassFile(r, bos, isDex);
+            bos.close();
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException("problem while parsing .dfh or .cfh file: "+cfhF.getAbsolutePath(), e);
+        } catch (FileNotFoundException e) {
+            throw new RuntimeException("problem while parsing .dfh or .cfh file: "+cfhF.getAbsolutePath(), e);
+        } catch (IOException e) {
+            throw new RuntimeException("problem while parsing .dfh or .cfh file: "+cfhF.getAbsolutePath(), e);
+        } 
+    }
+    
+    /**
+     * Calculates the signature for the <code>.dex</code> file in the
+     * given array, and modify the array to contain it.
+     * 
+     * Originally from com.android.dx.dex.file.DexFile.
+     * 
+     * @param bytes non-null; the bytes of the file
+     */
+    private void calcSignature(byte[] bytes) {
+        MessageDigest md;
+
+        try {
+            md = MessageDigest.getInstance("SHA-1");
+        } catch (NoSuchAlgorithmException ex) {
+            throw new RuntimeException(ex);
+        }
+
+        md.update(bytes, 32, bytes.length - 32);
+
+        try {
+            int amt = md.digest(bytes, 12, 20);
+            if (amt != 20) {
+                throw new RuntimeException("unexpected digest write: " + amt +
+                                           " bytes");
+            }
+        } catch (DigestException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    /**
+     * Calculates the checksum for the <code>.dex</code> file in the
+     * given array, and modify the array to contain it.
+     * 
+     * Originally from com.android.dx.dex.file.DexFile.
+     * 
+     * @param bytes non-null; the bytes of the file
+     */
+    private void calcChecksum(byte[] bytes) {
+        Adler32 a32 = new Adler32();
+
+        a32.update(bytes, 12, bytes.length - 12);
+
+        int sum = (int) a32.getValue();
+
+        bytes[8]  = (byte) sum;
+        bytes[9]  = (byte) (sum >> 8);
+        bytes[10] = (byte) (sum >> 16);
+        bytes[11] = (byte) (sum >> 24);
+    }   
+
+    public void writeClassFile(Reader r, OutputStream rOs, boolean isDex) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream(8192);
+        BufferedReader br = new BufferedReader(r);
+        String line;
+        String secondLine = null;
+        int lineCnt = 0;
+        try {
+            while ((line = br.readLine()) != null) {
+                if (isDex && lineCnt++ == 1) {
+                    secondLine = line;
+                }
+                // skip it if it is a comment
+                if (!line.trim().startsWith("//")) {
+                    // we have a row like "    ae 08 21 ff" etc.
+                    String[] parts = line.split("\\s+");
+                    for (int i = 0; i < parts.length; i++) {
+                        String part = parts[i].trim();
+                        if (!part.equals("")) {
+                            int res = Integer.parseInt(part, 16);
+                            baos.write(res);
+                        }
+                    }
+                }
+            }
+            
+            // now for dex, update the checksum and the signature.
+            // special case:
+            // for two tests (currently T_f1_9.dfh and T_f1_10.dfh), we need
+            // to keep the checksum or the signature, respectively.
+            byte[] outBytes = baos.toByteArray();
+            if (isDex) {
+                boolean leaveChecksum = secondLine.contains("//@leaveChecksum");
+                boolean leaveSignature= secondLine.contains("//@leaveSignature");
+                // update checksum and signature for dex file            
+                if(!leaveSignature)
+                    calcSignature(outBytes);
+                if(!leaveChecksum)
+                    calcChecksum(outBytes);
+            }
+            rOs.write(outBytes);
+            rOs.close();
+        } catch (IOException e) {
+            throw new RuntimeException("problem while writing file",e);
+        }
+    }
+
+}
diff --git a/tools/cfassembler/src/dxconvext/ClassFileParser.java b/tools/cfassembler/src/dxconvext/ClassFileParser.java
new file mode 100644
index 0000000..8a43396
--- /dev/null
+++ b/tools/cfassembler/src/dxconvext/ClassFileParser.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dxconvext;
+
+import com.android.dx.cf.direct.ClassPathOpener;
+import com.android.dx.cf.direct.DirectClassFile;
+import com.android.dx.cf.direct.StdAttributeFactory;
+import com.android.dx.cf.iface.Member;
+import com.android.dx.cf.iface.ParseObserver;
+import com.android.dx.util.ByteArray;
+import com.android.dx.util.FileUtils;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+public class ClassFileParser {
+
+    private BufferedWriter bw; // the writer to write the result to.
+
+    /**
+     * Parses a .class file and outputs a .cfh (class file in hex format) file.
+     * 
+     * args[0] is the absolute path to the java src directory e.g.
+     * /home/fjost/android/workspace/dxconverter/src
+     * 
+     * args[1] is the absolute path to the classes directory e.g.
+     * /home/fjost/android/workspace/out/classes_javac this is the place where
+     * 
+     * args[2] is the absolute path to the java source file, e.g.
+     * /home/fjost/android/workspace/dxconverter/src/test/MyTest.java
+     * 
+     * 
+     * 
+     * @param args
+     */
+    public static void main(String[] args) throws IOException {
+        ClassFileParser cfp = new ClassFileParser();
+        cfp.process(args[0], args[1], args[2]);
+    }
+
+    private void process(final String srcDir, final String classesDir,
+            final String absSrcFilePath) throws IOException {
+        ClassPathOpener opener;
+
+        String fileName = absSrcFilePath;
+        // e.g. test/p1/MyTest.java
+        String pckPath = fileName.substring(srcDir.length() + 1);
+        // e.g. test/p1
+        String pck = pckPath.substring(0, pckPath.lastIndexOf("/"));
+        // e.g. MyTest
+        String cName = pckPath.substring(pck.length() + 1);
+        cName = cName.substring(0, cName.lastIndexOf("."));
+        String cfName = pck+"/"+cName+".class";
+        // 2. calculate the target file name:
+        // e.g. <out-path>/test/p1/MyTest.class
+        String inFile = classesDir + "/" + pck + "/" + cName + ".class";
+        if (!new File(inFile).exists()) {
+            throw new RuntimeException("cannot read:" + inFile);
+        }
+        byte[] bytes = FileUtils.readFile(inFile);
+        // write the outfile to the same directory as the corresponding .java
+        // file
+        String outFile = absSrcFilePath.substring(0, absSrcFilePath
+                .lastIndexOf("/"))+ "/" + cName + ".cfh";
+        Writer w;
+        try {
+            w = new OutputStreamWriter(new FileOutputStream(new File(outFile)));
+        } catch (FileNotFoundException e) {
+            throw new RuntimeException("cannot write to file:"+outFile, e);
+        }
+        // Writer w = new OutputStreamWriter(System.out);
+        ClassFileParser.this.processFileBytes(w, cfName, bytes);
+
+    }
+
+    /**
+     * 
+     * @param w the writer to write the generated .cfh file to
+     * @param name the relative name of the java src file, e.g.
+     *        dxc/util/Util.java
+     * @param allbytes the bytes of this java src file
+     * @return true if everthing went alright
+     */
+    void processFileBytes(Writer w, String name, final byte[] allbytes) throws IOException {
+        String fixedPathName = fixPath(name);
+        DirectClassFile cf = new DirectClassFile(allbytes, fixedPathName, true);
+        bw = new BufferedWriter(w);
+        String className = fixedPathName.substring(0, fixedPathName.lastIndexOf("."));
+        out("//@class:" + className, 0);
+        cf.setObserver(new ParseObserver() {
+            private int cur_indent = 0;
+            private int checkpos = 0;
+
+            /**
+             * Indicate that the level of indentation for a dump should increase
+             * or decrease (positive or negative argument, respectively).
+             * 
+             * @param indentDelta the amount to change indentation
+             */
+            public void changeIndent(int indentDelta) {
+                cur_indent += indentDelta;
+            }
+
+            /**
+             * Indicate that a particular member is now being parsed.
+             * 
+             * @param bytes non-null; the source that is being parsed
+             * @param offset offset into <code>bytes</code> for the start of
+             *        the member
+             * @param name non-null; name of the member
+             * @param descriptor non-null; descriptor of the member
+             */
+            public void startParsingMember(ByteArray bytes, int offset,
+                    String name, String descriptor) {
+                // ByteArray ba = bytes.slice(offset, bytes.size());
+                out("// ========== start-ParseMember:" + name + ", offset "
+                        + offset + ", len:" + (bytes.size() - offset)
+                        + ",desc: " + descriptor);
+                // out("// "+dumpReadableString(ba));
+                // out(" "+dumpBytes(ba));
+            }
+
+            /**
+             * Indicate that a particular member is no longer being parsed.
+             * 
+             * @param bytes non-null; the source that was parsed
+             * @param offset offset into <code>bytes</code> for the end of the
+             *        member
+             * @param name non-null; name of the member
+             * @param descriptor non-null; descriptor of the member
+             * @param member non-null; the actual member that was parsed
+             */
+            public void endParsingMember(ByteArray bytes, int offset,
+                    String name, String descriptor, Member member) {
+                ByteArray ba = bytes.slice(offset, bytes.size());
+                out("// ========== end-ParseMember:" + name + ", desc: "
+                        + descriptor);
+                // out("// "+dumpReadableString(ba));
+                // out(" "+dumpBytes(ba));
+            }
+
+            /**
+             * Indicate that some parsing happened.
+             * 
+             * @param bytes non-null; the source that was parsed
+             * @param offset offset into <code>bytes</code> for what was
+             *        parsed
+             * @param len number of bytes parsed
+             * @param human non-null; human form for what was parsed
+             */
+            public void parsed(ByteArray bytes, int offset, int len,
+                    String human) {
+                human = human.replace('\n', ' ');
+                out("// parsed:" + ", offset " + offset + ", len " + len
+                        + ", h: " + human);
+                if (len > 0) {
+                    ByteArray ba = bytes.slice(offset, offset + len);
+                    check(ba);
+                    out("// " + dumpReadableString(ba));
+                    out("   " + dumpBytes(ba));
+                }
+            }
+
+            private void out(String msg) {
+                ClassFileParser.this.out(msg, cur_indent);
+
+            }
+
+            private void check(ByteArray ba) {
+                int len = ba.size();
+                int offset = checkpos;
+                for (int i = 0; i < len; i++) {
+                    int b = ba.getByte(i);
+                    byte b2 = allbytes[i + offset];
+                    if (b != b2)
+                        throw new RuntimeException("byte dump mismatch at pos "
+                                + (i + offset));
+                }
+                checkpos += len;
+            }
+
+
+
+            private String dumpBytes(ByteArray ba) {
+                String s = "";
+                for (int i = 0; i < ba.size(); i++) {
+                    int byt = ba.getUnsignedByte(i);
+                    String hexVal = Integer.toHexString(byt);
+                    if (hexVal.length() == 1) {
+                        hexVal = "0" + hexVal;
+                    }
+                    s += hexVal + " ";
+                }
+                return s;
+            }
+
+            private String dumpReadableString(ByteArray ba) {
+                String s = "";
+                for (int i = 0; i < ba.size(); i++) {
+                    int bb = ba.getUnsignedByte(i);
+                    if (bb > 31 && bb < 127) {
+                        s += (char) bb;
+                    } else {
+                        s += ".";
+                    }
+                    s += "  ";
+                }
+                return s;
+            }
+
+
+        });
+        cf.setAttributeFactory(StdAttributeFactory.THE_ONE);
+        // what is needed to force parsing to the end?
+        cf.getMagic();
+        // cf.getFields();
+        // cf.getAttributes();
+        // cf.getMethods();        
+        bw.close();
+    }
+
+
+    private String getIndent(int indent) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < indent * 4; i++) {
+            sb.append(' ');
+        }
+        return sb.toString();
+    }
+
+    private void out(String msg, int cur_indent) {
+        try {
+            bw.write(getIndent(cur_indent) + msg);
+            bw.newLine();
+        } catch (IOException ioe) {
+            throw new RuntimeException("error while writing to the writer", ioe);
+        }
+    }
+
+    private static String fixPath(String path) {
+        /*
+         * If the path separator is \ (like on windows), we convert the path to
+         * a standard '/' separated path.
+         */
+        if (File.separatorChar == '\\') {
+            path = path.replace('\\', '/');
+        }
+
+        int index = path.lastIndexOf("/./");
+
+        if (index != -1) {
+            return path.substring(index + 3);
+        }
+
+        if (path.startsWith("./")) {
+            return path.substring(2);
+        }
+
+        return path;
+    }
+
+
+
+}
diff --git a/tools/cfassembler/src/dxconvext/util/FileUtils.java b/tools/cfassembler/src/dxconvext/util/FileUtils.java
new file mode 100644
index 0000000..7b7003d
--- /dev/null
+++ b/tools/cfassembler/src/dxconvext/util/FileUtils.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dxconvext.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * File I/O utilities.
+ */
+public final class FileUtils {
+    /**
+     * This class is uninstantiable.
+     */
+    private FileUtils() {
+        // This space intentionally left blank.
+    }
+
+    /**
+     * Reads the named file, translating {@link IOException} to a
+     * {@link RuntimeException} of some sort.
+     * 
+     * @param fileName non-null; name of the file to read
+     * @return non-null; contents of the file
+     */
+    public static byte[] readFile(String fileName) {
+        File file = new File(fileName);
+        return readFile(file);
+    }
+
+    /**
+     * Reads the given file, translating {@link IOException} to a
+     * {@link RuntimeException} of some sort.
+     * 
+     * @param file non-null; the file to read
+     * @return non-null; contents of the file
+     */
+    public static byte[] readFile(File file) {
+        if (!file.exists()) {
+            throw new RuntimeException(file + ": file not found");
+        }
+
+        if (!file.isFile()) {
+            throw new RuntimeException(file + ": not a file");
+        }
+
+        if (!file.canRead()) {
+            throw new RuntimeException(file + ": file not readable");
+        }
+
+        long longLength = file.length();
+        int length = (int) longLength;
+        if (length != longLength) {
+            throw new RuntimeException(file + ": file too long");
+        }
+
+        byte[] result = new byte[length];
+
+        try {
+            FileInputStream in = new FileInputStream(file);
+            int at = 0;
+            while (length > 0) {
+                int amt = in.read(result, at, length);
+                if (amt == -1) {
+                    throw new RuntimeException(file + ": unexpected EOF");
+                }
+                at += amt;
+                length -= amt;
+            }
+            in.close();
+        } catch (IOException ex) {
+            throw new RuntimeException(file + ": trouble reading", ex);
+        }
+
+        return result;
+    }
+}