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 }