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}