001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.commons.jci.monitor;
019
020 import java.io.File;
021 import java.util.HashMap;
022 import java.util.HashSet;
023 import java.util.Map;
024 import java.util.Set;
025
026 import org.apache.commons.logging.Log;
027 import org.apache.commons.logging.LogFactory;
028
029 /**
030 * Implementation of a FilesystemAlterationObserver
031 *
032 * @author tcurdt
033 */
034 public class FilesystemAlterationObserverImpl implements FilesystemAlterationObserver {
035
036 private final Log log = LogFactory.getLog(FilesystemAlterationObserverImpl.class);
037
038 private interface MonitorFile {
039
040 long lastModified();
041 MonitorFile[] listFiles();
042 boolean isDirectory();
043 boolean exists();
044 String getName();
045
046 }
047
048 private final static class MonitorFileImpl implements MonitorFile {
049
050 private final File file;
051
052 public MonitorFileImpl( final File pFile ) {
053 file = pFile;
054 }
055
056 public boolean exists() {
057 return file.exists();
058 }
059
060 public MonitorFile[] listFiles() {
061 final File[] children = file.listFiles();
062 if (children == null) { // not a directory or IOError (e.g. protection issue)
063 return new MonitorFile[0];
064 }
065
066 final MonitorFile[] providers = new MonitorFile[children.length];
067 for (int i = 0; i < providers.length; i++) {
068 providers[i] = new MonitorFileImpl(children[i]);
069 }
070 return providers;
071 }
072
073 public String getName() {
074 return file.getName();
075 }
076
077 public boolean isDirectory() {
078 return file.isDirectory();
079 }
080
081 public long lastModified() {
082 return file.lastModified();
083 }
084
085 @Override
086 public String toString() {
087 return file.toString();
088 }
089
090 }
091
092 private final class Entry {
093
094 private final static int TYPE_UNKNOWN = 0;
095 private final static int TYPE_FILE = 1;
096 private final static int TYPE_DIRECTORY = 2;
097
098 private final MonitorFile file;
099 private long lastModified = -1;
100 private int lastType = TYPE_UNKNOWN;
101 private final Map<String, Entry> children = new HashMap<String, Entry>();
102
103 public Entry(final MonitorFile pFile) {
104 file = pFile;
105 }
106
107 public String getName() {
108 return file.getName();
109 }
110
111
112 @Override
113 public String toString() {
114 return file.toString();
115 }
116
117
118 private void compareChildren() {
119 if (!file.isDirectory()) {
120 return;
121 }
122
123 final MonitorFile[] files = file.listFiles();
124 final Set<Entry> deleted = new HashSet<Entry>(children.values());
125 for (MonitorFile f : files) {
126 final String name = f.getName();
127 final Entry entry = children.get(name);
128 if (entry != null) {
129 // already recognized as child
130 deleted.remove(entry);
131
132 if(entry.needsToBeDeleted()) {
133 // we have to delete this one
134 children.remove(name);
135 }
136 } else {
137 // a new child
138 final Entry newChild = new Entry(f);
139 children.put(name, newChild);
140 newChild.needsToBeDeleted();
141 }
142 }
143
144 // the ones not found on disk anymore
145
146 for (Entry entry : deleted) {
147 entry.deleteChildrenAndNotify();
148 children.remove(entry.getName());
149 }
150 }
151
152
153 private void deleteChildrenAndNotify() {
154 for (Entry entry : children.values()) {
155 entry.deleteChildrenAndNotify();
156 }
157 children.clear();
158
159 if(lastType == TYPE_DIRECTORY) {
160 notifyOnDirectoryDelete(this);
161 } else if (lastType == TYPE_FILE) {
162 notifyOnFileDelete(this);
163 }
164 }
165
166 public boolean needsToBeDeleted() {
167
168 if (!file.exists()) {
169 // deleted or has never existed yet
170
171 // log.debug(file + " does not exist or has been deleted");
172
173 deleteChildrenAndNotify();
174
175 // mark to be deleted by parent
176 return true;
177 } else {
178 // exists
179 final long currentModified = file.lastModified();
180
181 if (currentModified != lastModified) {
182 // last modified has changed
183 lastModified = currentModified;
184
185 // log.debug(file + " has new last modified");
186
187 // types only changes when also the last modified changes
188 final int newType = (file.isDirectory()?TYPE_DIRECTORY:TYPE_FILE);
189
190 if (lastType != newType) {
191 // the type has changed
192
193 // log.debug(file + " has a new type");
194
195 deleteChildrenAndNotify();
196
197 lastType = newType;
198
199 // and then an add as the new type
200
201 if (newType == TYPE_DIRECTORY) {
202 notifyOnDirectoryCreate(this);
203 compareChildren();
204 } else {
205 notifyOnFileCreate(this);
206 }
207
208 return false;
209 }
210
211 if (newType == TYPE_DIRECTORY) {
212 notifyOnDirectoryChange(this);
213 compareChildren();
214 } else {
215 notifyOnFileChange(this);
216 }
217
218 return false;
219
220 } else {
221
222 // so exists and has not changed
223
224 // log.debug(file + " does exist and has not changed");
225
226 compareChildren();
227
228 return false;
229 }
230 }
231 }
232
233 public MonitorFile getFile() {
234 return file;
235 }
236
237 public void markNotChanged() {
238 lastModified = file.lastModified();
239 }
240
241 }
242
243 private final File rootDirectory;
244 private final Entry rootEntry;
245
246 private FilesystemAlterationListener[] listeners = new FilesystemAlterationListener[0];
247 private final Set<FilesystemAlterationListener> listenersSet = new HashSet<FilesystemAlterationListener>();
248
249 public FilesystemAlterationObserverImpl( final File pRootDirectory ) {
250 rootDirectory = pRootDirectory;
251 rootEntry = new Entry(new MonitorFileImpl(pRootDirectory));
252 }
253
254
255
256 private void notifyOnStart() {
257 log.debug("onStart " + rootEntry);
258 for (FilesystemAlterationListener listener : listeners) {
259 listener.onStart(this);
260 }
261 }
262 private void notifyOnStop() {
263 log.debug("onStop " + rootEntry);
264 for (FilesystemAlterationListener listener : listeners) {
265 listener.onStop(this);
266 }
267 }
268
269 private void notifyOnFileCreate( final Entry pEntry ) {
270 log.debug("onFileCreate " + pEntry);
271 for (FilesystemAlterationListener listener : listeners) {
272 listener.onFileCreate(((MonitorFileImpl)pEntry.getFile()).file );
273 }
274 }
275 private void notifyOnFileChange( final Entry pEntry ) {
276 log.debug("onFileChange " + pEntry);
277 for (FilesystemAlterationListener listener : listeners) {
278 listener.onFileChange(((MonitorFileImpl)pEntry.getFile()).file );
279 }
280 }
281 private void notifyOnFileDelete( final Entry pEntry ) {
282 log.debug("onFileDelete " + pEntry);
283 for (FilesystemAlterationListener listener : listeners) {
284 listener.onFileDelete(((MonitorFileImpl)pEntry.getFile()).file );
285 }
286 }
287
288 private void notifyOnDirectoryCreate( final Entry pEntry ) {
289 log.debug("onDirectoryCreate " + pEntry);
290 for (FilesystemAlterationListener listener : listeners) {
291 listener.onDirectoryCreate(((MonitorFileImpl)pEntry.getFile()).file );
292 }
293 }
294 private void notifyOnDirectoryChange( final Entry pEntry ) {
295 log.debug("onDirectoryChange " + pEntry);
296 for (FilesystemAlterationListener listener : listeners) {
297 listener.onDirectoryChange(((MonitorFileImpl)pEntry.getFile()).file );
298 }
299 }
300 private void notifyOnDirectoryDelete( final Entry pEntry ) {
301 log.debug("onDirectoryDelete " + pEntry);
302 for (FilesystemAlterationListener listener : listeners) {
303 listener.onDirectoryDelete(((MonitorFileImpl)pEntry.getFile()).file );
304 }
305 }
306
307
308 private void checkEntries() {
309 if(rootEntry.needsToBeDeleted()) {
310 // root not existing
311 rootEntry.lastType = Entry.TYPE_UNKNOWN;
312 }
313 }
314
315
316 public void checkAndNotify() {
317 synchronized(listenersSet) {
318 if (listeners.length == 0) {
319 return;
320 }
321
322 notifyOnStart();
323
324 checkEntries();
325
326 notifyOnStop();
327 }
328 }
329
330
331 public File getRootDirectory() {
332 return rootDirectory;
333 }
334
335 public void addListener( final FilesystemAlterationListener pListener ) {
336 synchronized(listenersSet) {
337 if (listenersSet.add(pListener)) {
338 listeners = createArrayFromSet();
339 }
340 }
341 }
342
343 public void removeListener( final FilesystemAlterationListener pListener ) {
344 synchronized(listenersSet) {
345 if (listenersSet.remove(pListener)) {
346 listeners = createArrayFromSet();
347 }
348 }
349 }
350
351 private FilesystemAlterationListener[] createArrayFromSet() {
352 final FilesystemAlterationListener[] newListeners = new FilesystemAlterationListener[listenersSet.size()];
353 listenersSet.toArray(newListeners);
354 return newListeners;
355 }
356
357 public FilesystemAlterationListener[] getListeners() {
358 synchronized(listenersSet) {
359 final FilesystemAlterationListener[] res = new FilesystemAlterationListener[listeners.length];
360 System.arraycopy(listeners, 0, res, 0, res.length);
361 return res;
362 }
363 }
364 }