File Viewer, Part 1

I often find it useful to be able to open an arbitrary file and interpret it in an arbitrary fashion. Most commonly I want to view a file as text, but occasionally it’s useful to interpret it as hexadecimal integers, IEEE 754 floating-point data, or something else. In this book, I’m going to develop a program that lets you open any file and view its contents in a variety of different ways. In each chapter, I’ll add a piece to the program until it’s fully functional. Since this is only the beginning of the program, it’s important to keep the code as general and adaptable as possible.

Example 4.3 reads a series of filenames from the command line in the main() method. Each filename is passed to a method that opens the file. The file’s data is read and printed on System.out. Exactly how the data is printed on System.out is determined by a command-line switch. If the user selects ASCII format (-a), then the data will be assumed to be ASCII (more properly, ISO Latin-1) text and printed as chars. If the user selects decimal dump (-d), then each byte should be printed as unsigned decimal numbers between and 255, 16 to a line. For example:

000 234 127 034 234 234 000 000 000 002 004 070 000 234 127 098

Leading zeros are used to maintain a constant width for the printed byte values and for each line. A simple selection algorithm is used to determine how many leading zeros to attach to each number. For hex dump format (-h), each byte should be printed as two hexadecimal digits. For example:

CA FE BA BE 07 89 9A 65 45 65 43 6F F6 7F 8F EE E5 67 63 26 98 9E 9C

Hexadecimal encoding is easier, because each byte is always exactly two hex digits. The static Integer.toHexString() method is used to convert each byte read into two hexadecimal digits.

ASCII format is the default and is the simplest to implement. This conversion can be accomplished merely by copying the input data to the console.

Example 4-3. The FileDumper Program

import java.io.*;
import com.macfaq.io.*;

public class FileDumper {

  public static final int ASC = 0;
  public static final int DEC = 1;
  public static final int HEX = 2;

  public static void main(String[] args) {

    if (args.length < 1) {
      System.err.println("Usage: java FileDumper [-ahd] file1 file2...");
      return;
    }

    int firstArg = 0;
    int mode = ASC;

    if (args[0].startsWith("-")) {
      firstArg = 1;
      if (args[0].equals("-h")) mode = HEX;
      else if (args[0].equals("-d")) mode = DEC;
    }

    for (int i = firstArg; i < args.length; i++) {
      if (mode == ASC) dumpAscii(args[i]);
      else if (mode == HEX) dumpHex(args[i]);
      else if (mode == DEC) dumpDecimal(args[i]);      
      if (i < args.length-1) {  // more files to dump
        System.out.println("\r\n--------------------------------------\r\n");
      }
    }
  }

  public static void dumpAscii(String filename) {

    FileInputStream fin = null;
    try {
      fin = new FileInputStream(filename);
      StreamCopier.copy(fin, System.out);
    }
    catch (IOException e) {System.err.println(e);}
    finally {
      try {
        if (fin != null) fin.close();
       }
      catch (IOException e) { }
    }
  }

  public static void dumpDecimal(String filename) {

    FileInputStream fin = null;
    byte[] buffer = new byte[16];
    boolean end = false;
    int bytesRead;

    try {
      fin = new FileInputStream(filename);
      while (!end) {
        bytesRead = 0;
        while (bytesRead < buffer.length) {
          int r = fin.read(buffer, bytesRead, buffer.length - bytesRead);
          if (r == -1) {
            end = true;
            break;
          }
          bytesRead += r;
        }
        for (int i = 0; i < bytesRead; i++) {
          int dec = buffer[i];
          if (dec < 0) dec = 256 + dec;
          if (dec < 10) System.out.print("00" + dec + " ");
          else if (dec < 100) System.out.print("0" + dec + " ");
          else System.out.print(dec + " ");
        }
        System.out.println();
      }
    }
    catch (IOException e) {System.err.println(e);}
    finally {
      try {
        if (fin != null) fin.close();
       }
      catch (IOException e) { }
    }

  }

  public static void dumpHex(String filename) {

    FileInputStream fin = null;
    byte[] buffer = new byte[24];
    boolean end = false;
    int bytesRead;

    try {
      fin = new FileInputStream(filename);
      while (!end) {
        bytesRead = 0;
        while (bytesRead < buffer.length) {
          int r = fin.read(buffer, bytesRead, buffer.length - bytesRead);
          if (r == -1) {
            end = true;
            break;
          }
          bytesRead += r;
        }
        for (int i = 0; i < bytesRead; i++) {
          int hex = buffer[i];
          if (hex < 0) hex = 256 + hex;
          if (hex >= 16) System.out.print(Integer.toHexString(hex) + " ");
          else System.out.print("0" + Integer.toHexString(hex) + " ");
        }
        System.out.println();
      }
    }
    catch (IOException e) {System.err.println(e);}
    finally {
      try {
        if (fin != null) fin.close();
       }
      catch (IOException e) { }
    }
  }
}

When FileDumper is used to dump its own .class file in hexadecimal format, it produces the following:

D:\JAVA\ioexamples\04>java FileDumper -h FileDumper.class
ca fe ba be 00 03 00 2d 00 7e 03 00 00 00 00 03 00 00 00 01 03 00 00 00
02 08 00 43 08 00 44 08 00 52 08 00 53 08 00 54 08 00 55 08 00 56 08 00
63 07 00 5c 07 00 66 07 00 6d 07 00 6e 07 00 6f 07 00 70 07 00 71 07 00

In later chapters, I’ll add a graphical user interface and many more possible interpretations of the data in the file, including floating-point, big- and little-endian integer, and various text encodings.

Get Java I/O now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.