View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.io.monitor;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.Serializable;
22  import java.nio.file.Files;
23  import java.nio.file.attribute.FileTime;
24  import java.util.Objects;
25  
26  import org.apache.commons.io.FileUtils;
27  import org.apache.commons.io.file.attribute.FileTimes;
28  
29  /**
30   * The state of a file or directory, capturing the following {@link File} attributes at a point in time.
31   * <ul>
32   *   <li>File Name (see {@link File#getName()})</li>
33   *   <li>Exists - whether the file exists or not (see {@link File#exists()})</li>
34   *   <li>Directory - whether the file is a directory or not (see {@link File#isDirectory()})</li>
35   *   <li>Last Modified Date/Time (see {@link FileUtils#lastModifiedUnchecked(File)})</li>
36   *   <li>Length (see {@link File#length()}) - directories treated as zero</li>
37   *   <li>Children - contents of a directory (see {@link File#listFiles(java.io.FileFilter)})</li>
38   * </ul>
39   *
40   * <h2>Custom Implementations</h2>
41   * <p>
42   * If the state of additional {@link File} attributes is required then create a custom
43   * {@link FileEntry} with properties for those attributes. Override the
44   * {@link #newChildInstance(File)} to return a new instance of the appropriate type.
45   * You may also want to override the {@link #refresh(File)} method.
46   * </p>
47   * <h2>Deprecating Serialization</h2>
48   * <p>
49   * <em>Serialization is deprecated and will be removed in 3.0.</em>
50   * </p>
51   * @see FileAlterationObserver
52   * @since 2.0
53   */
54  public class FileEntry implements Serializable {
55  
56      private static final long serialVersionUID = -2505664948818681153L;
57  
58      static final FileEntry[] EMPTY_FILE_ENTRY_ARRAY = {};
59  
60      /** The parent. */
61      private final FileEntry parent;
62  
63      /** My children. */
64      private FileEntry[] children;
65  
66      /** Monitored file. */
67      private final File file;
68  
69      /** Monitored file name. */
70      private String name;
71  
72      /** Whether the file exists. */
73      private boolean exists;
74  
75      /** Whether the file is a directory or not. */
76      private boolean directory;
77  
78      /** The file's last modified timestamp. */
79      private SerializableFileTime lastModified = SerializableFileTime.EPOCH;
80  
81      /** The file's length. */
82      private long length;
83  
84      /**
85       * Constructs a new monitor for a specified {@link File}.
86       *
87       * @param file The file being monitored
88       */
89      public FileEntry(final File file) {
90          this(null, file);
91      }
92  
93      /**
94       * Constructs a new monitor for a specified {@link File}.
95       *
96       * @param parent The parent.
97       * @param file The file being monitored.
98       */
99      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 }