0

Using Java 1.8, I created a class which obtains a zip file from an external HTTP URL:

e.g.

https://raw.githubusercontent.com/mlampros/DataSets/master/fastText_data.zip

and converts it into a String based MD5 hash:

6aa2fe666f83953a089a2caa8b13b80e

My utility class:

public class HashUtils {
 
    public static String makeHashFromUrl(String fileUrl) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            InputStream is = new URL(fileUrl).openStream();

            try {
                is = new DigestInputStream(is, md);

                // Up to 8K per read
                byte[] ignoredBuffer = new byte[8 * 1024];

                while (is.read(ignoredBuffer) > 0) { }
            } finally {
                is.close();
            }
            byte[] digest = md.digest();
            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < digest.length; i++) {
                sb.append(Integer.toString((digest[i] & 0xff) + 0x100, 16).substring(1));
            }
            return sb.toString();

        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }
}

Whereas this is fine for an external URL containing zip file (or any file with any type of file extension), I need to be be able to use the same code (algorithm) for files that reside on a local filesystem.

e.g.

Inside $CATALINA_HOME/temp/fastText_data.zip

Would need to use this instead:

InputStream fis =  new FileInputStream(filename);

How could I do this using the same method (don't want to violate DRY - Don't Repeat Yourself)?

Of course, creating a brand new method containing the same code but using the InputStream fis = new FileInputStream(filename); instead of InputStream is = new URL(fileUrl).openStream(); would be violating the DRY principle?

What would be a good way to refactor this out? Two public methods with a refactored private method containing the same lines of code?

PacificNW_Lover
  • 4,746
  • 31
  • 90
  • 144
  • 1
    Write a `String makeHashFromStream(InputStream stream)` method. (That’s a roundabout way to convert bytes to their hex representation. Consider https://stackoverflow.com/questions/19450452/how-to-convert-byte-array-to-hex-format-in-java/19469285#19469285 instead.) – VGR Jul 24 '20 at 02:27
  • @VGR - But how to pass in both a fileURL or local file? Thanks for your link but that just shows how to make a hexString - which I don't necessarily need... I need to make an external file from a URL or a local file into a MD5 hash. Can you provide some code? Thanks for the suggestion. – PacificNW_Lover Jul 24 '20 at 02:42
  • 2
    In makeHashFromUrl, call makeHashFromStream(url.openStream()). Make yet another method like makeHashFromFile which calls makeHashFromStream(new FileInputStream(file)). Any InputStream is a valid argument to a method which expects an InputStream argument. – VGR Jul 24 '20 at 02:44
  • Yeah, but how would it know which method to step into for either case? Would would be the method parameters for these methods? – PacificNW_Lover Jul 24 '20 at 02:49

1 Answers1

3

Make three methods: A private method that expects an InputStream argument which is given to your current logic, and two very short public methods which each call the private method with an InputStream they create.

public static String makeHashFromUrl(String url) {
    try (InputStream stream = new URL(url).openStream()) {
        return makeHashFromStream(stream);
    }
}

public static String makeHashFromFile(File file) {
    try (InputStream stream = new BufferedInputStream(new FileInputStream(file))) {
        return makeHashFromStream(stream);
    }
}

private static String makeHashFromStream(InputStream is) {
    try {
        MessageDigest md = MessageDigest.getInstance("MD5");

        try {
            is = new DigestInputStream(is, md);

            // etc.
}
VGR
  • 40,506
  • 4
  • 48
  • 63