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 }