UnixFile.java

package emissary.util;

import emissary.core.Form;
import emissary.util.shell.Executrix;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;

public class UnixFile {

    private static final Logger log = LoggerFactory.getLogger(UnixFile.class);

    /** The magic number configuration file. The file which contains all magic number entries */
    private final List<File> magicFiles = new ArrayList<>();

    /** The Magic number helper class */
    private final MagicNumberUtil util = new MagicNumberUtil();

    /** The Binary file type description */
    public static final String FILETYPE_BINARY = "Binary File";

    /** The ASCII file type description */
    public static final String FILETYPE_ASCII = "ASCII File";

    /** The empty file type description */
    public static final String FILETYPE_EMPTY = Form.EMPTY;

    /**
     * Constructor to load instance using the specified File. If the specified file is invalid, an exception will be thrown
     * when attempting utilize the <code>execute</code> method.
     *
     * @param magicFile the <code>File</code> containing magic number entries
     */
    public UnixFile(File magicFile) throws IOException {
        new UnixFile(magicFile, false);
    }

    /**
     * Constructor to load instance using the specified File. If the specified file is invalid, an exception will be thrown
     * when attempting utilize the <code>execute</code> method.
     *
     * @param magicFile the <code>File</code> containing magic number entries
     * @param swallowParseException should we swallow Ignorable ParseException or bubble them up
     */
    public UnixFile(File magicFile, boolean swallowParseException) throws IOException {
        if (!magicFile.exists()) {
            throw new IllegalArgumentException("Magic file not found at: " + magicFile.getAbsolutePath());
        }

        this.magicFiles.add(magicFile);
        util.load(magicFile, swallowParseException);
    }

    /**
     * Load multiple magic files into one identification engine
     *
     * @param magicPaths the String names of magic files to load
     */
    public UnixFile(List<String> magicPaths) throws IOException {
        new UnixFile(magicPaths, false);
    }

    /**
     * Load multiple magic files into one identification engine
     * 
     * @param magicPaths the String names of magic files to load
     * @param swallowParseException should we swallow Ignorable ParseException or bubble them up
     */
    public UnixFile(List<String> magicPaths, boolean swallowParseException) throws IOException {
        for (String mPath : magicPaths) {
            File mFile = new File(mPath);
            if (!mFile.exists() || !mFile.canRead()) {
                throw new IllegalArgumentException("Magic file not found at " + mFile.getAbsolutePath());
            }
            this.magicFiles.add(mFile);
            util.load(mFile, swallowParseException);
        }
    }


    public int magicEntryCount() {
        return util.size();
    }

    /**
     * Behaves just like the UNIX file command. First performs a magic number test, then an ascii or binary file test. This
     * is also the same as calling <code>evaluateByMagicNumber (bytes :
     * byte[])</code> and then calling <code>evaluateBinaryProperty
     * (bytes : byte[])</code>
     */
    public String execute(@Nullable byte[] bytes) throws IOException {
        if (bytes == null || bytes.length < 1) {
            return FILETYPE_EMPTY;
        }

        String subjectFileType = evaluateByMagicNumber(bytes);

        if (subjectFileType != null) {
            return subjectFileType;
        } else {
            return evaluateBinaryProperty(bytes);
        }
    }

    /**
     * Statically tests a byte array to determine if the file representation can be of type ASCII or is binary. Simply
     * checks each byte value to be less then greater/equal then 127.
     */
    public static String evaluateBinaryProperty(@Nullable byte[] bytes) {
        if (bytes == null || bytes.length < 1) {
            return FILETYPE_EMPTY;
        }

        for (byte aByte : bytes) {
            if (aByte < 32) {
                return FILETYPE_BINARY;
            }
        }
        return FILETYPE_ASCII;
    }

    /**
     * Evaluates the byte array against the collection of Magic numbers
     */
    public String evaluateByMagicNumber(byte[] bytes) throws IOException {
        return util.describe(bytes);
    }

    /**
     * Test standalone main
     */
    @SuppressWarnings("SystemOut")
    public static void main(String[] args) throws Exception {
        if (args.length < 2) {
            log.info("Usage: UnixFile [-v] magicfile file1 [file2 ...]");
            return;
        }

        boolean verbose = false;

        int argIndex = 0;
        if (args[argIndex].equalsIgnoreCase("-v")) {
            verbose = true;
            argIndex++;
        }

        UnixFile unixfile = null;

        File magicNumberFile = new File(args[argIndex++]);
        if (!magicNumberFile.exists()) {
            System.err.println("Could not find magic file: " + magicNumberFile.getAbsolutePath());
            return;
        } else {
            unixfile = new UnixFile(magicNumberFile);
            if (verbose) {
                System.err.println("Using magic file at: " + magicNumberFile.getAbsolutePath());
            }
        }

        while (argIndex < args.length) {
            File target = new File(args[argIndex]);
            if (!target.exists()) {
                System.out.println(args[argIndex] + ": UNREADABLE");
            } else if (target.isDirectory()) {
                System.out.println(args[argIndex] + ": DIRECTORY");
            } else {
                byte[] data = Executrix.readDataFromFile(args[argIndex]);
                System.out.println(args[argIndex] + ": " + unixfile.evaluateByMagicNumber(data));
            }
            argIndex++;
        }

    }
}