    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.io.monitor;
    018
    019import java.io.File;
    020import java.io.IOException;
    021import java.io.Serializable;
    022import java.nio.file.Files;
    023import java.nio.file.attribute.FileTime;
    024import java.util.Objects;
    025
    026import org.apache.commons.io.FileUtils;
    027import org.apache.commons.io.file.attribute.FileTimes;
    028
    029/**
    030 * The state of a file or directory, capturing the following {@link File} attributes at a point in time.
    031 * <ul>
    032 *   <li>File Name (see {@link File#getName()})</li>
    033 *   <li>Exists - whether the file exists or not (see {@link File#exists()})</li>
    034 *   <li>Directory - whether the file is a directory or not (see {@link File#isDirectory()})</li>
    035 *   <li>Last Modified Date/Time (see {@link FileUtils#lastModifiedUnchecked(File)})</li>
    036 *   <li>Length (see {@link File#length()}) - directories treated as zero</li>
    037 *   <li>Children - contents of a directory (see {@link File#listFiles(java.io.FileFilter)})</li>
    038 * </ul>
    039 *
    040 * <h2>Custom Implementations</h2>
    041 * <p>
    042 * If the state of additional {@link File} attributes is required then create a custom
    043 * {@link FileEntry} with properties for those attributes. Override the
    044 * {@link #newChildInstance(File)} to return a new instance of the appropriate type.
    045 * You may also want to override the {@link #refresh(File)} method.
    046 * </p>
    047 * <h2>Deprecating Serialization</h2>
    048 * <p>
    049 * <em>Serialization is deprecated and will be removed in 3.0.</em>
    050 * </p>
    051 * @see FileAlterationObserver
    052 * @since 2.0
    053 */
    054public class FileEntry implements Serializable {
    055
    056    private static final long serialVersionUID = -2505664948818681153L;
    057
    058    static final FileEntry[] EMPTY_FILE_ENTRY_ARRAY = {};
    059
    060    private final FileEntry parent;
    061    private FileEntry[] children;
    062    private final File file;
    063    private String name;
    064    private boolean exists;
    065    private boolean directory;
    066    private SerializableFileTime lastModified = SerializableFileTime.EPOCH;
    067    private long length;
    068
    069    /**
    070     * Constructs a new monitor for a specified {@link File}.
    071     *
    072     * @param file The file being monitored
    073     */
    074    public FileEntry(final File file) {
    075        this(null, file);
    076    }
    077
    078    /**
    079     * Constructs a new monitor for a specified {@link File}.
    080     *
    081     * @param parent The parent
    082     * @param file The file being monitored
    083     */
    084    public FileEntry(final FileEntry parent, final File file) {
    085        this.file = Objects.requireNonNull(file, "file");
    086        this.parent = parent;
    087        this.name = file.getName();
    088    }
    089
    090    /**
    091     * Gets the directory's files.
    092     *
    093     * @return This directory's files or an empty
    094     * array if the file is not a directory or the
    095     * directory is empty
    096     */
    097    public FileEntry[] getChildren() {
    098        return children != null ? children : EMPTY_FILE_ENTRY_ARRAY;
    099    }
    100
    101    /**
    102     * Gets the file being monitored.
    103     *
    104     * @return the file being monitored
    105     */
    106    public File getFile() {
    107        return file;
    108    }
    109
    110    /**
    111     * Gets the last modified time from the last time it
    112     * was checked.
    113     *
    114     * @return the last modified time in milliseconds.
    115     */
    116    public long getLastModified() {
    117        return lastModified.toMillis();
    118    }
    119
    120    /**
    121     * Gets the last modified time from the last time it was checked.
    122     *
    123     * @return the last modified time.
    124     * @since 2.12.0
    125     */
    126    public FileTime getLastModifiedFileTime() {
    127        return lastModified.unwrap();
    128    }
    129
    130    /**
    131     * Gets the length.
    132     *
    133     * @return the length
    134     */
    135    public long getLength() {
    136        return length;
    137    }
    138
    139    /**
    140     * Gets the level
    141     *
    142     * @return the level
    143     */
    144    public int getLevel() {
    145        return parent == null ? 0 : parent.getLevel() + 1;
    146    }
    147
    148    /**
    149     * Gets the file name.
    150     *
    151     * @return the file name
    152     */
    153    public String getName() {
    154        return name;
    155    }
    156
    157    /**
    158     * Gets the parent entry.
    159     *
    160     * @return the parent entry
    161     */
    162    public FileEntry getParent() {
    163        return parent;
    164    }
    165
    166    /**
    167     * Tests whether the file is a directory or not.
    168     *
    169     * @return whether the file is a directory or not
    170     */
    171    public boolean isDirectory() {
    172        return directory;
    173    }
    174
    175    /**
    176     * Tests whether the file existed the last time it
    177     * was checked.
    178     *
    179     * @return whether the file existed
    180     */
    181    public boolean isExists() {
    182        return exists;
    183    }
    184
    185    /**
    186     * Constructs a new child instance.
    187     * <p>
    188     * Custom implementations should override this method to return
    189     * a new instance of the appropriate type.
    190     * </p>
    191     *
    192     * @param file The child file
    193     * @return a new child instance
    194     */
    195    public FileEntry newChildInstance(final File file) {
    196        return new FileEntry(this, file);
    197    }
    198
    199    /**
    200     * Refreshes the attributes from the {@link File}, indicating
    201     * whether the file has changed.
    202     * <p>
    203     * This implementation refreshes the {@code name}, {@code exists},
    204     * {@code directory}, {@code lastModified} and {@code length}
    205     * properties.
    206     * </p>
    207     * <p>
    208     * The {@code exists}, {@code directory}, {@code lastModified}
    209     * and {@code length} properties are compared for changes
    210     * </p>
    211     *
    212     * @param file the file instance to compare to
    213     * @return {@code true} if the file has changed, otherwise {@code false}
    214     */
    215    public boolean refresh(final File file) {
    216        // cache original values
    217        final boolean origExists = exists;
    218        final SerializableFileTime origLastModified = lastModified;
    219        final boolean origDirectory = directory;
    220        final long origLength = length;
    221
    222        // refresh the values
    223        name = file.getName();
    224        exists = Files.exists(file.toPath());
    225        directory = exists && file.isDirectory();
    226        try {
    227            setLastModified(exists ? FileUtils.lastModifiedFileTime(file) : FileTimes.EPOCH);
    228        } catch (final IOException e) {
    229            setLastModified(SerializableFileTime.EPOCH);
    230        }
    231        length = exists && !directory ? file.length() : 0;
    232
    233        // Return if there are changes
    234        return exists != origExists || !lastModified.equals(origLastModified) || directory != origDirectory
    235            || length != origLength;
    236    }
    237
    238    /**
    239     * Sets the directory's files.
    240     *
    241     * @param children This directory's files, may be null
    242     */
    243    public void setChildren(final FileEntry... children) {
    244        this.children = children;
    245    }
    246
    247    /**
    248     * Sets whether the file is a directory or not.
    249     *
    250     * @param directory whether the file is a directory or not
    251     */
    252    public void setDirectory(final boolean directory) {
    253        this.directory = directory;
    254    }
    255
    256    /**
    257     * Sets whether the file existed the last time it
    258     * was checked.
    259     *
    260     * @param exists whether the file exists or not
    261     */
    262    public void setExists(final boolean exists) {
    263        this.exists = exists;
    264    }
    265
    266    /**
    267     * Sets the last modified time from the last time it was checked.
    268     *
    269     * @param lastModified The last modified time.
    270     * @since 2.12.0
    271     */
    272    public void setLastModified(final FileTime lastModified) {
    273        setLastModified(new SerializableFileTime(lastModified));
    274    }
    275
    276    /**
    277     * Sets the last modified time from the last time it
    278     * was checked.
    279     *
    280     * @param lastModified The last modified time in milliseconds.
    281     */
    282    public void setLastModified(final long lastModified) {
    283        setLastModified(FileTime.fromMillis(lastModified));
    284    }
    285
    286    void setLastModified(final SerializableFileTime lastModified) {
    287        this.lastModified = lastModified;
    288    }
    289
    290    /**
    291     * Sets the length.
    292     *
    293     * @param length the length
    294     */
    295    public void setLength(final long length) {
    296        this.length = length;
    297    }
    298
    299    /**
    300     * Sets the file name.
    301     *
    302     * @param name the file name
    303     */
    304    public void setName(final String name) {
    305        this.name = name;
    306    }
    307}