blob: d20d02c625630acb91ac5245e2998ec530306f28 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.jci.monitor;
import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Implementation of a FilesystemAlterationObserver
*
* @author tcurdt
*/
public class FilesystemAlterationObserverImpl implements FilesystemAlterationObserver {
private final Log log = LogFactory.getLog(FilesystemAlterationObserverImpl.class);
private interface MonitorFile {
long lastModified();
MonitorFile[] listFiles();
boolean isDirectory();
boolean exists();
String getName();
}
private final static class MonitorFileImpl implements MonitorFile {
private final File file;
public MonitorFileImpl( final File pFile ) {
file = pFile;
}
public boolean exists() {
return file.exists();
}
public MonitorFile[] listFiles() {
final File[] children = file.listFiles();
if (children == null) { // not a directory or IOError (e.g. protection issue)
return new MonitorFile[0];
}
final MonitorFile[] providers = new MonitorFile[children.length];
for (int i = 0; i < providers.length; i++) {
providers[i] = new MonitorFileImpl(children[i]);
}
return providers;
}
public String getName() {
return file.getName();
}
public boolean isDirectory() {
return file.isDirectory();
}
public long lastModified() {
return file.lastModified();
}
@Override
public String toString() {
return file.toString();
}
}
private final class Entry {
private final static int TYPE_UNKNOWN = 0;
private final static int TYPE_FILE = 1;
private final static int TYPE_DIRECTORY = 2;
private final MonitorFile file;
private long lastModified = -1;
private int lastType = TYPE_UNKNOWN;
private Map<String, Entry> children = new HashMap<String, Entry>();
public Entry(final MonitorFile pFile) {
file = pFile;
}
public String getName() {
return file.getName();
}
@Override
public String toString() {
return file.toString();
}
private void compareChildren() {
if (!file.isDirectory()) {
return;
}
final MonitorFile[] files = file.listFiles();
final Set<Entry> deleted = new HashSet<Entry>(children.values());
for (MonitorFile f : files) {
final String name = f.getName();
final Entry entry = children.get(name);
if (entry != null) {
// already recognized as child
deleted.remove(entry);
if(entry.needsToBeDeleted()) {
// we have to delete this one
children.remove(name);
}
} else {
// a new child
final Entry newChild = new Entry(f);
children.put(name, newChild);
newChild.needsToBeDeleted();
}
}
// the ones not found on disk anymore
for (Entry entry : deleted) {
entry.deleteChildrenAndNotify();
children.remove(entry.getName());
}
}
private void deleteChildrenAndNotify() {
for (Entry entry : children.values()) {
entry.deleteChildrenAndNotify();
}
children.clear();
if(lastType == TYPE_DIRECTORY) {
notifyOnDirectoryDelete(this);
} else if (lastType == TYPE_FILE) {
notifyOnFileDelete(this);
}
}
public boolean needsToBeDeleted() {
if (!file.exists()) {
// deleted or has never existed yet
// log.debug(file + " does not exist or has been deleted");
deleteChildrenAndNotify();
// mark to be deleted by parent
return true;
} else {
// exists
final long currentModified = file.lastModified();
if (currentModified != lastModified) {
// last modified has changed
lastModified = currentModified;
// log.debug(file + " has new last modified");
// types only changes when also the last modified changes
final int newType = (file.isDirectory()?TYPE_DIRECTORY:TYPE_FILE);
if (lastType != newType) {
// the type has changed
// log.debug(file + " has a new type");
deleteChildrenAndNotify();
lastType = newType;
// and then an add as the new type
if (newType == TYPE_DIRECTORY) {
notifyOnDirectoryCreate(this);
compareChildren();
} else {
notifyOnFileCreate(this);
}
return false;
}
if (newType == TYPE_DIRECTORY) {
notifyOnDirectoryChange(this);
compareChildren();
} else {
notifyOnFileChange(this);
}
return false;
} else {
// so exists and has not changed
// log.debug(file + " does exist and has not changed");
compareChildren();
return false;
}
}
}
public MonitorFile getFile() {
return file;
}
public void markNotChanged() {
lastModified = file.lastModified();
}
}
private final File rootDirectory;
private final Entry rootEntry;
private FilesystemAlterationListener[] listeners = new FilesystemAlterationListener[0];
private Set<FilesystemAlterationListener> listenersSet = new HashSet<FilesystemAlterationListener>();
public FilesystemAlterationObserverImpl( final File pRootDirectory ) {
rootDirectory = pRootDirectory;
rootEntry = new Entry(new MonitorFileImpl(pRootDirectory));
}
private void notifyOnStart() {
log.debug("onStart " + rootEntry);
for (FilesystemAlterationListener listener : listeners) {
listener.onStart(this);
}
}
private void notifyOnStop() {
log.debug("onStop " + rootEntry);
for (FilesystemAlterationListener listener : listeners) {
listener.onStop(this);
}
}
private void notifyOnFileCreate( final Entry pEntry ) {
log.debug("onFileCreate " + pEntry);
for (FilesystemAlterationListener listener : listeners) {
listener.onFileCreate(((MonitorFileImpl)pEntry.getFile()).file );
}
}
private void notifyOnFileChange( final Entry pEntry ) {
log.debug("onFileChange " + pEntry);
for (FilesystemAlterationListener listener : listeners) {
listener.onFileChange(((MonitorFileImpl)pEntry.getFile()).file );
}
}
private void notifyOnFileDelete( final Entry pEntry ) {
log.debug("onFileDelete " + pEntry);
for (FilesystemAlterationListener listener : listeners) {
listener.onFileDelete(((MonitorFileImpl)pEntry.getFile()).file );
}
}
private void notifyOnDirectoryCreate( final Entry pEntry ) {
log.debug("onDirectoryCreate " + pEntry);
for (FilesystemAlterationListener listener : listeners) {
listener.onDirectoryCreate(((MonitorFileImpl)pEntry.getFile()).file );
}
}
private void notifyOnDirectoryChange( final Entry pEntry ) {
log.debug("onDirectoryChange " + pEntry);
for (FilesystemAlterationListener listener : listeners) {
listener.onDirectoryChange(((MonitorFileImpl)pEntry.getFile()).file );
}
}
private void notifyOnDirectoryDelete( final Entry pEntry ) {
log.debug("onDirectoryDelete " + pEntry);
for (FilesystemAlterationListener listener : listeners) {
listener.onDirectoryDelete(((MonitorFileImpl)pEntry.getFile()).file );
}
}
private void checkEntries() {
if(rootEntry.needsToBeDeleted()) {
// root not existing
rootEntry.lastType = Entry.TYPE_UNKNOWN;
}
}
public void checkAndNotify() {
synchronized(listenersSet) {
if (listeners.length == 0) {
return;
}
notifyOnStart();
checkEntries();
notifyOnStop();
}
}
public File getRootDirectory() {
return rootDirectory;
}
public void addListener( final FilesystemAlterationListener pListener ) {
synchronized(listenersSet) {
if (listenersSet.add(pListener)) {
listeners = createArrayFromSet();
}
}
}
public void removeListener( final FilesystemAlterationListener pListener ) {
synchronized(listenersSet) {
if (listenersSet.remove(pListener)) {
listeners = createArrayFromSet();
}
}
}
private FilesystemAlterationListener[] createArrayFromSet() {
final FilesystemAlterationListener[] newListeners = new FilesystemAlterationListener[listenersSet.size()];
listenersSet.toArray(newListeners);
return newListeners;
}
public FilesystemAlterationListener[] getListeners() {
synchronized(listenersSet) {
final FilesystemAlterationListener[] res = new FilesystemAlterationListener[listeners.length];
System.arraycopy(listeners, 0, res, 0, res.length);
return res;
}
}
/*
public static void main( String[] args ) {
final FilesystemAlterationObserverImpl observer = new FilesystemAlterationObserverImpl(new File(args[0]));
while(true) {
observer.checkEntries();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
*/
}