org.eclipse.jgit.treewalk.FileTreeIterator.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jgit.treewalk.FileTreeIterator.java

Source

/*
 * Copyright (C) 2008, Google Inc.
 * Copyright (C) 2007-2010, Robin Rosenberg <robin.rosenberg@dewire.com>
 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
 * Copyright (C) 2009, Tor Arne Vestb <torarnv@gmail.com>
 * and other copyright owners as documented in the project's IP log.
 *
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Distribution License v1.0 which
 * accompanies this distribution, is reproduced below, and is
 * available at http://www.eclipse.org/org/documents/edl-v10.php
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials provided
 *   with the distribution.
 *
 * - Neither the name of the Eclipse Foundation, Inc. nor the
 *   names of its contributors may be used to endorse or promote
 *   products derived from this software without specific prior
 *   written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.eclipse.jgit.treewalk;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.Instant;

import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.FS;

/**
 * Working directory iterator for standard Java IO.
 * <p>
 * This iterator uses the standard <code>java.io</code> package to read the
 * specified working directory as part of a
 * {@link org.eclipse.jgit.treewalk.TreeWalk}.
 */
public class FileTreeIterator extends WorkingTreeIterator {

    /**
     * the starting directory of this Iterator. All entries are located directly
     * in this directory.
     */
    protected final File directory;

    /**
     * the file system abstraction which will be necessary to perform certain
     * file system operations.
     */
    protected final FS fs;

    /**
     * the strategy used to compute the FileMode for a FileEntry. Can be used to
     * control things such as whether to recurse into a directory or create a
     * gitlink.
     *
     * @since 4.3
     */
    protected final FileModeStrategy fileModeStrategy;

    /**
     * Create a new iterator to traverse the work tree and its children.
     *
     * @param repo
     *            the repository whose working tree will be scanned.
     */
    public FileTreeIterator(Repository repo) {
        this(repo, repo.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks() ? NoGitlinksStrategy.INSTANCE
                : DefaultFileModeStrategy.INSTANCE);
    }

    /**
     * Create a new iterator to traverse the work tree and its children.
     *
     * @param repo
     *            the repository whose working tree will be scanned.
     * @param fileModeStrategy
     *            the strategy to use to determine the FileMode for a FileEntry;
     *            controls gitlinks etc.
     * @since 4.3
     */
    public FileTreeIterator(Repository repo, FileModeStrategy fileModeStrategy) {
        this(repo.getWorkTree(), repo.getFS(), repo.getConfig().get(WorkingTreeOptions.KEY), fileModeStrategy);
        initRootIterator(repo);
    }

    /**
     * Create a new iterator to traverse the given directory and its children.
     *
     * @param root
     *            the starting directory. This directory should correspond to
     *            the root of the repository.
     * @param fs
     *            the file system abstraction which will be necessary to perform
     *            certain file system operations.
     * @param options
     *            working tree options to be used
     */
    public FileTreeIterator(File root, FS fs, WorkingTreeOptions options) {
        this(root, fs, options, DefaultFileModeStrategy.INSTANCE);
    }

    /**
     * Create a new iterator to traverse the given directory and its children.
     *
     * @param root
     *            the starting directory. This directory should correspond to
     *            the root of the repository.
     * @param fs
     *            the file system abstraction which will be necessary to perform
     *            certain file system operations.
     * @param options
     *            working tree options to be used
     * @param fileModeStrategy
     *            the strategy to use to determine the FileMode for a FileEntry;
     *            controls gitlinks etc.
     * @since 4.3
     */
    public FileTreeIterator(final File root, FS fs, WorkingTreeOptions options, FileModeStrategy fileModeStrategy) {
        super(options);
        directory = root;
        this.fs = fs;
        this.fileModeStrategy = fileModeStrategy;
        init(entries());
    }

    /**
     * Create a new iterator to traverse a subdirectory.
     *
     * @param p
     *            the parent iterator we were created from.
     * @param root
     *            the subdirectory. This should be a directory contained within
     *            the parent directory.
     * @param fs
     *            the file system abstraction which will be necessary to perform
     *            certain file system operations.
     * @since 4.3
     */
    protected FileTreeIterator(final FileTreeIterator p, final File root, FS fs) {
        this(p, root, fs, p.fileModeStrategy);
    }

    /**
     * Create a new iterator to traverse a subdirectory, given the specified
     * FileModeStrategy.
     *
     * @param p
     *            the parent iterator we were created from.
     * @param root
     *            the subdirectory. This should be a directory contained within
     *            the parent directory
     * @param fs
     *            the file system abstraction which will be necessary to perform
     *            certain file system operations.
     * @param fileModeStrategy
     *            the strategy to use to determine the FileMode for a given
     *            FileEntry.
     * @since 4.3
     */
    protected FileTreeIterator(final WorkingTreeIterator p, final File root, FS fs,
            FileModeStrategy fileModeStrategy) {
        super(p);
        directory = root;
        this.fs = fs;
        this.fileModeStrategy = fileModeStrategy;
        init(entries());
    }

    /** {@inheritDoc} */
    @Override
    public AbstractTreeIterator createSubtreeIterator(ObjectReader reader)
            throws IncorrectObjectTypeException, IOException {
        if (!walksIgnoredDirectories() && isEntryIgnored()) {
            DirCacheIterator iterator = getDirCacheIterator();
            if (iterator == null) {
                return new EmptyTreeIterator(this);
            }
            // Only enter if we have an associated DirCacheIterator that is
            // at the same entry (which indicates there is some already
            // tracked file underneath this directory). Otherwise the
            // directory is indeed ignored and can be skipped entirely.
        }
        return enterSubtree();
    }

    /**
     * Create a new iterator for the current entry's subtree.
     * <p>
     * The parent reference of the iterator must be <code>this</code>, otherwise
     * the caller would not be able to exit out of the subtree iterator
     * correctly and return to continue walking <code>this</code>.
     *
     * @return a new iterator that walks over the current subtree.
     * @since 5.0
     */
    protected AbstractTreeIterator enterSubtree() {
        return new FileTreeIterator(this, ((FileEntry) current()).getFile(), fs, fileModeStrategy);
    }

    private Entry[] entries() {
        return fs.list(directory, fileModeStrategy);
    }

    /**
     * An interface representing the methods used to determine the FileMode for
     * a FileEntry.
     *
     * @since 4.3
     */
    public interface FileModeStrategy {
        /**
         * Compute the FileMode for a given File, based on its attributes.
         *
         * @param f
         *            the file to return a FileMode for
         * @param attributes
         *            the attributes of a file
         * @return a FileMode indicating whether the file is a regular file, a
         *         directory, a gitlink, etc.
         */
        FileMode getMode(File f, FS.Attributes attributes);
    }

    /**
     * A default implementation of a FileModeStrategy; defaults to treating
     * nested .git directories as gitlinks, etc.
     *
     * @since 4.3
     */
    public static class DefaultFileModeStrategy implements FileModeStrategy {
        /**
         * a singleton instance of the default FileModeStrategy
         */
        public final static DefaultFileModeStrategy INSTANCE = new DefaultFileModeStrategy();

        @Override
        public FileMode getMode(File f, FS.Attributes attributes) {
            if (attributes.isSymbolicLink()) {
                return FileMode.SYMLINK;
            } else if (attributes.isDirectory()) {
                if (new File(f, Constants.DOT_GIT).exists()) {
                    return FileMode.GITLINK;
                }
                return FileMode.TREE;
            } else if (attributes.isExecutable()) {
                return FileMode.EXECUTABLE_FILE;
            } else {
                return FileMode.REGULAR_FILE;
            }
        }
    }

    /**
     * A FileModeStrategy that implements native git's DIR_NO_GITLINKS
     * behavior. This is the same as the default FileModeStrategy, except
     * all directories will be treated as directories regardless of whether
     * or not they contain a .git directory or file.
     *
     * @since 4.3
     */
    public static class NoGitlinksStrategy implements FileModeStrategy {

        /**
         * a singleton instance of the default FileModeStrategy
         */
        public final static NoGitlinksStrategy INSTANCE = new NoGitlinksStrategy();

        @Override
        public FileMode getMode(File f, FS.Attributes attributes) {
            if (attributes.isSymbolicLink()) {
                return FileMode.SYMLINK;
            } else if (attributes.isDirectory()) {
                return FileMode.TREE;
            } else if (attributes.isExecutable()) {
                return FileMode.EXECUTABLE_FILE;
            } else {
                return FileMode.REGULAR_FILE;
            }
        }
    }

    /**
     * Wrapper for a standard Java IO file
     */
    public static class FileEntry extends Entry {
        private final FileMode mode;

        private FS.Attributes attributes;

        private FS fs;

        /**
         * Create a new file entry.
         *
         * @param f
         *            file
         * @param fs
         *            file system
         */
        public FileEntry(File f, FS fs) {
            this(f, fs, DefaultFileModeStrategy.INSTANCE);
        }

        /**
         * Create a new file entry given the specified FileModeStrategy
         *
         * @param f
         *            file
         * @param fs
         *            file system
         * @param fileModeStrategy
         *            the strategy to use when determining the FileMode of a
         *            file; controls gitlinks etc.
         *
         * @since 4.3
         */
        public FileEntry(File f, FS fs, FileModeStrategy fileModeStrategy) {
            this.fs = fs;
            f = fs.normalize(f);
            attributes = fs.getAttributes(f);
            mode = fileModeStrategy.getMode(f, attributes);
        }

        /**
         * Create a new file entry given the specified FileModeStrategy
         *
         * @param f
         *            file
         * @param fs
         *            file system
         * @param attributes
         *            of the file
         * @param fileModeStrategy
         *            the strategy to use when determining the FileMode of a
         *            file; controls gitlinks etc.
         *
         * @since 5.0
         */
        public FileEntry(File f, FS fs, FS.Attributes attributes, FileModeStrategy fileModeStrategy) {
            this.fs = fs;
            this.attributes = attributes;
            f = fs.normalize(f);
            mode = fileModeStrategy.getMode(f, attributes);
        }

        @Override
        public FileMode getMode() {
            return mode;
        }

        @Override
        public String getName() {
            return attributes.getName();
        }

        @Override
        public long getLength() {
            return attributes.getLength();
        }

        @Override
        @Deprecated
        public long getLastModified() {
            return attributes.getLastModifiedInstant().toEpochMilli();
        }

        /**
         * @since 5.1.9
         */
        @Override
        public Instant getLastModifiedInstant() {
            return attributes.getLastModifiedInstant();
        }

        @Override
        public InputStream openInputStream() throws IOException {
            if (attributes.isSymbolicLink()) {
                return new ByteArrayInputStream(fs.readSymLink(getFile()).getBytes(UTF_8));
            }
            return new FileInputStream(getFile());
        }

        /**
         * Get the underlying file of this entry.
         *
         * @return the underlying file of this entry
         */
        public File getFile() {
            return attributes.getFile();
        }
    }

    /**
     * <p>Getter for the field <code>directory</code>.</p>
     *
     * @return The root directory of this iterator
     */
    public File getDirectory() {
        return directory;
    }

    /**
     * Get the location of the working file.
     *
     * @return The location of the working file. This is the same as {@code new
     *         File(getDirectory(), getEntryPath())} but may be faster by
     *         reusing an internal File instance.
     */
    public File getEntryFile() {
        return ((FileEntry) current()).getFile();
    }

    /** {@inheritDoc} */
    @Override
    protected byte[] idSubmodule(Entry e) {
        return idSubmodule(getDirectory(), e);
    }

    /** {@inheritDoc} */
    @Override
    protected String readSymlinkTarget(Entry entry) throws IOException {
        return fs.readSymLink(getEntryFile());
    }
}