001/*
002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     bstefanescu
011 *
012 * $Id$
013 */
014
015package org.nuxeo.runtime.deploy;
016
017import java.io.File;
018import java.io.IOException;
019import java.util.Hashtable;
020import java.util.Timer;
021import java.util.TimerTask;
022import java.util.Map;
023
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026import org.nuxeo.common.collections.ListenerList;
027
028/**
029 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
030 */
031public class FileChangeNotifier {
032
033    private static final Log log = LogFactory.getLog(FileChangeNotifier.class);
034
035    private final ListenerList listeners = new ListenerList();
036
037    private final Timer timer = new Timer("FileChangeNotifier");
038
039    private final Map<String, FileEntry> files = new Hashtable<String, FileEntry>();
040
041    public void start(int startAfter, int interval) {
042        timer.scheduleAtFixedRate(new WatchTask(), startAfter, interval);
043    }
044
045    public void start() {
046        start(10000, 2000);
047    }
048
049    public void stop() {
050        timer.cancel();
051        timer.purge();
052    }
053
054    public String watch(File file) throws IOException {
055        FileEntry entry = new FileEntry(file);
056        files.put(entry.id, entry);
057        return entry.id;
058    }
059
060    public String watch(String id, File file) throws IOException {
061        files.put(id, new FileEntry(id, file));
062        return id;
063    }
064
065    public void unwatch(File file) throws IOException {
066        files.remove(new FileEntry(file).id);
067    }
068
069    public void unwatch(String id, File file) throws IOException {
070        files.remove(new FileEntry(id, file).id);
071    }
072
073    public void addListener(FileChangeListener listener) {
074        listeners.add(listener);
075    }
076
077    public void removeListener(FileChangeListener listener) {
078        listeners.remove(listener);
079    }
080
081    protected void fireNotification(FileEntry entry) {
082        long tm = System.currentTimeMillis();
083        for (Object listener : listeners.getListeners()) {
084            ((FileChangeListener) listener).fileChanged(entry, tm);
085        }
086    }
087
088    class WatchTask extends TimerTask {
089        @Override
090        public void run() {
091            // make a copy to avoid concurrent modifs if a listener is
092            // unwatching a file
093            FileEntry[] entries = files.values().toArray(new FileEntry[files.size()]);
094            for (FileEntry entry : entries) {
095                long lastModified = entry.file.lastModified();
096                if (entry.lastModified < lastModified) {
097                    fireNotification(entry);
098                    entry.lastModified = lastModified;
099                }
100            }
101        }
102    }
103
104    public class FileEntry {
105        public final String id;
106
107        public final File file;
108
109        public long lastModified;
110
111        FileEntry(String id, File file) throws IOException {
112            this.file = file.getCanonicalFile();
113            lastModified = file.lastModified();
114            this.id = id == null ? file.getAbsolutePath() : id;
115        }
116
117        FileEntry(File file) throws IOException {
118            this(null, file);
119        }
120
121        @Override
122        public boolean equals(Object obj) {
123            if (obj == null) {
124                return false;
125            }
126            if (obj.getClass() == FileEntry.class) {
127                return id.equals(((FileEntry) obj).id);
128            }
129            return false;
130        }
131
132        @Override
133        public int hashCode() {
134            return id != null ? id.hashCode() : 0;
135        }
136
137        @Override
138        public String toString() {
139            return id;
140        }
141    }
142
143}