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