jonelo.jacksum.concurrent.Jacksum2Cli.java Source code

Java tutorial

Introduction

Here is the source code for jonelo.jacksum.concurrent.Jacksum2Cli.java

Source

/*
 * Copyright (C) 2015 Federico Tello Gentile <federicotg@gmail.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package jonelo.jacksum.concurrent;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import jonelo.jacksum.algorithm.Algorithm;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;

/**
 *
 * @author Federico Tello Gentile <federicotg@gmail.com>
 */
public class Jacksum2Cli {

    private static final List<String> GENERIC_CRC_SPECS = new ArrayList<>();

    private static final int ERROR_STATUS = 2;
    private static final int OK = 0;

    public static void addCRCSpec(String spec) {
        GENERIC_CRC_SPECS.add(spec);
    }

    private PrintStream out = System.out;
    private PrintStream err = System.err;

    @Option(name = "-a", handler = AlgorithmHandler.class, metaVar = "algo")
    private List<Algorithm> algorithms;

    @Option(name = "-A")
    private boolean alternative = false;

    @Option(name = "-c", metaVar = "list")
    private File checkFile = null;

    @Option(name = "-d")
    private boolean ignoreSymbolicLinksToDirectories = false;

    @Option(name = "-e", metaVar = "seq")
    private String expectedHashValue = null;

    @Option(name = "-E", metaVar = "encoding", forbids = { "-x", "-X" })
    private Encoding encoding = Encoding.HEX;

    @Option(name = "-f")
    private boolean processFilesOnly = false;

    @Option(name = "-F", metaVar = "format", forbids = { "-m" })
    private String format = null;

    @Option(name = "-g", metaVar = "count")
    private int hexaGroupSize = -1;

    @Option(name = "-G", metaVar = "separatorChar", depends = { "-g" })
    private char hexaGroupSeparatorChar = ' ';

    @Option(name = "-h", forbids = { "-a", "-A", "-c", "-d", "-e", "-E", "-f", "-g", "-G", "-p", "-o", "-O", "-I",
            "-l", "-m", "-P", "-q", "-r", "-s", "-S", "-t", "-u", "-U", "-v", "-V", "-w", "-x", "-X" })
    private boolean help = false;

    @Option(name = "-p")
    private boolean fullPath = false;

    @Option(name = "-o", metaVar = "file", forbids = { "-O" })
    private String outputFile = null;

    @Option(name = "-O", metaVar = "file", forbids = { "-o" })
    private String overwriteOutputFile = null;

    @Option(name = "-I", metaVar = "string")
    private String prefixToIgnoreInFilenames = null;

    @Option(name = "-l", depends = { "-c" })
    private boolean onlyListModifiedFiles = false;

    @Option(name = "-m", forbids = { "-F" })
    private boolean printMetainfo;

    @Option(name = "-P", metaVar = "char")
    private char separator = File.separatorChar;

    @Option(name = "-q")
    private String quickSequence;

    @Option(name = "-r")
    private boolean recursive;

    @Option(name = "-s", metaVar = "sep")
    private String customSeparator = " ";

    @Option(name = "-S")
    private boolean summary;

    @Option(name = "-t", metaVar = "form")
    private String dateFormat = null;

    @Option(name = "-u", metaVar = "file", forbids = { "-U" })
    private String errorFileName;

    @Option(name = "-U", metaVar = "file", forbids = { "-u" })
    private String overwriteErrorFileName;

    @Option(name = "-v")
    private boolean showVersion;

    @Option(name = "-V", metaVar = "control")
    private String verbosity;

    @Option(name = "-w")
    private boolean fileParamIsDirAndWorkingDirectory;

    @Option(name = "-x", forbids = { "-E", "-X" })
    private boolean lowerHexaFormat = false;

    @Option(name = "-X", forbids = { "-E", "-x" })
    private boolean upperHexaFormat = false;

    @Argument
    private List<String> filenames = new ArrayList<>();

    public static void main(String[] args) {
        Jacksum2Cli app = new Jacksum2Cli();
        try {
            app.initArgs(args);
        } catch (CmdLineException ex) {
            System.err.println(ex.getMessage());
            System.exit(ERROR_STATUS);
        }

        int status = app.printResults();
        System.exit(status);

    }

    public void initArgs(String[] args) throws CmdLineException {

        CmdLineParser parser = new CmdLineParser(this);

        // parse the arguments.
        parser.parseArgument(args);
        // you can parse additional arguments if you want.
        // parser.parseArgument("more","args");
        // after parsing arguments, you should check
        // if enough arguments are given.

    }

    private void buildQuickReport(JacksumReport report, HashFormat simpleFormat) {
        final byte[] bytes = QuickSequenceType.decodeQuickSequence(this.quickSequence);

        final Map<Algorithm, byte[]> results = new ConcurrentHasher().hashBytes(bytes, this.algorithms,
                this.alternative, GENERIC_CRC_SPECS);

        report.addLine(
                simpleFormat.format(algorithms,
                        this.algorithms.stream().map(algo -> results.get(algo)).collect(Collectors.toList())),
                bytes.length);
    }

    private void buildFilesReport(JacksumReport report, HashFormat simpleFormat)
            throws IOException, NoSuchAlgorithmException, InterruptedException, ExecutionException {
        final List<Path> allFiles = new ArrayList<>();
        final Map<Path, Long> fileSizes = new ConcurrentHashMap<>();
        final Map<Path, Long> fileLastModified = new ConcurrentHashMap<>();
        this.loadFilesToHash(allFiles, fileSizes, fileLastModified);
        final Map<Pair<Path, Algorithm>, byte[]> results = new ConcurrentHasher().hashFiles(allFiles,
                this.algorithms, this.alternative, GENERIC_CRC_SPECS);

        for (Path fn : allFiles) {
            report.addLine(fn.toString(),
                    simpleFormat.format(algorithms, this.algorithms.stream()
                            .map(algo -> results.get(new Pair<>(fn, algo))).collect(Collectors.toList())),
                    fileSizes.get(fn));

        }
    }

    public int printResults() {
        try {

            if (this.isHelp()) {
                this.printHelp();
                return OK;
            }

            this.initOutput();

            if (this.isPrintMetainfo()) {

                final HashFormat simpleFormat = new SimpleHashFormat(this.getEncoding(), this.getHexaGroupSize(),
                        this.getHexaGroupSeparatorChar(), null);

                final ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);

                final JacksumReport report = new JacksumReport();
                report.setAlgorithms(
                        this.algorithms.stream().map(algo -> algo.getCanonicalName()).collect(Collectors.toList()));
                report.setAlternative(this.alternative);
                report.setEncoding(this.getEncoding().getValue());
                report.setHexaGroupSeparatorChar(this.hexaGroupSeparatorChar);
                report.setHexaGroupSize(this.hexaGroupSize);
                report.setPathSeparator(this.separator);

                if (this.quickSequence != null) {

                    this.buildQuickReport(report, simpleFormat);

                    mapper.writeValue(this.out, report);

                    return OK;
                }
                //process files
                this.buildFilesReport(report, simpleFormat);

                mapper.enable(SerializationFeature.INDENT_OUTPUT).writeValue(this.out, report);

                return OK;

            }

            if (this.quickSequence != null) {
                this.out.println(this.getFormattedQuickHash());
                return OK;
            }

            for (String resultString : this.getFormattedFileHashes()) {
                this.out.println(resultString);
            }

            return OK;

        } catch (Throwable ex) {
            this.printError(ex.getMessage());
            return ERROR_STATUS;
        }
    }

    public String getFormattedQuickHash() {
        final byte[] bytes = QuickSequenceType.decodeQuickSequence(this.quickSequence);
        final Map<Algorithm, byte[]> results = new ConcurrentHasher().hashBytes(bytes, this.algorithms,
                this.alternative, GENERIC_CRC_SPECS);

        return this.getHashFormat().format(this.algorithms,
                this.algorithms.stream().map(algo -> results.get(algo)).collect(Collectors.toList()), bytes.length);
    }

    private void loadFilesToHash(List<Path> allFiles, Map<Path, Long> fileSizes, Map<Path, Long> fileLastModified)
            throws IOException {

        final int maxDepth = this.recursive ? Integer.MAX_VALUE : 1;
        final FileSystem fs = FileSystems.getDefault();
        final FileVisitOption[] options = this.ignoreSymbolicLinksToDirectories ? new FileVisitOption[0]
                : new FileVisitOption[] { FileVisitOption.FOLLOW_LINKS };

        for (String filename : this.filenames) {
            Files.walk(fs.getPath(filename), maxDepth, options).filter(path -> Files.isRegularFile(path))
                    .forEach(path -> {
                        allFiles.add(path);
                        try {
                            fileSizes.put(path, Files.size(path));
                            fileLastModified.put(path, Files.getLastModifiedTime(path).toMillis());
                        } catch (IOException ioEx) {
                            fileSizes.put(path, -1l);
                            fileLastModified.put(path, 0l);
                        }
                    });
        }

    }

    public List<String> getFormattedFileHashes()
            throws IOException, NoSuchAlgorithmException, InterruptedException, ExecutionException {
        final List<Path> allFiles = new ArrayList<>();
        final Map<Path, Long> fileSizes = new ConcurrentHashMap<>();
        final Map<Path, Long> fileLastModified = new ConcurrentHashMap<>();

        this.loadFilesToHash(allFiles, fileSizes, fileLastModified);
        final Map<Pair<Path, Algorithm>, byte[]> results = new ConcurrentHasher().hashFiles(allFiles,
                this.algorithms, this.alternative, GENERIC_CRC_SPECS);

        final HashFormat hashFormat = this.getHashFormat();

        return allFiles.stream()
                .map(filename -> hashFormat.format(this.algorithms,
                        this.algorithms.stream().map(algo -> results.get(new Pair<>(filename, algo)))
                                .collect(Collectors.toList()),
                        filename.toFile().getAbsolutePath(), fileSizes.get(filename),
                        fileLastModified.get(filename)))
                .collect(Collectors.toList());
    }

    public List<Algorithm> getAlgorithms() {
        return algorithms;
    }

    public boolean isAlternate() {
        return alternative;
    }

    public File getCheckFile() {
        return checkFile;
    }

    public boolean isIgnoreSymbolicLinksToDirectories() {
        return ignoreSymbolicLinksToDirectories;
    }

    public String getExpectedHashValue() {
        return expectedHashValue;
    }

    public Encoding getEncoding() {
        if (this.lowerHexaFormat) {
            return Encoding.HEX;
        }
        if (this.upperHexaFormat) {
            return Encoding.HEXUP;
        }
        return encoding;
    }

    public boolean isProcessFilesOnly() {
        return processFilesOnly;
    }

    public String getFormat() {
        return format;
    }

    public int getHexaGroupSize() {
        return hexaGroupSize;
    }

    public char getHexaGroupSeparatorChar() {
        return hexaGroupSeparatorChar;
    }

    public boolean isHelp() {
        return help;
    }

    public boolean isFullPath() {
        return fullPath;
    }

    public String getOutputFile() {
        return outputFile;
    }

    public String getOverwriteOutputFile() {
        return overwriteOutputFile;
    }

    public String getPrefixToIgnoreInFilenames() {
        return prefixToIgnoreInFilenames;
    }

    public boolean isOnlyListModifiedFiles() {
        return onlyListModifiedFiles;
    }

    public boolean isPrintMetainfo() {
        return printMetainfo;
    }

    public char getSeparator() {
        return separator;
    }

    public String getQuickSequence() {
        return quickSequence;
    }

    public boolean isRecursive() {
        return recursive;
    }

    public String getCustomSeparator() {
        return customSeparator;
    }

    public boolean isSummary() {
        return summary;
    }

    public String getDateFormat() {
        return dateFormat;
    }

    public String getErrorFileName() {
        return errorFileName;
    }

    public String getOverwriteErrorFileName() {
        return overwriteErrorFileName;
    }

    public boolean isShowVersion() {
        return showVersion;
    }

    public String getVerbosity() {
        return verbosity;
    }

    public boolean isFileParamIsDirAndWorkingDirectory() {
        return fileParamIsDirAndWorkingDirectory;
    }

    public boolean isLowerHexaFormat() {
        return lowerHexaFormat;
    }

    public boolean isUpperHexaFormat() {
        return upperHexaFormat;
    }

    public List<String> getFilenames() {
        return filenames;
    }

    private void printHelp() throws IOException {
        try (InputStream helpTextStream = Jacksum2Cli.class
                .getResourceAsStream(ResourceBundle.getBundle("resources").getString("help_file"))) {
            byte[] buffer = new byte[8192];
            int read;
            while ((read = helpTextStream.read(buffer)) > 0) {
                this.out.write(buffer, 0, read);
            }
        }
    }

    public void setOut(PrintStream out) {
        this.out = out;
    }

    private void printError(String errorMessage) {
        this.err.println(errorMessage);
    }

    private HashFormat getHashFormat() {
        if (this.format == null) {
            return new SimpleHashFormat(this.getEncoding(), hexaGroupSize, hexaGroupSeparatorChar, dateFormat);
        }
        return new CustomHashFormat(format, this.getEncoding(), hexaGroupSize, hexaGroupSeparatorChar,
                customSeparator, dateFormat);
    }

    private void initOutput() throws IOException {

        if (this.outputFile != null) {
            File outfile = new File(this.outputFile);
            if (!outfile.exists()) {
                this.setOut(new PrintStream(new BufferedOutputStream(new FileOutputStream(outfile))));
            } else {
                throw new IOException(
                        "The file " + this.outputFile + " already exists. Specify the file by -O to overwrite it.");
            }
        } else if (this.overwriteOutputFile != null) {
            this.setOut(new PrintStream(
                    new BufferedOutputStream(new FileOutputStream(new File(this.overwriteOutputFile)))));
        }

        if (this.errorFileName != null) {
            File outfile = new File(this.errorFileName);
            if (!outfile.exists()) {
                this.err = new PrintStream(new BufferedOutputStream(new FileOutputStream(outfile)));
            } else {
                throw new IOException("The file " + this.errorFileName
                        + " already exists. Specify the file by -U to overwrite it.");
            }
        } else if (this.overwriteErrorFileName != null) {
            this.err = new PrintStream(
                    new BufferedOutputStream(new FileOutputStream(new File(this.overwriteErrorFileName))));
        }

    }
}