    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    }