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}