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    *      https://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   *
52   * @see FileAlterationObserver
53   * @since 2.0
54   */
55  public class FileEntry implements Serializable {
56  
57      private static final long serialVersionUID = -2505664948818681153L;
58  
59      static final FileEntry[] EMPTY_FILE_ENTRY_ARRAY = {};
60  
61      /** The parent. */
62      private final FileEntry parent;
63  
64      /** My children. */
65      private FileEntry[] children;
66  
67      /** Monitored file. */
68      private final File file;
69  
70      /** Monitored file name. */
71      private String name;
72  
73      /** Whether the file exists. */
74      private boolean exists;
75  
76      /** Whether the file is a directory or not. */
77      private boolean directory;
78  
79      /** The file's last modified timestamp. */
80      private SerializableFileTime lastModified = SerializableFileTime.EPOCH;
81  
82      /** The file's length. */
83      private long length;
84  
85      /**
86       * Constructs a new monitor for a specified {@link File}.
87       *
88       * @param file The file being monitored.
89       */
90      public FileEntry(final File file) {
91          this(null, file);
92      }
93  
94      /**
95       * Constructs a new monitor for a specified {@link File}.
96       *
97       * @param parent The parent.
98       * @param file The file being monitored.
99       */
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 }