Search code examples
javainputstreamfileinputstreamsystem.outsystem.in

Resetting FileInputStream in Java so I can run multiple instances of another program in a test program


My problem is that I was assigned to modify and improve upon a program that does LZW compression. My program, as far as I know, runs fine, but relies on System.in and System.out redirection for the input file and output file. For my test program, I run through a folder full of files and for each file, run 4 different tests on the program. To emulate the command line input/output redirection, on the first iteration of the loop for each file I do the following:

FileOutputStream fos = new FileOutputStream(compressOut); // compressOut is compressed file
PrintStream ps = new PrintStream(fos);
System.setOut(ps);
FileInputStream fis = new FileInputStream(file); // file is file to be compressed
System.setIn(fis);
fis.mark(0);// mark not supported so this is actually left over from my test code -- wanted to show I tried to do this

The first time it runs the program I'm testing, it works brilliantly and returns a compression ratio and prepares to write it to the program output file.

However, upon the second program call (and, if I were to eliminate all of the extra calls, the second iteration of the for loop) it crashes and returns that the input stream is empty. I haven't gotten the chance yet to see if the output stream will do the same thing, but what I have attempted to do to reset the stream is as follows:

Before each call program call, I have the following blocks of code which I **thought would fix the problem, but to no avail:

//System.setIn(null); // Tried - didn't work
//System.setOut(null); // Tried - didn't work
fos.close();
ps.close();
fis.close();
fos = new FileOutputStream(compressOut);
ps = new PrintStream(fos);
System.setOut(ps);
fis = new FileInputStream(file);
System.setIn(fis);
//fis.reset(); // Tried - didn't work

I've tried all sorts of combinations of different ways to reset the input stream, but every solution still returns the same result, an error message indicating that it's reading from an empty input stream - an error which means that the stream is at the end of the file (or there is nothing in the file).

The only other error I can get is that mark() isn't supported when I call reset if I attempt the mark(0); and reset(); methods.

I've done reading and can't find any solid answers as to how to get this to work. The program turns the StdIn into a BufferedInputStream and StdOut to a BufferedOutputStream, so I need a method that will be compatible and provide performance similar to StdIn/StdOut redirection.

TL;DR: I need to reset my FileInputStream and can't do so using mark() reset() nor if I reinitialize the FileInputStream

Update: tried wrapping FIS into a BufferedInputStream as suggested on a similar post, where mark() is supported. However, the mark() and reset() methods are only good for the size of the BufferedInputStream (which I believe is 4096 bytes) which pretty much excludes the potential usefulness of the mark/reset feature in all of my files. They are all larger than 4kB :(

Update 2: I have decided to post the full snippet of code from where I begin the for-loop to where the empty stream error occurs, in case anyone can see something I can't. This is the code in question:

public static void main(String[] args) throws IOException, InterruptedException {
    File programOutputPath = new File("-- the compression file path --");
    File folderPath = new File("-- the relative folder path --");
    File compressOut = new File(folderPath + "compressed.lzw");
    File[] allFiles = folderPath.listFiles(); // get an array of all files
    FileWriter fw = new FileWriter(programOutputPath);
    for(File file : allFiles) {
        FileOutputStream fos = new FileOutputStream(compressOut);
        PrintStream ps = new PrintStream(fos);
        System.setOut(ps);
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
        System.setIn(bis);
        String[] cArgs = new String[2];
        cArgs[0] = "-"; cArgs[1] = "-n";
        String[] dArgs = new String[1];
        dArgs[0] = "+";
        String[] LZWArgs = new String[1];
        LZWArgs[0] = "-";
        System.err.println("File: " + file.toString());
        if(!file.canWrite()) {
            System.err.println("SKIPPED FILE");
        }
        if(file.getName().equalsIgnoreCase(".gitignore") || file.getName().equalsIgnoreCase("compressed.lzw")) continue;
        MyLZW.main(cArgs); // runs fine
        if(decompress) {
            //MyLZW.main(dArgs); // not executing this
        }
        long sizeUnc = file.length();
        long sizeC = compressOut.length();
        double ratio = (double)sizeUnc/(double)sizeC; // compression ratio is correct
        System.err.println("java MyLZW - -r <" + file.getName() + "> " + " compressed.lzw compression ratio:" + ratio ); // works fine
        fw.write("java MyLZW - -n <" + file.getName() + "> " + " compressed.lzw compression ratio:" + ratio + "\n");
        cArgs[1] = "-r";
        bis.close(); // close BufferedInputStream
        bis = new BufferedInputStream(new FileInputStream(file)); // reinitialize BIS
        System.setIn(bis); // setIn to newly initialized BufferedInputStream
        MyLZW.main(cArgs); // crashes here b/c empty input stream

Solution

  • It sounds like the problem is internal to the program; it may be retaining a reference to the original value of System.in on subsequent calls.

    Another possibility is that the program is clobbering the input file (replacing it with an empty file) as a side-effect.

    There is no problem calling System.setIn() repeatedly, and creating a new FileInputStream as you have done will indeed read the file again from the beginning. Therefore, the problem lies in code that you have not posted.


    Inside the program, the input is initialized as a static variable, like this:

    private static BufferedInputStream in = new BufferedInputStream(System.in); 
    

    That initialization happens only once, when the class is loaded. This is a bad design, and it should be fixed. Because System.in is read only once, the subsequent changes you make with System.setIn() are ignored.

    If you are unable to fix it properly, you could reset that variable with reflection, but this problem could just be the tip of the iceberg of bad design. Alternatively, you could try isolating this program in its own class loader, and create a new class loader each time your master process runs the program.