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