de.fosd.jdime.common.FileArtifact.java Source code

Java tutorial

Introduction

Here is the source code for de.fosd.jdime.common.FileArtifact.java

Source

/*
 * Copyright (C) 2013-2014 Olaf Lessenich
 * Copyright (C) 2014-2015 University of Passau, Germany
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301  USA
 *
 * Contributors:
 *     Olaf Lessenich <lessenic@fim.uni-passau.de>
 *     Georg Seibt <seibt@fim.uni-passau.de>
 */
package de.fosd.jdime.common;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import javax.activation.MimetypesFileTypeMap;

import de.fosd.jdime.common.operations.MergeOperation;
import de.fosd.jdime.matcher.Color;
import de.fosd.jdime.matcher.Matching;
import de.fosd.jdime.strategy.DirectoryStrategy;
import de.fosd.jdime.strategy.MergeStrategy;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.log4j.Logger;

/**
 * This class represents an artifact of a program.
 *
 * @author Olaf Lessenich
 */
public class FileArtifact extends Artifact<FileArtifact> {

    private static final Logger LOG = Logger.getLogger(ClassUtils.getShortClassName(FileArtifact.class));

    /**
     * The expected MIME content type for java source files.
     */
    private static final String MIME_JAVA_SOURCE = "text/x-java";

    /**
     * Used for determining the content type of this <code>FileArtifact</code> if
     * {@link Files#probeContentType(java.nio.file.Path)} fails.
     */
    private static final MimetypesFileTypeMap mimeMap;

    static {
        mimeMap = new MimetypesFileTypeMap();
        mimeMap.addMimeTypes(MIME_JAVA_SOURCE + " java");
    }

    /**
     * File in which the artifact is stored.
     */
    private File file;

    /**
     * Constructs a new <code>FileArtifact</code> contained in the given <code>File</code>.
     * The newly constructed <code>FileArtifact</code> will not belong to a revision.
     *
     * @param file
     *       the <code>File</code> in which the artifact is stored
     *
     * @throws FileNotFoundException
     *       if <code>file</code> does not exist according to {@link java.io.File#exists()}
     */
    public FileArtifact(File file) throws FileNotFoundException {
        this(null, file);
    }

    /**
     * Constructs a new <code>FileArtifact</code> contained in the given <code>File</code>.
     *
     * @param revision
     *       the <code>Revision</code> the artifact belongs to
     * @param file
     *       the <code>File</code> in which the artifact is stored
     *
     * @throws FileNotFoundException
     *       if <code>file</code> does not exist according to {@link java.io.File#exists()}
     */
    public FileArtifact(Revision revision, File file) throws FileNotFoundException {
        this(revision, file, true);
    }

    /**
     * Constructs a new <code>FileArtifact</code> contained in the given <code>File</code>.
     *
     * @param revision
     *       the <code>Revision</code> the artifact belongs to
     * @param file
     *       the <code>File</code> in which the artifact is stored
     * @param checkExistence
     *       whether to ensure that <code>file</code> exists
     *
     * @throws FileNotFoundException
     *       if <code>checkExistence</code> is <code>true</code> and <code>file</code> does not exist according to {@link
     *       java.io.File#exists()}
     */
    public FileArtifact(Revision revision, File file, boolean checkExistence) throws FileNotFoundException {
        assert file != null;

        if (checkExistence && !file.exists()) {
            LOG.fatal("File not found: " + file.getAbsolutePath());
            throw new FileNotFoundException();
        }

        setRevision(revision);
        this.file = file;

        if (LOG.isTraceEnabled()) {
            LOG.trace("Artifact initialized: " + file.getPath());
            LOG.trace("Artifact exists: " + exists());
            LOG.trace("File exists: " + file.exists());

            if (exists()) {
                LOG.trace("Artifact isEmpty: " + isEmpty());
            }
        }
    }

    @Override
    public final FileArtifact addChild(final FileArtifact child) throws IOException {
        assert (child != null);

        assert (!isLeaf()) : "Child elements can not be added to leaf artifacts. " + "isLeaf(" + this + ") = "
                + isLeaf();

        assert (getClass().equals(child.getClass())) : "Can only add children of same type";

        FileArtifact myChild = new FileArtifact(getRevision(), new File(file + File.separator + child), false);

        return myChild;
    }

    @Override
    public final int compareTo(final FileArtifact o) {
        if (o == this) {
            return 0;
        }

        return this.toString().compareTo(o.toString());
    }

    @Override
    public final void copyArtifact(final FileArtifact destination) throws IOException {
        assert (destination != null);

        if (destination.isFile()) {
            if (isFile()) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Copying file " + this + " to file " + destination);
                    LOG.debug("Destination already exists overwriting: " + destination.exists());
                }

                FileUtils.copyFile(file, destination.file);
            } else {
                throw new UnsupportedOperationException(
                        "When copying to a file, " + "the source must also be a file.");
            }
        } else if (destination.isDirectory()) {
            if (isFile()) {
                assert (destination.exists()) : "Destination directory does not exist: " + destination;
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Copying file " + this + " to directory " + destination);
                }
                FileUtils.copyFileToDirectory(file, destination.file);
            } else if (isDirectory()) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Copying directory " + this + " to directory " + destination);
                    LOG.debug("Destination already exists overwriting: " + destination.exists());
                }
                FileUtils.copyDirectory(file, destination.file);
            }
        } else {
            LOG.fatal("Failed copying " + this + " to " + destination);
            LOG.fatal("isDirectory(" + this + ") = " + isDirectory());
            LOG.fatal("isDirectory(" + destination + ") = " + destination.isDirectory());
            throw new NotYetImplementedException("Only copying files and directories is supported.");
        }
    }

    @Override
    public final void createArtifact(final boolean isLeaf) throws IOException {

        // assert (!artifact.exists() || Main.isForceOverwriting())
        // : "File would be overwritten: " + artifact;
        //
        // if (artifact.exists()) {
        // Artifact.remove(artifact);
        // }
        assert (!exists()) : "File would be overwritten: " + this;

        if (file.getParentFile() != null) {
            boolean createdParents = file.getParentFile().mkdirs();

            if (LOG.isTraceEnabled()) {
                LOG.trace("Had to create parent directories: " + createdParents);
            }
        }

        if (isLeaf) {
            file.createNewFile();
            if (LOG.isTraceEnabled()) {
                LOG.trace("Created file" + file);
            }
        } else {
            file.mkdir();
            if (LOG.isTraceEnabled()) {
                LOG.trace("Created directory " + file);
            }

        }

        assert (exists());
    }

    @Override
    public final FileArtifact createEmptyDummy() throws FileNotFoundException {
        File dummyFile;

        try {
            dummyFile = Files.createTempFile(null, null).toFile();
            dummyFile.deleteOnExit();
        } catch (IOException e) {
            throw new FileNotFoundException(e.getMessage());
        }

        FileArtifact dummyArtifact = new FileArtifact(dummyFile);
        dummyArtifact.setEmptyDummy(true);

        LOG.trace("Artifact is a dummy artifact. Using temporary file " + dummyFile.getAbsolutePath());

        return dummyArtifact;
    }

    @Override
    protected final String dumpTree(final String indent) {
        StringBuilder sb = new StringBuilder();

        Matching<FileArtifact> m = null;
        if (hasMatches()) {
            Set<Revision> matchingRevisions = matches.keySet();

            // print color code
            String color = "";

            for (Revision rev : matchingRevisions) {
                m = getMatching(rev);
                color = m.getColor().toShell();
            }

            sb.append(color);
        }

        sb.append(indent).append("(").append(getId()).append(") ");
        sb.append(this);

        if (hasMatches()) {
            assert (m != null);
            sb.append(" <=> (").append(m.getMatchingArtifact(this)).append(")");
            sb.append(Color.DEFAULT.toShell());
        }
        sb.append(System.lineSeparator());

        if (!isLeaf()) {
            // children
            for (FileArtifact child : getChildren()) {
                sb.append(child.dumpTree(indent + "  "));
            }
        }

        return sb.toString();
    }

    @Override
    public final boolean equals(final Object obj) {
        assert (obj != null);
        assert (obj instanceof FileArtifact);
        if (this == obj) {
            return true;
        }
        return this.toString().equals(((FileArtifact) obj).toString());
    }

    @Override
    public final boolean exists() {
        assert (file != null);
        return file.exists();
    }

    /**
     * Returns the MIME content type of the <code>File</code> in which this <code>FileArtifact</code> is stored. 
     * If the content type can not be determined <code>null</code> will be returned.
     *
     * @return the MIME content type 
     *
     * @throws IOException
     *         if an I/O exception occurs while trying to determine the content type
     */
    public final String getContentType() throws IOException {
        assert (exists());

        String mimeType = Files.probeContentType(file.toPath());

        if (mimeType == null) {

            // returns application/octet-stream if the type can not be determined
            mimeType = mimeMap.getContentType(file);

            if ("application/octet-stream".equals(mimeType)) {
                mimeType = null;
            }
        }

        return mimeType;
    }

    /**
     * Returns the list of artifacts contained in this directory.
     *
     * @return list of artifacts contained in this directory
     */
    public final ArtifactList<FileArtifact> getDirContent() {
        assert (isDirectory());

        ArtifactList<FileArtifact> contentArtifacts = new ArtifactList<>();
        File[] content = file.listFiles();

        for (int i = 0; i < content.length; i++) {
            try {
                FileArtifact child = new FileArtifact(getRevision(), content[i]);
                child.setParent(this);
                contentArtifacts.add(child);
            } catch (FileNotFoundException e) {
                LOG.fatal(e);
            }
        }

        return contentArtifacts;
    }

    /**
     * Returns the encapsulated file.
     *
     * @return file
     */
    public final File getFile() {
        return file;
    }

    public ArtifactList<FileArtifact> getJavaFiles() throws IOException {
        ArtifactList<FileArtifact> javaFiles = new ArtifactList<>();

        if (isFile() && MIME_JAVA_SOURCE.equals(getContentType())) {
            javaFiles.add(this);
        } else if (isDirectory()) {

            for (FileArtifact child : getDirContent()) {
                javaFiles.addAll(child.getJavaFiles());
            }
        }

        return javaFiles;
    }

    /**
     * Returns the absolute path of this artifact.
     *
     * @return absolute part of the artifact
     */
    public final String getFullPath() {
        assert (file != null);
        return file.getAbsolutePath();
    }

    /*
     * (non-Javadoc)
     * 
     * @see de.fosd.jdime.common.Artifact#getId()
     */
    @Override
    public final String getId() {
        return getPath();
    }

    /**
     * Returns the path of this artifact.
     *
     * @return path of the artifact
     */
    public final String getPath() {
        assert (file != null);
        return file.getPath();
    }

    /**
     * Returns a reader that can be used to retrieve the content of the
     * artifact.
     *
     * @return Reader
     * @throws FileNotFoundException
     *             If the artifact is a file which is not found
     */
    public final BufferedReader getReader() throws FileNotFoundException {
        if (isFile()) {
            return new BufferedReader(new FileReader(file));
        } else {
            throw new NotYetImplementedException();
        }
    }

    /**
     * Returns the list of (relative) filenames contained in this directory.
     *
     * @return list of relative filenames
     */
    public final List<String> getRelativeDirContent() {
        assert (isDirectory());
        return Arrays.asList(file.list());
    }

    @Override
    public final String getStatsKey(final MergeContext context) {
        assert (context != null);

        // MergeStrategy<FileArtifact> strategy
        // = (MergeStrategy<FileArtifact>) (isDirectory()
        // ? new DirectoryStrategy() : context.getMergeStrategy());
        // assert (strategy != null);
        //
        // return strategy.getStatsKey(this);
        return isDirectory() ? "directories" : "files";
    }

    /*
     * (non-Javadoc)
     * 
     * @see de.fosd.jdime.common.Artifact#hashCode()
     */
    @Override
    public final int hashCode() {
        return toString().hashCode();
    }

    @Override
    public final boolean hasUniqueLabels() {
        return true;
    }

    /*
     * (non-Javadoc)
     * 
     * @see de.fosd.jdime.common.Artifact#initializeChildren()
     */
    @Override
    public final void initializeChildren() {
        assert (exists());

        if (isDirectory()) {
            setChildren(getDirContent());
        } else {
            setChildren(null);
        }
    }

    /**
     * Returns true if artifact is a directory.
     *
     * @return true if artifact is a directory
     */
    public final boolean isDirectory() {
        return file.isDirectory();
    }

    /**
     * Returns true if the artifact is empty.
     *
     * @return true if the artifact is empty
     */
    @Override
    public final boolean isEmpty() {
        assert (exists());
        if (isDirectory()) {
            return file.listFiles().length == 0;
        } else {
            return FileUtils.sizeOf(file) == 0;
        }
    }

    /**
     * Returns true if artifact is a normal file.
     *
     * @return true if artifact is a normal file
     */
    public final boolean isFile() {
        return file.isFile();
    }

    @Override
    public final boolean isLeaf() {
        return !file.isDirectory();
    }

    @Override
    public final boolean isOrdered() {
        return false;
    }

    @Override
    public final boolean matches(final FileArtifact other) {
        if (isDirectory() && isRoot() && other.isDirectory() && other.isRoot()) {
            if (LOG.isDebugEnabled()) {
                LOG.debug(this + " and " + other + " are toplevel directories.");
                LOG.debug("We assume a match here and continue to merge the " + "contained files and directories.");
            }

            return true;
        }
        return this.equals(other);
    }

    @Override
    public final void merge(MergeOperation<FileArtifact> operation, MergeContext context)
            throws IOException, InterruptedException {
        Objects.requireNonNull(operation, "operation must not be null!");
        Objects.requireNonNull(context, "context must not be null!");

        if (!exists()) {
            String className = getClass().getSimpleName();
            String filePath = file.getAbsolutePath();
            String message = String.format("Trying to merge %s whose file %s does not exist.", className, filePath);

            throw new RuntimeException(message);
        }

        @SuppressWarnings("unchecked")
        MergeStrategy<FileArtifact> strategy = (MergeStrategy<FileArtifact>) context.getMergeStrategy();

        if (isDirectory()) {
            strategy = new DirectoryStrategy();
        } else {
            String contentType = getContentType();

            if (LOG.isTraceEnabled()) {
                LOG.trace(getId() + " (" + this + ") has content type: " + contentType);
            }

            if (!MIME_JAVA_SOURCE.equals(contentType)) {
                LOG.debug("Skipping non-java file.");
                return;
            }
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("Using strategy: " + strategy);
        }
        LOG.info(this);

        strategy.merge(operation, context);

        if (!context.isQuiet() && context.hasOutput()) {
            System.out.print(context.getStdIn());
        }
        context.resetStreams();
    }

    /**
     * Removes the artifact's file.
     *
     * @throws IOException
     *             If an input output exception occurs
     */
    public final void remove() throws IOException {
        assert (exists() && !isEmptyDummy()) : "Tried to remove non-existing file: " + getFullPath();

        if (isDirectory()) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Deleting directory recursively: " + file);
            }
            FileUtils.deleteDirectory(file);
        } else if (isFile()) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Deleting file: " + file);
            }
            file.delete();
        } else {
            throw new UnsupportedOperationException("Only files and directories can be removed at the moment");
        }

        assert (!exists());
    }

    @Override
    public final String toString() {
        assert (file != null);
        return file.getName();
    }

    /**
     * Writes from a BufferedReader to the artifact.
     *
     * @param str
     *            String to write
     * @throws IOException
     *             If an input output exception occurs.
     */
    @Override
    public final void write(final String str) throws IOException {
        assert (file != null);
        assert (str != null);
        try (FileWriter writer = new FileWriter(file)) {
            writer.write(str);
        }
    }

    @Override
    public final FileArtifact createConflictDummy(final FileArtifact type, final FileArtifact left,
            final FileArtifact right) throws FileNotFoundException {
        throw new NotYetImplementedException();
    }

    public final String getContent() throws IOException {
        return file == null ? "" : FileUtils.readFileToString(file);
    }
}