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 }