    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    /** The parent. */
    061    private final FileEntry parent;
    062
    063    /** My children. */
    064    private FileEntry[] children;
    065
    066    /** Monitored file. */
    067    private final File file;
    068
    069    /** Monitored file name. */
    070    private String name;
    071
    072    /** Whether the file exists. */
    073    private boolean exists;
    074
    075    /** Whether the file is a directory or not. */
    076    private boolean directory;
    077
    078    /** The file's last modified timestamp. */
    079    private SerializableFileTime lastModified = SerializableFileTime.EPOCH;
    080
    081    /** The file's length. */
    082    private long length;
    083
    084    /**
    085     * Constructs a new monitor for a specified {@link File}.
    086     *
    087     * @param file The file being monitored
    088     */
    089    public FileEntry(final File file) {
    090        this(null, file);
    091    }
    092
    093    /**
    094     * Constructs a new monitor for a specified {@link File}.
    095     *
    096     * @param parent The parent.
    097     * @param file The file being monitored.
    098     */
    099    public FileEntry(final FileEntry parent, final File file) {
    100        this.file = Objects.requireNonNull(file, "file");
    101        this.parent = parent;
    102        this.name = file.getName();
    103    }
    104
    105    /**
    106     * Gets the directory's files.
    107     *
    108     * @return This directory's files or an empty
    109     * array if the file is not a directory or the
    110     * directory is empty
    111     */
    112    public FileEntry[] getChildren() {
    113        return children != null ? children : EMPTY_FILE_ENTRY_ARRAY;
    114    }
    115
    116    /**
    117     * Gets the file being monitored.
    118     *
    119     * @return the file being monitored
    120     */
    121    public File getFile() {
    122        return file;
    123    }
    124
    125    /**
    126     * Gets the last modified time from the last time it
    127     * was checked.
    128     *
    129     * @return the last modified time in milliseconds.
    130     */
    131    public long getLastModified() {
    132        return lastModified.toMillis();
    133    }
    134
    135    /**
    136     * Gets the last modified time from the last time it was checked.
    137     *
    138     * @return the last modified time.
    139     * @since 2.12.0
    140     */
    141    public FileTime getLastModifiedFileTime() {
    142        return lastModified.unwrap();
    143    }
    144
    145    /**
    146     * Gets the length.
    147     *
    148     * @return the length
    149     */
    150    public long getLength() {
    151        return length;
    152    }
    153
    154    /**
    155     * Gets the level
    156     *
    157     * @return the level
    158     */
    159    public int getLevel() {
    160        return parent == null ? 0 : parent.getLevel() + 1;
    161    }
    162
    163    /**
    164     * Gets the file name.
    165     *
    166     * @return the file name
    167     */
    168    public String getName() {
    169        return name;
    170    }
    171
    172    /**
    173     * Gets the parent entry.
    174     *
    175     * @return the parent entry
    176     */
    177    public FileEntry getParent() {
    178        return parent;
    179    }
    180
    181    /**
    182     * Tests whether the file is a directory or not.
    183     *
    184     * @return whether the file is a directory or not
    185     */
    186    public boolean isDirectory() {
    187        return directory;
    188    }
    189
    190    /**
    191     * Tests whether the file existed the last time it
    192     * was checked.
    193     *
    194     * @return whether the file existed
    195     */
    196    public boolean isExists() {
    197        return exists;
    198    }
    199
    200    /**
    201     * Constructs a new child instance.
    202     * <p>
    203     * Custom implementations should override this method to return
    204     * a new instance of the appropriate type.
    205     * </p>
    206     *
    207     * @param file The child file
    208     * @return a new child instance
    209     */
    210    public FileEntry newChildInstance(final File file) {
    211        return new FileEntry(this, file);
    212    }
    213
    214    /**
    215     * Refreshes the attributes from the {@link File}, indicating
    216     * whether the file has changed.
    217     * <p>
    218     * This implementation refreshes the {@code name}, {@code exists},
    219     * {@code directory}, {@code lastModified} and {@code length}
    220     * properties.
    221     * </p>
    222     * <p>
    223     * The {@code exists}, {@code directory}, {@code lastModified}
    224     * and {@code length} properties are compared for changes
    225     * </p>
    226     *
    227     * @param file the file instance to compare to
    228     * @return {@code true} if the file has changed, otherwise {@code false}
    229     */
    230    public boolean refresh(final File file) {
    231        // cache original values
    232        final boolean origExists = exists;
    233        final SerializableFileTime origLastModified = lastModified;
    234        final boolean origDirectory = directory;
    235        final long origLength = length;
    236
    237        // refresh the values
    238        name = file.getName();
    239        exists = Files.exists(file.toPath());
    240        directory = exists && file.isDirectory();
    241        try {
    242            setLastModified(exists ? FileUtils.lastModifiedFileTime(file) : FileTimes.EPOCH);
    243        } catch (final IOException e) {
    244            setLastModified(SerializableFileTime.EPOCH);
    245        }
    246        length = exists && !directory ? file.length() : 0;
    247
    248        // Return if there are changes
    249        return exists != origExists || !lastModified.equals(origLastModified) || directory != origDirectory
    250            || length != origLength;
    251    }
    252
    253    /**
    254     * Sets the directory's files.
    255     *
    256     * @param children This directory's files, may be null
    257     */
    258    public void setChildren(final FileEntry... children) {
    259        this.children = children;
    260    }
    261
    262    /**
    263     * Sets whether the file is a directory or not.
    264     *
    265     * @param directory whether the file is a directory or not
    266     */
    267    public void setDirectory(final boolean directory) {
    268        this.directory = directory;
    269    }
    270
    271    /**
    272     * Sets whether the file existed the last time it
    273     * was checked.
    274     *
    275     * @param exists whether the file exists or not
    276     */
    277    public void setExists(final boolean exists) {
    278        this.exists = exists;
    279    }
    280
    281    /**
    282     * Sets the last modified time from the last time it was checked.
    283     *
    284     * @param lastModified The last modified time.
    285     * @since 2.12.0
    286     */
    287    public void setLastModified(final FileTime lastModified) {
    288        setLastModified(new SerializableFileTime(lastModified));
    289    }
    290
    291    /**
    292     * Sets the last modified time from the last time it
    293     * was checked.
    294     *
    295     * @param lastModified The last modified time in milliseconds.
    296     */
    297    public void setLastModified(final long lastModified) {
    298        setLastModified(FileTime.fromMillis(lastModified));
    299    }
    300
    301    void setLastModified(final SerializableFileTime lastModified) {
    302        this.lastModified = lastModified;
    303    }
    304
    305    /**
    306     * Sets the length.
    307     *
    308     * @param length the length
    309     */
    310    public void setLength(final long length) {
    311        this.length = length;
    312    }
    313
    314    /**
    315     * Sets the file name.
    316     *
    317     * @param name the file name
    318     */
    319    public void setName(final String name) {
    320        this.name = name;
    321    }
    322}