    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;
    023
    024import org.apache.commons.io.FileUtils;
    025
    026/**
    027 * The state of a file or directory, capturing the following {@link File} attributes at a point in time.
    028 * <ul>
    029 *   <li>File Name (see {@link File#getName()})</li>
    030 *   <li>Exists - whether the file exists or not (see {@link File#exists()})</li>
    031 *   <li>Directory - whether the file is a directory or not (see {@link File#isDirectory()})</li>
    032 *   <li>Last Modified Date/Time (see {@link FileUtils#lastModifiedUnchecked(File)})</li>
    033 *   <li>Length (see {@link File#length()}) - directories treated as zero</li>
    034 *   <li>Children - contents of a directory (see {@link File#listFiles(java.io.FileFilter)})</li>
    035 * </ul>
    036 *
    037 * <h2>Custom Implementations</h2>
    038 * <p>
    039 * If the state of additional {@link File} attributes is required then create a custom
    040 * {@link FileEntry} with properties for those attributes. Override the
    041 * {@link #newChildInstance(File)} to return a new instance of the appropriate type.
    042 * You may also want to override the {@link #refresh(File)} method.
    043 * </p>
    044 * @see FileAlterationObserver
    045 * @since 2.0
    046 */
    047public class FileEntry implements Serializable {
    048
    049    private static final long serialVersionUID = -2505664948818681153L;
    050
    051    static final FileEntry[] EMPTY_FILE_ENTRY_ARRAY = {};
    052
    053    private final FileEntry parent;
    054    private FileEntry[] children;
    055    private final File file;
    056    private String name;
    057    private boolean exists;
    058    private boolean directory;
    059    private long lastModified;
    060    private long length;
    061
    062    /**
    063     * Construct a new monitor for a specified {@link File}.
    064     *
    065     * @param file The file being monitored
    066     */
    067    public FileEntry(final File file) {
    068        this(null, file);
    069    }
    070
    071    /**
    072     * Construct a new monitor for a specified {@link File}.
    073     *
    074     * @param parent The parent
    075     * @param file The file being monitored
    076     */
    077    public FileEntry(final FileEntry parent, final File file) {
    078        if (file == null) {
    079            throw new IllegalArgumentException("File is missing");
    080        }
    081        this.file = file;
    082        this.parent = parent;
    083        this.name = file.getName();
    084    }
    085
    086    /**
    087     * Refresh the attributes from the {@link File}, indicating
    088     * whether the file has changed.
    089     * <p>
    090     * This implementation refreshes the {@code name}, {@code exists},
    091     * {@code directory}, {@code lastModified} and {@code length}
    092     * properties.
    093     * <p>
    094     * The {@code exists}, {@code directory}, {@code lastModified}
    095     * and {@code length} properties are compared for changes
    096     *
    097     * @param file the file instance to compare to
    098     * @return {@code true} if the file has changed, otherwise {@code false}
    099     */
    100    public boolean refresh(final File file) {
    101        // cache original values
    102        final boolean origExists = exists;
    103        final long origLastModified = lastModified;
    104        final boolean origDirectory = directory;
    105        final long origLength = length;
    106
    107        // refresh the values
    108        name = file.getName();
    109        exists = Files.exists(file.toPath());
    110        directory = exists && file.isDirectory();
    111        try {
    112            lastModified = exists ? FileUtils.lastModified(file) : 0;
    113        } catch (final IOException e) {
    114            lastModified = 0;
    115        }
    116        length = exists && !directory ? file.length() : 0;
    117
    118        // Return if there are changes
    119        return exists != origExists || lastModified != origLastModified || directory != origDirectory
    120            || length != origLength;
    121    }
    122
    123    /**
    124     * Create a new child instance.
    125     * <p>
    126     * Custom implementations should override this method to return
    127     * a new instance of the appropriate type.
    128     *
    129     * @param file The child file
    130     * @return a new child instance
    131     */
    132    public FileEntry newChildInstance(final File file) {
    133        return new FileEntry(this, file);
    134    }
    135
    136    /**
    137     * Return the parent entry.
    138     *
    139     * @return the parent entry
    140     */
    141    public FileEntry getParent() {
    142        return parent;
    143    }
    144
    145    /**
    146     * Return the level
    147     *
    148     * @return the level
    149     */
    150    public int getLevel() {
    151        return parent == null ? 0 : parent.getLevel() + 1;
    152    }
    153
    154    /**
    155     * Return the directory's files.
    156     *
    157     * @return This directory's files or an empty
    158     * array if the file is not a directory or the
    159     * directory is empty
    160     */
    161    public FileEntry[] getChildren() {
    162        return children != null ? children : EMPTY_FILE_ENTRY_ARRAY;
    163    }
    164
    165    /**
    166     * Set the directory's files.
    167     *
    168     * @param children This directory's files, may be null
    169     */
    170    public void setChildren(final FileEntry... children) {
    171        this.children = children;
    172    }
    173
    174    /**
    175     * Return the file being monitored.
    176     *
    177     * @return the file being monitored
    178     */
    179    public File getFile() {
    180        return file;
    181    }
    182
    183    /**
    184     * Return the file name.
    185     *
    186     * @return the file name
    187     */
    188    public String getName() {
    189        return name;
    190    }
    191
    192    /**
    193     * Set the file name.
    194     *
    195     * @param name the file name
    196     */
    197    public void setName(final String name) {
    198        this.name = name;
    199    }
    200
    201    /**
    202     * Return the last modified time from the last time it
    203     * was checked.
    204     *
    205     * @return the last modified time
    206     */
    207    public long getLastModified() {
    208        return lastModified;
    209    }
    210
    211    /**
    212     * Return the last modified time from the last time it
    213     * was checked.
    214     *
    215     * @param lastModified The last modified time
    216     */
    217    public void setLastModified(final long lastModified) {
    218        this.lastModified = lastModified;
    219    }
    220
    221    /**
    222     * Return the length.
    223     *
    224     * @return the length
    225     */
    226    public long getLength() {
    227        return length;
    228    }
    229
    230    /**
    231     * Set the length.
    232     *
    233     * @param length the length
    234     */
    235    public void setLength(final long length) {
    236        this.length = length;
    237    }
    238
    239    /**
    240     * Indicate whether the file existed the last time it
    241     * was checked.
    242     *
    243     * @return whether the file existed
    244     */
    245    public boolean isExists() {
    246        return exists;
    247    }
    248
    249    /**
    250     * Set whether the file existed the last time it
    251     * was checked.
    252     *
    253     * @param exists whether the file exists or not
    254     */
    255    public void setExists(final boolean exists) {
    256        this.exists = exists;
    257    }
    258
    259    /**
    260     * Indicate whether the file is a directory or not.
    261     *
    262     * @return whether the file is a directory or not
    263     */
    264    public boolean isDirectory() {
    265        return directory;
    266    }
    267
    268    /**
    269     * Set whether the file is a directory or not.
    270     *
    271     * @param directory whether the file is a directory or not
    272     */
    273    public void setDirectory(final boolean directory) {
    274        this.directory = directory;
    275    }
    276}