serhii
serhii

Reputation: 1177

InputStream or Reader. Method with same logic

I need implement the same logic: comparing files using InputStream for comparing in binary form, and Reader for comparing in unicode. Can I somehow make a method that takes InputStream or Reader and does the same logic with read() method of taken parameter.

Can't find a wildcard for this case, because, as I see, InputStream and Reader has no interacting hierarchies.

I need a method that takes BufferedInputStream or BufferedReader.

Upvotes: 2

Views: 119

Answers (2)

jdevelop
jdevelop

Reputation: 12296

Some more or less generic approach, that you could extend for your needs. The idea is to pipe the original stream to another stream to be read by another thread while the original stream is being read in the current thread. So TeeInputStream here is used to wrap the original stream and send copy of data from the stream to the PipeOutputStream. In turn, PipeOutputStream is being read by PipeInputStream, that is running in the separate thread. The content of the stream is "hashed" by MD5 to be compared when both streams are read fully, but you may use any approach you'd like to compare byte data (diffs etc).

This is a little bit verbose, but works well in case if you need to feed the stream to an XML reader and calculate the CRC or MD5 checksum of the stream at the same time without reading from the stream twice.

    import org.apache.commons.io.input.TeeInputStream;

    import java.io.*;
    import java.security.MessageDigest;
    import java.util.Arrays;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;

    public class Test1 {

        private static final ExecutorService executor = Executors.newFixedThreadPool(2);

        public static boolean compareStreams(InputStream is1, InputStream is2) throws Exception {
            // create pipe that will copy data from is1 to pipe accessible by pis1
            final PipedOutputStream pos1 = new PipedOutputStream();
            final PipedInputStream pis1 = new PipedInputStream(pos1, 1024);
            final TeeInputStream tee1 = new TeeInputStream(is1, pos1, true);

            // create pipe that will copy data from is2 to pipe accessible by pis2
            final PipedOutputStream pos2 = new PipedOutputStream();
            final PipedInputStream pis2 = new PipedInputStream(pos2, 1024);
            final TeeInputStream tee2 = new TeeInputStream(is2, pos2, true);

            class Comparator implements Runnable {
                private final InputStream is;
                final MessageDigest md = MessageDigest.getInstance("MD5");

                public Comparator(InputStream is) throws Exception {
                    this.is = is;
                }

                @Override
                public void run() {
                    byte[] arr = new byte[1024];
                    int read = 0;
                    try {
                        while ((read = is.read(arr)) >= 0) {
                            md.update(arr, 0, read);
                        }
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            }

            Comparator comparatorIs1 = new Comparator(pis1);
            Future<?> f1 = executor.submit(comparatorIs1);
            Comparator comparatorIs2 = new Comparator(pis2);
            Future<?> f2 = executor.submit(comparatorIs2);

            Reader r1 = new InputStreamReader(is1);
            Reader r2 = new InputStreamReader(is2);

            char[] c1 = new char[1024];
            char[] c2 = new char[1024];

            int read1 = 0;
            int read2 = 0;

            boolean supposeEquals = true;

            while (supposeEquals) {
                read1 = r1.read(c1);
                read2 = r2.read(c2);
                if (read1 != read2 || (read1 < 0 && read2 < 0)) {
                    break;
                }
                for (int i = 0; i < read1; i++) {
                    if (c1[i] != c2[i]) {
                        supposeEquals = false;
                        break;
                    }
                }
            }

            f1.cancel(true);
            f2.cancel(true);

            return read1 == read2 && supposeEquals && Arrays.equals(comparatorIs1.md.digest(), comparatorIs2.md.digest());
        }

        public static void main(String[] args) throws Exception {
            System.out.println("Comparison result : " + compareStreams(new ByteArrayInputStream("test string here".getBytes()), new ByteArrayInputStream("test string here".getBytes())));
            System.out.println("Comparison result : " + compareStreams(new ByteArrayInputStream("test string test".getBytes()), new ByteArrayInputStream("test string here".getBytes())));
            System.out.println("Comparison result : " + compareStreams(new ByteArrayInputStream("test".getBytes()), new ByteArrayInputStream("test string here".getBytes())));
        }

    }

Upvotes: 1

Tim B
Tim B

Reputation: 41188

You can create two methods using overloading to mean that each takes one of the inputs you want to support.

Define an interface with the functionality you need to access from the two methods and then write wrapper classes implementing that interface (these can be anonymous inner classes in the corresponding methods).

Pass the wrapped input on to an internal private processing method that works on the interface and doesn't care what it may be wrapping.

This can be expanded to support any number of different types of incoming object (so long as it is possible to wrap them) just by adding a new method and a new wrapper for each one.

Upvotes: 1

Related Questions