nl.opengeogroep.filesetsync.FileRecord.java Source code

Java tutorial

Introduction

Here is the source code for nl.opengeogroep.filesetsync.FileRecord.java

Source

/*
 * Copyright (C) 2014 B3Partners B.V.
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

package nl.opengeogroep.filesetsync;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.io.Serializable;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.AndFileFilter;
import org.apache.commons.io.filefilter.CanReadFileFilter;
import org.apache.commons.io.filefilter.FileFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.commons.lang3.mutable.MutableInt;
import org.apache.commons.lang3.mutable.MutableLong;

/**
 *
 * @author Matthijs Laan
 */
public class FileRecord implements Serializable {
    private static final long serialVersionUID = 0L;

    public static final char TYPE_DIRECTORY = 'd';
    public static final char TYPE_FILE = 'f';
    public static final char TYPE_OTHER = 'o';

    private char type;

    /**
     * Path relative to the fileset root.
     */
    private String name;

    private long size;

    private long lastModified;

    private String hash;

    private transient File file;

    public FileRecord() {
    }

    public FileRecord(File f, String name) {
        this.file = f;
        if (f.isFile()) {
            type = TYPE_FILE;
            this.size = f.length();
        } else if (f.isDirectory()) {
            type = TYPE_DIRECTORY;
            this.size = 0;
        } else {
            // special or deleted file
            type = TYPE_OTHER;
        }

        this.name = name;
        this.lastModified = f.lastModified();
    }

    public static char typeOf(File file) {
        if (file.isFile()) {
            return TYPE_FILE;
        } else if (file.isDirectory()) {
            return TYPE_DIRECTORY;
        } else {
            return TYPE_OTHER;
        }
    }

    /**
     * Return an Iterable of FileRecords in this fileset recursing into
     * directories. The resulting FileRecords have no hash calculated.
     */
    public static Iterable<FileRecord> getFileRecordsInDir(final String path, final String regexp,
            final MutableInt noRegexpMatches) throws IOException {
        final File f = new File(path);
        if (f.isFile()) {
            return Arrays.asList(new FileRecord(f, "."));
        } else {
            return new Iterable<FileRecord>() {
                @Override
                public Iterator<FileRecord> iterator() {
                    return new FileRecordIterator(f, regexp, noRegexpMatches);
                }
            };
        }
    }

    public static class FileRecordIterator implements Iterator<FileRecord> {
        private final String rootPath;
        private final Iterator<File> it;
        private final String regexp;
        private final MutableInt noRegexpMatches;

        private FileRecord next;

        public FileRecordIterator(File startDir, String regexp, MutableInt noRegexpMatches) {
            this.rootPath = startDir.getAbsolutePath();
            this.regexp = regexp;
            this.noRegexpMatches = noRegexpMatches;
            it = FileUtils.iterateFilesAndDirs(startDir,
                    new AndFileFilter(FileFileFilter.FILE, CanReadFileFilter.CAN_READ), TrueFileFilter.INSTANCE);
        }

        @Override
        public FileRecord next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            FileRecord r = next;
            next = null;
            return r;
        }

        @Override
        public boolean hasNext() {
            if (next != null) {
                return true;
            }
            while (it.hasNext()) {
                File f = it.next();
                String absolutePath = f.getAbsolutePath();
                if (absolutePath.equals(rootPath)) {
                    // Root directory, set "." as relative name
                    next = new FileRecord(f, ".");
                    return true;
                } else {
                    String sub = absolutePath.substring(rootPath.length() + 1);
                    if (regexp == null || sub.matches(regexp)) {
                        next = new FileRecord(f, sub);
                        return true;
                    } else {
                        if (regexp != null) {
                            noRegexpMatches.increment();
                        }
                    }
                }
            }

            return false;
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }

    public void calculateHash() throws FileNotFoundException, IOException {
        calculateHash(null);
    }

    public void calculateHash(MutableLong hashTimeMillisAccumulator) throws FileNotFoundException, IOException {
        this.hash = calculateHash(file, hashTimeMillisAccumulator);
    }

    public static String calculateHash(File f, MutableLong hashTimeMillisAccumulator)
            throws FileNotFoundException, IOException {

        long startTime = hashTimeMillisAccumulator == null ? 0 : System.currentTimeMillis();

        // On Windows do not use memory mapped files, because the client may
        // want to overwrite a file it has just calculated the checksum of.
        // http://bugs.java.com/view_bug.do?bug_id=4724038

        // Performance difference is minimal or negative in some tests

        //String hash = SystemUtils.IS_OS_WINDOWS ? calculateHashNormalIO(f) : calculateHashMappedIO(f);
        String hash = calculateHashNormalIO(f);

        if (hashTimeMillisAccumulator != null) {
            hashTimeMillisAccumulator.add(System.currentTimeMillis() - startTime);
        }
        return hash;
    }

    public static String calculateHashNormalIO(File f) throws FileNotFoundException, IOException {
        try (InputStream in = new FileInputStream(f);) {
            return DigestUtils.md5Hex(in);
        }
    }

    public static String calculateHashMappedIO(File f) throws FileNotFoundException, IOException {
        try (RandomAccessFile raf = new RandomAccessFile(f, "r"); FileChannel channel = raf.getChannel();) {
            MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
            buffer.load();

            MessageDigest md = DigestUtils.getMd5Digest();
            md.update(buffer);
            byte[] digest = md.digest();

            return Hex.encodeHexString(digest);
        }
    }

    // <editor-fold defaultstate="collapsed" desc="getters and setters">
    public char getType() {
        return type;
    }

    public void setType(char type) {
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public long getSize() {
        return size;
    }

    public void setSize(long size) {
        this.size = size;
    }

    public long getLastModified() {
        return lastModified;
    }

    public void setLastModified(long lastModified) {
        this.lastModified = lastModified;
    }

    public String getHash() {
        return hash;
    }

    public void setHash(String hash) {
        this.hash = hash;
    }

    public File getFile() {
        return file;
    }

    public void setFile(File file) {
        this.file = file;
    }
    // </editor-fold>

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this, ToStringStyle.SIMPLE_STYLE);
    }
}