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 private final FileEntry parent;
61 private FileEntry[] children;
62 private final File file;
63 private String name;
64 private boolean exists;
65 private boolean directory;
66 private SerializableFileTime lastModified = SerializableFileTime.EPOCH;
67 private long length;
68
69 /**
70 * Constructs a new monitor for a specified {@link File}.
71 *
72 * @param file The file being monitored
73 */
74 public FileEntry(final File file) {
75 this(null, file);
76 }
77
78 /**
79 * Constructs a new monitor for a specified {@link File}.
80 *
81 * @param parent The parent
82 * @param file The file being monitored
83 */
84 public FileEntry(final FileEntry parent, final File file) {
85 this.file = Objects.requireNonNull(file, "file");
86 this.parent = parent;
87 this.name = file.getName();
88 }
89
90 /**
91 * Gets the directory's files.
92 *
93 * @return This directory's files or an empty
94 * array if the file is not a directory or the
95 * directory is empty
96 */
97 public FileEntry[] getChildren() {
98 return children != null ? children : EMPTY_FILE_ENTRY_ARRAY;
99 }
100
101 /**
102 * Gets the file being monitored.
103 *
104 * @return the file being monitored
105 */
106 public File getFile() {
107 return file;
108 }
109
110 /**
111 * Gets the last modified time from the last time it
112 * was checked.
113 *
114 * @return the last modified time in milliseconds.
115 */
116 public long getLastModified() {
117 return lastModified.toMillis();
118 }
119
120 /**
121 * Gets the last modified time from the last time it was checked.
122 *
123 * @return the last modified time.
124 * @since 2.12.0
125 */
126 public FileTime getLastModifiedFileTime() {
127 return lastModified.unwrap();
128 }
129
130 /**
131 * Gets the length.
132 *
133 * @return the length
134 */
135 public long getLength() {
136 return length;
137 }
138
139 /**
140 * Gets the level
141 *
142 * @return the level
143 */
144 public int getLevel() {
145 return parent == null ? 0 : parent.getLevel() + 1;
146 }
147
148 /**
149 * Gets the file name.
150 *
151 * @return the file name
152 */
153 public String getName() {
154 return name;
155 }
156
157 /**
158 * Gets the parent entry.
159 *
160 * @return the parent entry
161 */
162 public FileEntry getParent() {
163 return parent;
164 }
165
166 /**
167 * Tests whether the file is a directory or not.
168 *
169 * @return whether the file is a directory or not
170 */
171 public boolean isDirectory() {
172 return directory;
173 }
174
175 /**
176 * Tests whether the file existed the last time it
177 * was checked.
178 *
179 * @return whether the file existed
180 */
181 public boolean isExists() {
182 return exists;
183 }
184
185 /**
186 * Constructs a new child instance.
187 * <p>
188 * Custom implementations should override this method to return
189 * a new instance of the appropriate type.
190 * </p>
191 *
192 * @param file The child file
193 * @return a new child instance
194 */
195 public FileEntry newChildInstance(final File file) {
196 return new FileEntry(this, file);
197 }
198
199 /**
200 * Refreshes the attributes from the {@link File}, indicating
201 * whether the file has changed.
202 * <p>
203 * This implementation refreshes the {@code name}, {@code exists},
204 * {@code directory}, {@code lastModified} and {@code length}
205 * properties.
206 * </p>
207 * <p>
208 * The {@code exists}, {@code directory}, {@code lastModified}
209 * and {@code length} properties are compared for changes
210 * </p>
211 *
212 * @param file the file instance to compare to
213 * @return {@code true} if the file has changed, otherwise {@code false}
214 */
215 public boolean refresh(final File file) {
216 // cache original values
217 final boolean origExists = exists;
218 final SerializableFileTime origLastModified = lastModified;
219 final boolean origDirectory = directory;
220 final long origLength = length;
221
222 // refresh the values
223 name = file.getName();
224 exists = Files.exists(file.toPath());
225 directory = exists && file.isDirectory();
226 try {
227 setLastModified(exists ? FileUtils.lastModifiedFileTime(file) : FileTimes.EPOCH);
228 } catch (final IOException e) {
229 setLastModified(SerializableFileTime.EPOCH);
230 }
231 length = exists && !directory ? file.length() : 0;
232
233 // Return if there are changes
234 return exists != origExists || !lastModified.equals(origLastModified) || directory != origDirectory
235 || length != origLength;
236 }
237
238 /**
239 * Sets the directory's files.
240 *
241 * @param children This directory's files, may be null
242 */
243 public void setChildren(final FileEntry... children) {
244 this.children = children;
245 }
246
247 /**
248 * Sets whether the file is a directory or not.
249 *
250 * @param directory whether the file is a directory or not
251 */
252 public void setDirectory(final boolean directory) {
253 this.directory = directory;
254 }
255
256 /**
257 * Sets whether the file existed the last time it
258 * was checked.
259 *
260 * @param exists whether the file exists or not
261 */
262 public void setExists(final boolean exists) {
263 this.exists = exists;
264 }
265
266 /**
267 * Sets the last modified time from the last time it was checked.
268 *
269 * @param lastModified The last modified time.
270 * @since 2.12.0
271 */
272 public void setLastModified(final FileTime lastModified) {
273 setLastModified(new SerializableFileTime(lastModified));
274 }
275
276 /**
277 * Sets the last modified time from the last time it
278 * was checked.
279 *
280 * @param lastModified The last modified time in milliseconds.
281 */
282 public void setLastModified(final long lastModified) {
283 setLastModified(FileTime.fromMillis(lastModified));
284 }
285
286 void setLastModified(final SerializableFileTime lastModified) {
287 this.lastModified = lastModified;
288 }
289
290 /**
291 * Sets the length.
292 *
293 * @param length the length
294 */
295 public void setLength(final long length) {
296 this.length = length;
297 }
298
299 /**
300 * Sets the file name.
301 *
302 * @param name the file name
303 */
304 public void setName(final String name) {
305 this.name = name;
306 }
307 }