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}