This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

View | Details | Raw Unified | Return to bug 170862
Collapse All | Expand All

(-)a/masterfs/manifest.mf (-1 / +1 lines)
Lines 1-7 Link Here
1
Manifest-Version: 1.0
1
Manifest-Version: 1.0
2
OpenIDE-Module: org.netbeans.modules.masterfs/2
2
OpenIDE-Module: org.netbeans.modules.masterfs/2
3
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/masterfs/resources/Bundle.properties
3
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/masterfs/resources/Bundle.properties
4
OpenIDE-Module-Specification-Version: 2.17
4
OpenIDE-Module-Specification-Version: 2.18
5
AutoUpdate-Show-In-Client: false
5
AutoUpdate-Show-In-Client: false
6
AutoUpdate-Essential-Module: true
6
AutoUpdate-Essential-Module: true
7
7
(-)a/masterfs/nbproject/project.xml (-1 / +1 lines)
Lines 60-66 Link Here
60
                    <build-prerequisite/>
60
                    <build-prerequisite/>
61
                    <compile-dependency/>
61
                    <compile-dependency/>
62
                    <run-dependency>
62
                    <run-dependency>
63
                        <specification-version>6.2</specification-version>
63
                        <specification-version>7.28</specification-version>
64
                    </run-dependency>
64
                    </run-dependency>
65
                </dependency>
65
                </dependency>
66
                <dependency>
66
                <dependency>
(-)a/masterfs/src/org/netbeans/modules/masterfs/filebasedfs/fileobjects/BaseFileObj.java (+16 lines)
Lines 203-208 Link Here
203
    public final boolean isRoot() {
203
    public final boolean isRoot() {
204
        return false;
204
        return false;
205
    }
205
    }
206
207
    public final java.util.Date lastModified() {
208
        final File f = getFileName().getFile();
209
        final long lastModified = f.lastModified();
210
        return new Date(lastModified);
211
    }
206
     
212
     
207
    @Override
213
    @Override
208
    public final FileObject move(FileLock lock, FileObject target, String name, String ext) throws IOException {
214
    public final FileObject move(FileLock lock, FileObject target, String name, String ext) throws IOException {
Lines 376-381 Link Here
376
        getEventSupport().remove(FileChangeListener.class, fcl);
382
        getEventSupport().remove(FileChangeListener.class, fcl);
377
    }
383
    }
378
384
385
    @Override
386
    public void addRecursiveListener(FileChangeListener fcl) {
387
        addFileChangeListener(fcl);
388
    }
389
    
390
    @Override
391
    public void removeRecursiveListener(FileChangeListener fcl) {
392
        removeFileChangeListener(fcl);
393
    }
394
379
    private Enumeration getListeners() {
395
    private Enumeration getListeners() {
380
        if (eventSupport == null) {
396
        if (eventSupport == null) {
381
            return Enumerations.empty();
397
            return Enumerations.empty();
(-)a/masterfs/src/org/netbeans/modules/masterfs/filebasedfs/fileobjects/FileObj.java (-5 lines)
Lines 210-220 Link Here
210
        return super.canWrite();
210
        return super.canWrite();
211
    }
211
    }
212
        
212
        
213
    public final Date lastModified() {
214
        final File f = getFileName().getFile();
215
        return new Date(f.lastModified());
216
    }
217
218
    final void setLastModified(long lastModified) {
213
    final void setLastModified(long lastModified) {
219
        if (this.lastModified != 0) { // #130998 - don't set when already invalidated
214
        if (this.lastModified != 0) { // #130998 - don't set when already invalidated
220
            if (this.lastModified != -1 && !realLastModifiedCached) {
215
            if (this.lastModified != -1 && !realLastModifiedCached) {
(-)70eda1b69061 (+249 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * If you wish your version of this file to be governed by only the CDDL
25
 * or only the GPL Version 2, indicate your decision by adding
26
 * "[Contributor] elects to include this software in this distribution
27
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
28
 * single choice of license, a recipient has the option to distribute
29
 * your version of this file under either the CDDL, the GPL Version 2 or
30
 * to extend the choice of license to its licensees as provided above.
31
 * However, if you add GPL Version 2 code and therefore, elected the GPL
32
 * Version 2 license, then the option applies only if the new code is
33
 * made subject to such option by the copyright holder.
34
 *
35
 * Contributor(s):
36
 *
37
 * Portions Copyrighted 2009 Sun Microsystems, Inc.
38
 */
39
40
package org.netbeans.modules.masterfs.filebasedfs.fileobjects;
41
42
import java.io.File;
43
import java.util.Collection;
44
import java.util.Enumeration;
45
import java.util.HashSet;
46
import java.util.Set;
47
import java.util.concurrent.CopyOnWriteArraySet;
48
import java.util.logging.Level;
49
import java.util.logging.Logger;
50
import org.netbeans.modules.masterfs.filebasedfs.fileobjects.FileObjectFactory.Caller;
51
import org.openide.filesystems.FileAttributeEvent;
52
import org.openide.filesystems.FileChangeListener;
53
import org.openide.filesystems.FileEvent;
54
import org.openide.filesystems.FileObject;
55
import org.openide.filesystems.FileRenameEvent;
56
57
/** Keeps list of fileobjects under given root. Adapted from Jan Lahoda's work
58
 * in issue 168237
59
 */
60
final class FileObjectKeeper implements FileChangeListener {
61
    private static final Logger LOG = Logger.getLogger(FileObjectKeeper.class.getName());
62
63
    private Set<FileObject> kept;
64
    private Collection<FileChangeListener> listeners;
65
    private final FolderObj root;
66
    private long timeStamp;
67
68
    public FileObjectKeeper(FolderObj root) {
69
        this.root = root;
70
    }
71
72
    public synchronized void addRecursiveListener(FileChangeListener fcl) {
73
        if (listeners == null) {
74
            listeners = new CopyOnWriteArraySet<FileChangeListener>();
75
        }
76
        if (listeners.isEmpty()) {
77
            listenToAll();
78
        }
79
        listeners.add(fcl);
80
    }
81
82
    public synchronized void removeRecursiveListener(FileChangeListener fcl) {
83
        if (listeners == null) {
84
            return;
85
        }
86
        listeners.remove(fcl);
87
        if (listeners.isEmpty()) {
88
            listenNoMore();
89
        }
90
    }
91
92
    public void init(long previous, FileObjectFactory factory, boolean expected) {
93
        File file = root.getFileName().getFile();
94
        File[] arr = file.listFiles();
95
        long ts = 0;
96
        if (arr != null) {
97
            for (File f : arr) {
98
                if (f.isDirectory()) {
99
                    continue;
100
                }
101
                long lm = f.lastModified();
102
                LOG.log(Level.FINE, "  check {0} for {1}", new Object[] { lm, f });
103
                if (lm > ts) {
104
                    ts = lm;
105
                }
106
                if (lm > previous && factory != null) {
107
                    final BaseFileObj prevFO = factory.getCachedOnly(f);
108
                    if (prevFO == null) {
109
                        BaseFileObj who = factory.getValidFileObject(f, Caller.Others);
110
                        LOG.log(Level.FINE, "External change detected {0}", who);
111
                        who.fireFileChangedEvent(expected);
112
                    } else {
113
                        prevFO.refresh(expected, true);
114
                    }
115
                }
116
            }
117
        }
118
        timeStamp = ts;
119
        LOG.log(Level.FINE, "Testing {0}, time {1}", new Object[] { file, timeStamp });
120
    }
121
122
    private final void listenToAll() {
123
        assert Thread.holdsLock(this);
124
        assert kept == null;
125
        kept = new HashSet<FileObject>();
126
        root.addFileChangeListener(this);
127
        Enumeration<? extends FileObject> en = root.getChildren(true);
128
        while (en.hasMoreElements()) {
129
            FileObject fo = en.nextElement();
130
            if (fo instanceof FolderObj) {
131
                FolderObj obj = (FolderObj)fo;
132
                obj.addFileChangeListener(this);
133
                kept.add(obj);
134
                obj.getKeeper();
135
            }
136
        }
137
    }
138
139
    private final void listenNoMore() {
140
        assert Thread.holdsLock(this);
141
142
        root.removeFileChangeListener(this);
143
        Set<FileObject> k = kept;
144
        if (k != null) {
145
            for (FileObject fo : k) {
146
                fo.removeFileChangeListener(this);
147
            }
148
            kept = null;
149
        }
150
    }
151
152
    public void fileFolderCreated(FileEvent fe) {
153
        Collection<FileChangeListener> arr = listeners;
154
        if (arr == null) {
155
            return;
156
        }
157
        final FileObject f = fe.getFile();
158
        if (f instanceof FolderObj) {
159
            synchronized (this) {
160
                kept.add(f);
161
                f.addFileChangeListener(this);
162
                Enumeration<? extends FileObject> en = f.getChildren(true);
163
                while (en.hasMoreElements()) {
164
                    FileObject fo = en.nextElement();
165
                    if (fo instanceof FolderObj) {
166
                        fo.addFileChangeListener(this);
167
                    }
168
                }
169
            }
170
        }
171
        for (FileChangeListener l : arr) {
172
            l.fileFolderCreated(fe);
173
        }
174
    }
175
176
    public void fileDataCreated(FileEvent fe) {
177
        Collection<FileChangeListener> arr = listeners;
178
        if (arr == null) {
179
            return;
180
        }
181
        for (FileChangeListener l : arr) {
182
            l.fileDataCreated(fe);
183
        }
184
    }
185
186
    public void fileChanged(FileEvent fe) {
187
        Collection<FileChangeListener> arr = listeners;
188
        if (arr == null) {
189
            return;
190
        }
191
        for (FileChangeListener l : arr) {
192
            l.fileChanged(fe);
193
        }
194
    }
195
196
    public void fileDeleted(FileEvent fe) {
197
        Collection<FileChangeListener> arr = listeners;
198
        if (arr == null) {
199
            return;
200
        }
201
        final FileObject f = fe.getFile();
202
        if (f.isFolder() && fe.getSource() == f && f != root) {
203
            // there will be another event for parent folder
204
            return;
205
        }
206
207
        for (FileChangeListener l : arr) {
208
            l.fileDeleted(fe);
209
        }
210
        if (f instanceof FolderObj) {
211
            synchronized (this) {
212
                if (kept != null) {
213
                    kept.remove(f);
214
                }
215
                f.removeFileChangeListener(this);
216
            }
217
        }
218
    }
219
220
    public void fileRenamed(FileRenameEvent fe) {
221
        Collection<FileChangeListener> arr = listeners;
222
        if (arr == null) {
223
            return;
224
        }
225
        final FileObject f = fe.getFile();
226
        if (f.isFolder() && fe.getSource() == f && f != root) {
227
            // there will be another event for parent folder
228
            return;
229
        }
230
        for (FileChangeListener l : arr) {
231
            l.fileRenamed(fe);
232
        }
233
    }
234
235
    public void fileAttributeChanged(FileAttributeEvent fe) {
236
        Collection<FileChangeListener> arr = listeners;
237
        if (arr == null) {
238
            return;
239
        }
240
        for (FileChangeListener l : arr) {
241
            l.fileAttributeChanged(fe);
242
        }
243
    }
244
245
    long childrenLastModified() {
246
        return timeStamp;
247
    }
248
249
}
(-)a/masterfs/src/org/netbeans/modules/masterfs/filebasedfs/fileobjects/FolderObj.java (-6 / +28 lines)
Lines 48-54 Link Here
48
import java.io.OutputStream;
48
import java.io.OutputStream;
49
import java.io.SyncFailedException;
49
import java.io.SyncFailedException;
50
import java.util.ArrayList;
50
import java.util.ArrayList;
51
import java.util.Date;
52
import java.util.HashSet;
51
import java.util.HashSet;
53
import java.util.Iterator;
52
import java.util.Iterator;
54
import java.util.LinkedList;
53
import java.util.LinkedList;
Lines 69-74 Link Here
69
import org.netbeans.modules.masterfs.filebasedfs.utils.FileChangedManager;
68
import org.netbeans.modules.masterfs.filebasedfs.utils.FileChangedManager;
70
import org.netbeans.modules.masterfs.filebasedfs.utils.FileInfo;
69
import org.netbeans.modules.masterfs.filebasedfs.utils.FileInfo;
71
import org.netbeans.modules.masterfs.providers.ProvidedExtensions;
70
import org.netbeans.modules.masterfs.providers.ProvidedExtensions;
71
import org.openide.filesystems.FileChangeListener;
72
import org.openide.filesystems.FileLock;
72
import org.openide.filesystems.FileLock;
73
import org.openide.filesystems.FileObject;
73
import org.openide.filesystems.FileObject;
74
import org.openide.util.Mutex;
74
import org.openide.util.Mutex;
Lines 82-88 Link Here
82
    private static final Mutex mutex = new Mutex(FolderObj.mp);
82
    private static final Mutex mutex = new Mutex(FolderObj.mp);
83
83
84
    private FolderChildrenCache folderChildren;
84
    private FolderChildrenCache folderChildren;
85
    boolean valid = true;    
85
    boolean valid = true;
86
    private FileObjectKeeper keeper;
86
87
87
    /**
88
    /**
88
     * Creates a new instance of FolderImpl
89
     * Creates a new instance of FolderImpl
Lines 333-338 Link Here
333
    public void refreshImpl(final boolean expected, boolean fire) {
334
    public void refreshImpl(final boolean expected, boolean fire) {
334
        final ChildrenCache cache = getChildrenCache();
335
        final ChildrenCache cache = getChildrenCache();
335
        final Mutex.Privileged mutexPrivileged = cache.getMutexPrivileged();
336
        final Mutex.Privileged mutexPrivileged = cache.getMutexPrivileged();
337
        final long previous = keeper == null ? -1 : keeper.childrenLastModified();
336
338
337
        Set oldChildren = null;
339
        Set oldChildren = null;
338
        Map refreshResult = null;
340
        Map refreshResult = null;
Lines 412-417 Link Here
412
                fireFileDeletedEvent(expected);
414
                fireFileDeletedEvent(expected);
413
            }
415
            }
414
        }
416
        }
417
418
        if (previous != -1) {
419
            assert keeper != null;
420
            keeper.init(previous, factory, expected);
421
        }
415
    }
422
    }
416
423
417
    @Override
424
    @Override
Lines 487-496 Link Here
487
        throw new IOException(getPath());
494
        throw new IOException(getPath());
488
    }
495
    }
489
496
490
    public final java.util.Date lastModified() {
491
        final File f = getFileName().getFile();
492
        return new Date(f.lastModified());
493
    }
494
497
495
    public final FileLock lock() throws IOException {
498
    public final FileLock lock() throws IOException {
496
        return new FileLock();
499
        return new FileLock();
Lines 508-513 Link Here
508
        return folderChildren;
511
        return folderChildren;
509
    }
512
    }
510
513
514
    synchronized FileObjectKeeper getKeeper() {
515
        if (keeper == null) {
516
            keeper = new FileObjectKeeper(this);
517
            keeper.init(-1, null, false);
518
        }
519
        return keeper;
520
    }
521
522
    @Override
523
    public final void addRecursiveListener(FileChangeListener fcl) {
524
        getKeeper().addRecursiveListener(fcl);
525
    }
526
527
    @Override
528
    public final void removeRecursiveListener(FileChangeListener fcl) {
529
        getKeeper().removeRecursiveListener(fcl);
530
    }
531
532
511
    public final class FolderChildrenCache implements ChildrenCache {
533
    public final class FolderChildrenCache implements ChildrenCache {
512
        public final ChildrenSupport ch = new ChildrenSupport();
534
        public final ChildrenSupport ch = new ChildrenSupport();
513
535
(-)a/masterfs/src/org/netbeans/modules/masterfs/filebasedfs/fileobjects/ReplaceForSerialization.java (-4 lines)
Lines 91-100 Link Here
91
            return false;
91
            return false;
92
        }
92
        }
93
93
94
        public Date lastModified() {
95
            return new Date(0L);
96
        }
97
98
        /* Test whether the file is valid. The file can be invalid if it has been deserialized
94
        /* Test whether the file is valid. The file can be invalid if it has been deserialized
99
        * and the file no longer exists on disk; or if the file has been deleted.
95
        * and the file no longer exists on disk; or if the file has been deleted.
100
        *
96
        *
(-)a/masterfs/test/unit/src/org/netbeans/modules/masterfs/filebasedfs/FileUtilTest.java (+457 lines)
Lines 53-58 Link Here
53
import java.util.List;
53
import java.util.List;
54
import java.util.Map;
54
import java.util.Map;
55
import java.util.Random;
55
import java.util.Random;
56
import junit.framework.Test;
56
import org.netbeans.junit.NbTestCase;
57
import org.netbeans.junit.NbTestCase;
57
import org.netbeans.junit.RandomlyFails;
58
import org.netbeans.junit.RandomlyFails;
58
import org.openide.filesystems.FileAttributeEvent;
59
import org.openide.filesystems.FileAttributeEvent;
Lines 239-244 Link Here
239
        assertGC("FileChangeListener not collected.", ref);
240
        assertGC("FileChangeListener not collected.", ref);
240
    }
241
    }
241
242
243
    public void testAddRecursiveListener() throws IOException, InterruptedException {
244
        clearWorkDir();
245
        File rootF = getWorkDir();
246
        File dirF = new File(rootF, "dir");
247
        File fileF = new File(dirF, "subdir");
248
249
        // adding listeners
250
        TestFileChangeListener fcl = new TestFileChangeListener();
251
        FileUtil.addRecursiveListener(fcl, fileF);
252
        try {
253
            FileUtil.addRecursiveListener(fcl, fileF);
254
            fail("Should not be possible to add listener for the same path.");
255
        } catch (IllegalArgumentException iae) {
256
            // ok
257
        }
258
        TestFileChangeListener fcl2 = new TestFileChangeListener();
259
        try {
260
            FileUtil.removeRecursiveListener(fcl2, fileF);
261
            fail("Should not be possible to remove listener which is not registered.");
262
        } catch (IllegalArgumentException iae) {
263
            // ok
264
        }
265
        FileUtil.addRecursiveListener(fcl2, fileF);
266
267
        // creation
268
        final FileObject rootFO = FileUtil.toFileObject(rootF);
269
        FileObject dirFO = rootFO.createFolder("dir");
270
        assertEquals("Event fired when just parent dir created.", 0, fcl.checkAll());
271
        FileObject fileFO = FileUtil.createData(dirFO, "subdir/subsubdir/file");
272
        assertEquals("Event not fired when file was created.", 2, fcl.check(EventType.FOLDER_CREATED));
273
        assertEquals("Event not fired when file was created.", 2, fcl2.check(EventType.FOLDER_CREATED));
274
        FileObject fileFO2 = FileUtil.createData(dirFO, "subdir/subsubdir/file2");
275
        assertEquals("Event not fired when file was created.", 2, fcl.check(EventType.DATA_CREATED));
276
        assertEquals("Event not fired when file was created.", 2, fcl2.check(EventType.DATA_CREATED));
277
        FileObject fileAFO = FileUtil.createData(dirFO, "fileA");
278
        assertEquals("No other events should be fired.", 0, fcl.checkAll());
279
280
        // remove listener
281
        FileUtil.removeRecursiveListener(fcl2, fileF);
282
        fcl2.disabled = true;
283
        assertEquals("No other events should be fired.", 0, fcl2.checkAll());
284
285
        // modification
286
        fileFO.getOutputStream().close();
287
        fileFO.getOutputStream().close();
288
        assertEquals("Event not fired when file was modified.", 2, fcl.check(EventType.CHANGED));
289
        // no event fired when other file modified
290
        fileAFO.getOutputStream().close();
291
        assertEquals("No other events should be fired.", 0, fcl.checkAll());
292
293
        // deletion
294
        fileFO.delete();
295
        assertEquals("Event not fired when file deleted.", 1, fcl.check(EventType.DELETED));
296
        dirFO.delete();
297
        assertEquals("Event not fired when parent dir deleted.", 1, fcl.checkAll());
298
        dirFO = rootFO.createFolder("dir");
299
        fileFO = FileUtil.createData(dirFO, "subdir/subsubdir/file");
300
        assertEquals("Event not fired when file was created.", 1, fcl.check(EventType.DATA_CREATED));
301
        assertEquals("Event not fired when dirs created.", 2, fcl.check(EventType.FOLDER_CREATED));
302
        dirFO.delete();
303
        assertEquals("Event not fired when parent dir deleted.", 1, fcl.check(EventType.DELETED));
304
        assertEquals("No other events should be fired.", 0, fcl.checkAll());
305
306
        // atomic action
307
        FileUtil.runAtomicAction(new Runnable() {
308
309
            public void run() {
310
                FileObject dirFO;
311
                try {
312
                    dirFO = rootFO.createFolder("dir");
313
                    rootFO.createFolder("fakedir");
314
                    rootFO.setAttribute("fake", "fake");
315
                    rootFO.createData("fakefile");
316
                    FileUtil.createData(dirFO, "subdir/subsubdir/file");
317
                } catch (IOException ex) {
318
                    throw new RuntimeException(ex);
319
                }
320
321
            }
322
        });
323
        assertEquals("Notifying the folder creation only.", 1, fcl.check(EventType.FOLDER_CREATED));
324
        assertEquals("No other events should be fired.", 0, fcl.checkAll());
325
326
        // rename
327
        dirFO = FileUtil.toFileObject(dirF);
328
        fileFO = FileUtil.toFileObject(fileF);
329
        FileLock lock = dirFO.lock();
330
        dirFO.rename(lock, "dirRenamed", null);
331
        lock.releaseLock();
332
        assertEquals("Event fired when parent dir renamed.", 0, fcl.checkAll());
333
        lock = fileFO.lock();
334
        fileFO.rename(lock, "fileRenamed", null);
335
        lock.releaseLock();
336
        assertEquals("Renamed event not fired.", 1, fcl.check(EventType.RENAMED));
337
        assertEquals("No other events should be fired.", 0, fcl.checkAll());
338
339
        // disk changes
340
        dirF.mkdir();
341
        final File subdir = new File(fileF, "subdir");
342
        subdir.mkdirs();
343
        final File newfile = new File(subdir, "newfile");
344
        assertTrue(newfile.createNewFile());
345
        FileUtil.refreshAll();
346
        assertEquals("Event not fired when file was created.", 1, fcl.check(EventType.FOLDER_CREATED));
347
        Thread.sleep(1000); // make sure timestamp changes
348
        new FileOutputStream(newfile).close();
349
        FileUtil.refreshAll();
350
        assertEquals("Event not fired when file was modified.", 1, fcl.check(EventType.CHANGED));
351
        assertEquals("Attribute change event not fired (see #129178).", 3, fcl.check(EventType.ATTRIBUTE_CHANGED));
352
        newfile.delete();
353
        FileUtil.refreshAll();
354
        assertEquals("Event not fired when file deleted.", 1, fcl.check(EventType.DELETED));
355
        assertEquals("No other events should be fired.", 0, fcl.checkAll());
356
357
        // disk changes #66444
358
        File fileX = new File(subdir, "oscilating.file");
359
        for (int cntr = 0; cntr < 50; cntr++) {
360
            fileX.getParentFile().mkdirs();
361
            new FileOutputStream(fileX).close();
362
            FileUtil.refreshAll();
363
            assertEquals("Event not fired when file was created; count=" + cntr, 1, fcl.check(EventType.DATA_CREATED));
364
            fileX.delete();
365
            FileUtil.refreshAll();
366
            assertEquals("Event not fired when file deleted; count=" + cntr, 1, fcl.check(EventType.DELETED));
367
        }
368
369
        // removed listener
370
        assertEquals("No other events should be fired in removed listener.", 0, fcl2.checkAll());
371
372
        // weakness
373
        WeakReference ref = new WeakReference(fcl);
374
        fcl = null;
375
        assertGC("FileChangeListener not collected.", ref);
376
    }
377
242
    /** Tests FileChangeListener on folder. As declared in
378
    /** Tests FileChangeListener on folder. As declared in
243
     * {@link FileUtil#addFileChangeListener(org.openide.filesystems.FileChangeListener, java.io.File) }
379
     * {@link FileUtil#addFileChangeListener(org.openide.filesystems.FileChangeListener, java.io.File) }
244
     * - fileFolderCreated event is fired when the folder is created or a child folder created
380
     * - fileFolderCreated event is fired when the folder is created or a child folder created
Lines 309-320 Link Here
309
        assertEquals("No other events should be fired.", 0, fcl.checkAll());
445
        assertEquals("No other events should be fired.", 0, fcl.checkAll());
310
    }
446
    }
311
447
448
    /** Tests FileObject.addRecursiveListener on folder as declared in
449
     * {@link FileObject#addRecursiveListener(org.openide.filesystems.FileChangeListener) }.
450
     * It is expected that all events from sub folders are delivered just once.
451
     */
452
    public void testAddRecursiveListenerToFileObjectFolder() throws Exception {
453
        checkFolderRecursiveListener(false);
454
    }
455
456
    /** Tests FileUtil.addRecursiveListener on folder as declared in
457
     * {@link FileUtil#addRecursiveListener(org.openide.filesystems.FileChangeListener, java.io.File) }.
458
     * It is expected that all events from sub folders are delivered just once.
459
     */
460
    public void testAddRecursiveListenerToFileFolder() throws Exception {
461
        checkFolderRecursiveListener(true);
462
    }
463
464
    /** Tests addRecursiveListener on folder either added to FileObject or File.
465
     * @param isOnFile true to add listener to java.io.File, false to FileObject
466
     */
467
    private void checkFolderRecursiveListener(boolean isOnFile) throws Exception {
468
        clearWorkDir();
469
        // test files: dir/file1, dir/subdir/subfile, dir/subdir/subsubdir/subsubfile
470
        final File rootF = getWorkDir();
471
        final File dirF = new File(rootF, "dir");
472
        File fileF = new File(dirF, "file1");
473
        File subdirF = new File(dirF, "subdir");
474
        File subfileF = new File(subdirF, "subfile");
475
        File subsubdirF = new File(subdirF, "subsubdir");
476
        File subsubfileF = new File(subsubdirF, "subsubfile");
477
478
        TestFileChangeListener fcl = new TestFileChangeListener();
479
        FileObject dirFO;
480
        if (isOnFile) {
481
            FileUtil.addRecursiveListener(fcl, dirF);
482
            dirFO = FileUtil.createFolder(dirF);
483
            assertEquals("Wrong number of events fired when folder created.", 1, fcl.check(EventType.FOLDER_CREATED));
484
        } else {
485
            dirFO = FileUtil.createFolder(dirF);
486
            dirFO.addRecursiveListener(fcl);
487
        }
488
489
        // create dir
490
        FileObject subdirFO = dirFO.createFolder("subdir");
491
        assertEquals("Wrong number of events fired when sub folder created.", 1, fcl.check(EventType.FOLDER_CREATED));
492
        FileObject subsubdirFO = subdirFO.createFolder("subsubdir");
493
        assertEquals("Wrong number of events when sub sub folder created.", 1, fcl.check(EventType.FOLDER_CREATED));
494
495
        // create file
496
        FileObject file1FO = dirFO.createData("file1");
497
        assertEquals("Wrong number of events when data created.", 1, fcl.check(EventType.DATA_CREATED));
498
        FileObject subfileFO = subdirFO.createData("subfile");
499
        assertEquals("Wrong number of events when data in sub folder created.", 1, fcl.check(EventType.DATA_CREATED));
500
        FileObject subsubfileFO = subsubdirFO.createData("subsubfile");
501
        assertEquals("Wrong number of events when data in sub sub folder created.", 1, fcl.check(EventType.DATA_CREATED));
502
503
        // modify
504
        file1FO.getOutputStream().close();
505
        assertEquals("Wrong number of events when file folder modified.", 1, fcl.check(EventType.CHANGED));
506
        subfileFO.getOutputStream().close();
507
        assertEquals("Wrong number of events when file in sub folder modified.", 1, fcl.check(EventType.CHANGED));
508
        subsubfileFO.getOutputStream().close();
509
        assertEquals("Wrong number of events when file in sub sub folder modified.", 1, fcl.check(EventType.CHANGED));
510
511
        // delete
512
        file1FO.delete();
513
        assertEquals("Wrong number of events when child file deleted.", 1, fcl.check(EventType.DELETED));
514
        subsubfileFO.delete();
515
        assertEquals("Wrong number of events when child file in sub sub folder deleted.", 1, fcl.check(EventType.DELETED));
516
        subsubdirFO.delete();
517
        assertEquals("Wrong number of events when sub sub folder deleted.", 1, fcl.check(EventType.DELETED));
518
        subfileFO.delete();
519
        assertEquals("Wrong number of events when child file in sub folder deleted.", 1, fcl.check(EventType.DELETED));
520
        subdirFO.delete();
521
        assertEquals("Wrong number of events when sub folder deleted.", 1, fcl.check(EventType.DELETED));
522
523
        // atomic action
524
        FileUtil.runAtomicAction(new Runnable() {
525
526
            public void run() {
527
                try {
528
                    FileObject rootFO = FileUtil.toFileObject(rootF);
529
                    rootFO.createFolder("fakedir");  // no events
530
                    rootFO.setAttribute("fake", "fake");  // no events
531
                    rootFO.createData("fakefile");  // no events
532
                    FileObject dirFO = FileUtil.toFileObject(dirF);
533
                    dirFO.createData("file1");
534
                    FileObject subdirFO = dirFO.createFolder("subdir");
535
                    subdirFO.createData("subfile");
536
                    FileObject subsubdirFO = subdirFO.createFolder("subsubdir");
537
                    subsubdirFO.createData("subsubfile");
538
                } catch (IOException ex) {
539
                    throw new RuntimeException(ex);
540
                }
541
542
            }
543
        });
544
        // TODO - should be 3
545
        assertEquals("Wrong number of events fired when file was created in atomic action.", 1, fcl.check(EventType.DATA_CREATED));
546
        // TODO - should be 2
547
        assertEquals("Wrong number of events fired when file was created in atomic action.", 1, fcl.check(EventType.FOLDER_CREATED));
548
        assertEquals("No other events should be fired.", 0, fcl.checkAll());
549
550
        // rename
551
        file1FO = dirFO.getFileObject("file1");
552
        subdirFO = dirFO.getFileObject("subdir");
553
        subfileFO = subdirFO.getFileObject("subfile");
554
        subsubdirFO = subdirFO.getFileObject("subsubdir");
555
        subsubfileFO = subsubdirFO.getFileObject("subsubfile");
556
        fcl.clearAll();
557
        FileLock lock = file1FO.lock();
558
        file1FO.rename(lock, "file1Renamed", null);
559
        lock.releaseLock();
560
        assertEquals("Wrong number of events when child file renamed.", 1, fcl.check(EventType.RENAMED));
561
        lock = subfileFO.lock();
562
        subfileFO.rename(lock, "subfileRenamed", null);
563
        lock.releaseLock();
564
        assertEquals("Wrong number of events when child file in sub folder renamed.", 1, fcl.check(EventType.RENAMED));
565
        lock = subsubfileFO.lock();
566
        subsubfileFO.rename(lock, "subsubfileRenamed", null);
567
        lock.releaseLock();
568
        assertEquals("Wrong number of events when child file in sub sub folder renamed.", 1, fcl.check(EventType.RENAMED));
569
        lock = subsubdirFO.lock();
570
        subsubdirFO.rename(lock, "subsubdirRenamed", null);
571
        lock.releaseLock();
572
        assertEquals("Wrong number of events when sub sub folder renamed.", 1, fcl.check(EventType.RENAMED));
573
        lock = subdirFO.lock();
574
        subdirFO.rename(lock, "subdirRenamed", null);
575
        lock.releaseLock();
576
        assertEquals("Wrong number of events when sub folder renamed.", 1, fcl.check(EventType.RENAMED));
577
        lock = dirFO.lock();
578
        dirFO.rename(lock, "dirRenamed", null);
579
        lock.releaseLock();
580
        assertEquals("Wrong number of events when sub folder renamed.", 1, fcl.check(EventType.RENAMED));
581
        lock = dirFO.lock();
582
        dirFO.rename(lock, "dir", null);
583
        lock.releaseLock();
584
        /* According to jskrivanek in http://www.netbeans.org/nonav/issues/showattachment.cgi/86910/X.diff, the rename back does not need to
585
         * fire an event. Instead the support delivers FOLDER_CREATED event:
586
        assertEquals("Wrong number of events when sub folder renamed.", 1, fcl.check(EventType.RENAMED));
587
        assertEquals("Wrong number of events when sub folder renamed.", 1, fcl.check(EventType.FOLDER_CREATED));
588
        fcl.printAll();
589
        assertEquals("No other events should be fired.", 0, fcl.checkAll());
590
         */
591
        // cleanup after rename
592
        dirFO.getFileObject("file1Renamed").delete();
593
        dirFO.getFileObject("subdirRenamed").delete();
594
        fcl.clearAll();
595
596
        // disk changes
597
        Thread.sleep(1000); // give OS same time
598
        assertTrue(subsubdirF.mkdirs());
599
        assertTrue(fileF.createNewFile());
600
        assertTrue(subfileF.createNewFile());
601
        assertTrue(subsubfileF.createNewFile());
602
        FileUtil.refreshAll();
603
        // TODO - should be 3
604
        assertEquals("Wrong number of events when file was created.", 1, fcl.check(EventType.DATA_CREATED));
605
        // TODO - should be 2
606
        assertEquals("Wrong number of events when folder created.", 1, fcl.check(EventType.FOLDER_CREATED));
607
        // TODO - should be 0
608
        assertEquals("Wrong number of Attribute change events (see #129178).", 1, fcl.check(EventType.ATTRIBUTE_CHANGED));
609
        assertEquals("No other events should be fired.", 0, fcl.checkAll());
610
611
        Thread.sleep(1000); // make sure timestamp changes
612
        new FileOutputStream(subsubfileF).close();
613
        new FileOutputStream(subfileF).close();
614
        new FileOutputStream(fileF).close();
615
        FileUtil.refreshAll();
616
        assertEquals("Wrong number of events when file was modified.", 3, fcl.check(EventType.CHANGED));
617
        assertEquals("Wrong number of Attribute change events (see #129178).", 7, fcl.check(EventType.ATTRIBUTE_CHANGED));
618
619
        assertTrue(subsubfileF.delete());
620
        assertTrue(subsubdirF.delete());
621
        assertTrue(subfileF.delete());
622
        assertTrue(subdirF.delete());
623
        assertTrue(fileF.delete());
624
        FileUtil.refreshAll();
625
        assertEquals("Wrong number of events when file deleted.", 5, fcl.check(EventType.DELETED));
626
627
        // delete folder itself
628
        dirFO.delete();
629
        assertEquals("Wrong number of events when folder deleted.", 1, fcl.check(EventType.DELETED));
630
    }
631
632
    /** Tests recursive FileChangeListener on File.
633
     * @see FileUtil#addRecursiveListener(org.openide.filesystems.FileChangeListener, java.io.File)
634
     */
635
    public void testAddRecursiveListenerToFile() throws IOException, InterruptedException {
636
        clearWorkDir();
637
        File rootF = getWorkDir();
638
        File dirF = new File(rootF, "dir");
639
        File fileF = new File(dirF, "file");
640
641
        // adding listeners
642
        TestFileChangeListener fcl = new TestFileChangeListener();
643
        FileUtil.addRecursiveListener(fcl, fileF);
644
        try {
645
            FileUtil.addRecursiveListener(fcl, fileF);
646
            fail("Should not be possible to add listener for the same path.");
647
        } catch (IllegalArgumentException iae) {
648
            // ok
649
        }
650
        TestFileChangeListener fcl2 = new TestFileChangeListener();
651
        try {
652
            FileUtil.removeRecursiveListener(fcl2, fileF);
653
            fail("Should not be possible to remove listener which is not registered.");
654
        } catch (IllegalArgumentException iae) {
655
            // ok
656
        }
657
        FileUtil.addRecursiveListener(fcl2, fileF);
658
659
        // creation
660
        final FileObject rootFO = FileUtil.toFileObject(rootF);
661
        FileObject dirFO = rootFO.createFolder("dir");
662
        assertEquals("Event fired when just parent dir created.", 0, fcl.checkAll());
663
        FileObject fileFO = dirFO.createData("file");
664
        assertEquals("Wrong number of events when file was created.", 1, fcl.check(EventType.DATA_CREATED));
665
        assertEquals("Wrong number of events when file was created.", 1, fcl2.check(EventType.DATA_CREATED));
666
        FileObject fileAFO = dirFO.createData("fileA");
667
        assertEquals("No other events should be fired.", 0, fcl.checkAll());
668
669
        // remove listener
670
        FileUtil.removeRecursiveListener(fcl2, fileF);
671
672
        // modification
673
        fileFO.getOutputStream().close();
674
        fileFO.getOutputStream().close();
675
        assertEquals("Wrong number of events when file was modified.", 2, fcl.check(EventType.CHANGED));
676
        // no event fired when other file modified
677
        fileAFO.getOutputStream().close();
678
        assertEquals("No other events should be fired.", 0, fcl.checkAll());
679
680
        // deletion
681
        fileFO.delete();
682
        assertEquals("Wrong number of events when file deleted.", 1, fcl.check(EventType.DELETED));
683
        dirFO.delete();
684
        assertEquals("Event fired when parent dir deleted and file already deleted.", 0, fcl.checkAll());
685
        dirFO = rootFO.createFolder("dir");
686
        fileFO = dirFO.createData("file");
687
        assertEquals("Wrong number of events when file was created.", 1, fcl.check(EventType.DATA_CREATED));
688
        dirFO.delete();
689
        assertEquals("Wrong number of events when parent dir deleted.", 1, fcl.check(EventType.DELETED));
690
        assertEquals("No other events should be fired.", 0, fcl.checkAll());
691
692
        // atomic action
693
        FileUtil.runAtomicAction(new Runnable() {
694
695
            public void run() {
696
                FileObject dirFO;
697
                try {
698
                    dirFO = rootFO.createFolder("dir");
699
                    rootFO.createFolder("fakedir");
700
                    rootFO.setAttribute("fake", "fake");
701
                    rootFO.createData("fakefile");
702
                    dirFO.createData("file");
703
                } catch (IOException ex) {
704
                    throw new RuntimeException(ex);
705
                }
706
707
            }
708
        });
709
        assertEquals("Wrong number of events fired when file was created in atomic action.", 1, fcl.check(EventType.DATA_CREATED));
710
        assertEquals("No other events should be fired.", 0, fcl.checkAll());
711
712
        // rename
713
        dirFO = FileUtil.toFileObject(dirF);
714
        fileFO = FileUtil.toFileObject(fileF);
715
        FileLock lock = dirFO.lock();
716
        dirFO.rename(lock, "dirRenamed", null);
717
        lock.releaseLock();
718
        assertEquals("Event fired when parent dir renamed.", 0, fcl.checkAll());
719
        lock = fileFO.lock();
720
        fileFO.rename(lock, "fileRenamed", null);
721
        lock.releaseLock();
722
        assertEquals("Renamed event not fired.", 1, fcl.check(EventType.RENAMED));
723
        assertEquals("No other events should be fired.", 0, fcl.checkAll());
724
725
        // disk changes
726
        dirF.mkdir();
727
        assertTrue(fileF.createNewFile());
728
        FileUtil.refreshAll();
729
        assertEquals("Wrong number of events when file was created.", 1, fcl.check(EventType.DATA_CREATED));
730
        Thread.sleep(1000); // make sure timestamp changes
731
        new FileOutputStream(fileF).close();
732
        FileUtil.refreshAll();
733
        assertEquals("Wrong number of events when file was modified.", 1, fcl.check(EventType.CHANGED));
734
        assertEquals("Attribute change event not fired (see #129178).", 2, fcl.check(EventType.ATTRIBUTE_CHANGED));
735
        fileF.delete();
736
        dirF.delete();
737
        FileUtil.refreshAll();
738
        assertEquals("Wrong number of events when file deleted.", 1, fcl.check(EventType.DELETED));
739
        assertEquals("No other events should be fired.", 0, fcl.checkAll());
740
741
        // disk changes #66444
742
        for (int cntr = 0; cntr < 50; cntr++) {
743
            dirF.mkdir();
744
            new FileOutputStream(fileF).close();
745
            FileUtil.refreshAll();
746
            assertEquals("Event not fired when file was created; count=" + cntr, 1, fcl.check(EventType.DATA_CREATED));
747
            fileF.delete();
748
            dirF.delete();
749
            FileUtil.refreshAll();
750
            assertEquals("Event not fired when file deleted; count=" + cntr, 1, fcl.check(EventType.DELETED));
751
        }
752
753
        // removed listener
754
        assertEquals("No other events should be fired in removed listener.", 0, fcl2.checkAll());
755
756
        // weakness
757
        WeakReference ref = new WeakReference(fcl);
758
        fcl = null;
759
        assertGC("FileChangeListener not collected.", ref);
760
    }
761
312
    private static enum EventType {
762
    private static enum EventType {
313
763
314
        DATA_CREATED, FOLDER_CREATED, DELETED, CHANGED, RENAMED, ATTRIBUTE_CHANGED
764
        DATA_CREATED, FOLDER_CREATED, DELETED, CHANGED, RENAMED, ATTRIBUTE_CHANGED
315
    };
765
    };
316
766
317
    private static class TestFileChangeListener implements FileChangeListener {
767
    private static class TestFileChangeListener implements FileChangeListener {
768
        boolean disabled;
318
769
319
        private final Map<EventType, List<FileEvent>> type2Event = new HashMap<EventType, List<FileEvent>>();
770
        private final Map<EventType, List<FileEvent>> type2Event = new HashMap<EventType, List<FileEvent>>();
320
771
Lines 356-381 Link Here
356
        }
807
        }
357
808
358
        public void fileFolderCreated(FileEvent fe) {
809
        public void fileFolderCreated(FileEvent fe) {
810
            assertFalse("No changes expected", disabled);
359
            type2Event.get(EventType.FOLDER_CREATED).add(fe);
811
            type2Event.get(EventType.FOLDER_CREATED).add(fe);
360
        }
812
        }
361
813
362
        public void fileDataCreated(FileEvent fe) {
814
        public void fileDataCreated(FileEvent fe) {
815
            assertFalse("No changes expected", disabled);
363
            type2Event.get(EventType.DATA_CREATED).add(fe);
816
            type2Event.get(EventType.DATA_CREATED).add(fe);
364
        }
817
        }
365
818
366
        public void fileChanged(FileEvent fe) {
819
        public void fileChanged(FileEvent fe) {
820
            assertFalse("No changes expected", disabled);
367
            type2Event.get(EventType.CHANGED).add(fe);
821
            type2Event.get(EventType.CHANGED).add(fe);
368
        }
822
        }
369
823
370
        public void fileDeleted(FileEvent fe) {
824
        public void fileDeleted(FileEvent fe) {
825
            assertFalse("No changes expected", disabled);
371
            type2Event.get(EventType.DELETED).add(fe);
826
            type2Event.get(EventType.DELETED).add(fe);
372
        }
827
        }
373
828
374
        public void fileRenamed(FileRenameEvent fe) {
829
        public void fileRenamed(FileRenameEvent fe) {
830
            assertFalse("No changes expected", disabled);
375
            type2Event.get(EventType.RENAMED).add(fe);
831
            type2Event.get(EventType.RENAMED).add(fe);
376
        }
832
        }
377
833
378
        public void fileAttributeChanged(FileAttributeEvent fe) {
834
        public void fileAttributeChanged(FileAttributeEvent fe) {
835
            assertFalse("No changes expected", disabled);
379
            type2Event.get(EventType.ATTRIBUTE_CHANGED).add(fe);
836
            type2Event.get(EventType.ATTRIBUTE_CHANGED).add(fe);
380
        }
837
        }
381
    }
838
    }
(-)70eda1b69061 (+340 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2009 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
package org.netbeans.modules.masterfs.filebasedfs.fileobjects;
42
43
import java.io.File;
44
import java.io.FileOutputStream;
45
import java.io.OutputStream;
46
import java.lang.ref.Reference;
47
import java.lang.ref.WeakReference;
48
import java.util.logging.Level;
49
import java.util.logging.Logger;
50
import org.netbeans.junit.NbTestCase;
51
import org.openide.filesystems.FileAttributeEvent;
52
import org.openide.filesystems.FileChangeAdapter;
53
import org.openide.filesystems.FileChangeListener;
54
import org.openide.filesystems.FileEvent;
55
import org.openide.filesystems.FileObject;
56
import org.openide.filesystems.FileRenameEvent;
57
import org.openide.filesystems.FileUtil;
58
59
public class ExternalTouchTest extends NbTestCase {
60
    private Logger LOG;
61
    private FileObject testFolder;
62
63
    public ExternalTouchTest(String testName) {
64
        super(testName);
65
    }
66
67
    @Override
68
    protected Level logLevel() {
69
        return Level.FINE;
70
    }
71
    
72
    @Override
73
    protected void setUp() throws Exception {
74
        clearWorkDir();
75
        
76
        LOG = Logger.getLogger("test." + getName());
77
        Logger.getLogger("org.openide.util.Mutex").setUseParentHandlers(false);
78
79
        File dir = new File(getWorkDir(), "test");
80
        dir.mkdirs();
81
        testFolder = FileUtil.toFileObject(dir);
82
        assertNotNull("Test folder created", testFolder);
83
84
    }
85
86
    public void testChangeInChildrenNoticed() throws Exception {
87
        long lm = System.currentTimeMillis();
88
        FileObject fileObject1 = testFolder.createData("fileObject1");
89
        assertNotNull("Just to initialize the stamp", lm);
90
        FileObject[] arr = testFolder.getChildren();
91
        assertEquals("One child", 1, arr.length);
92
        assertEquals("Right child", fileObject1, arr[0]);
93
94
        File file = FileUtil.toFile(fileObject1);
95
        assertNotNull("File found", file);
96
        Reference<FileObject> ref = new WeakReference<FileObject>(fileObject1);
97
        arr = null;
98
        fileObject1 = null;
99
        assertGC("File Object can disappear", ref);
100
101
102
        class L extends FileChangeAdapter {
103
            int cnt;
104
            FileEvent event;
105
            
106
            @Override
107
            public void fileChanged(FileEvent fe) {
108
                LOG.info("file change " + fe.getFile());
109
                cnt++;
110
                event = fe;
111
            }
112
        }
113
        L listener = new L();
114
        testFolder.addRecursiveListener(listener);
115
116
        Thread.sleep(1000);
117
118
        FileOutputStream os = new FileOutputStream(file);
119
        os.write(10);
120
        os.close();
121
122
        if (lm > file.lastModified() - 50) {
123
            fail("New modification time shall be at last 50ms after the original one: " + (file.lastModified() - lm));
124
        }
125
126
        testFolder.refresh();
127
128
        assertEquals("Change notified", 1, listener.cnt);
129
        assertEquals("Right file", file, FileUtil.toFile(listener.event.getFile()));
130
        assertEquals("Right source", file.getParentFile(), FileUtil.toFile((FileObject)listener.event.getSource()));
131
    }
132
    public void testNewChildNoticed() throws Exception {
133
        FileObject fileObject1 = testFolder.createData("fileObject1");
134
        FileObject[] arr = testFolder.getChildren();
135
        assertEquals("One child", 1, arr.length);
136
        assertEquals("Right child", fileObject1, arr[0]);
137
138
        File file = FileUtil.toFile(fileObject1);
139
        assertNotNull("File found", file);
140
        arr = null;
141
        fileObject1 = null;
142
        Reference<FileObject> ref = new WeakReference<FileObject>(fileObject1);
143
        assertGC("File Object can disappear", ref);
144
145
        Thread.sleep(100);
146
147
        class L extends FileChangeAdapter {
148
            int cnt;
149
            FileEvent event;
150
151
            @Override
152
            public void fileDataCreated(FileEvent fe) {
153
                cnt++;
154
                event = fe;
155
            }
156
157
        }
158
        L listener = new L();
159
        testFolder.addRecursiveListener(listener);
160
161
        File nfile = new File(file.getParentFile(), "new.txt");
162
        nfile.createNewFile();
163
164
        testFolder.refresh();
165
166
        assertEquals("Change notified", 1, listener.cnt);
167
        assertEquals("Right file", nfile, FileUtil.toFile(listener.event.getFile()));
168
    }
169
    public void testDeleteOfAChildNoticed() throws Exception {
170
        FileObject fileObject1 = testFolder.createData("fileObject1");
171
        FileObject[] arr = testFolder.getChildren();
172
        assertEquals("One child", 1, arr.length);
173
        assertEquals("Right child", fileObject1, arr[0]);
174
175
        File file = FileUtil.toFile(fileObject1);
176
        assertNotNull("File found", file);
177
        arr = null;
178
        fileObject1 = null;
179
        Reference<FileObject> ref = new WeakReference<FileObject>(fileObject1);
180
        assertGC("File Object can disappear", ref);
181
182
        Thread.sleep(100);
183
184
        class L extends FileChangeAdapter {
185
            int cnt;
186
            FileEvent event;
187
188
            @Override
189
            public void fileDeleted(FileEvent fe) {
190
                cnt++;
191
                event = fe;
192
            }
193
194
        }
195
        L listener = new L();
196
        testFolder.addRecursiveListener(listener);
197
198
        file.delete();
199
200
        testFolder.refresh();
201
202
        assertEquals("Change notified", 1, listener.cnt);
203
        assertEquals("Right file", file, FileUtil.toFile(listener.event.getFile()));
204
    }
205
206
    public void testRecursiveListener() throws Exception {
207
        FileObject sub;
208
        File fobj;
209
        File fsub;
210
        {
211
            FileObject obj = FileUtil.createData(testFolder, "my/sub/children/children.java");
212
            fobj = FileUtil.toFile(obj);
213
            assertNotNull("File found", fobj);
214
            sub = obj.getParent().getParent();
215
            fsub = FileUtil.toFile(sub);
216
217
            WeakReference<Object> ref = new WeakReference(obj);
218
            obj = null;
219
            assertGC("File object can disappear", ref);
220
        }
221
222
        class L implements FileChangeListener {
223
            StringBuilder sb = new StringBuilder();
224
225
            public void fileFolderCreated(FileEvent fe) {
226
                LOG.info("FolderCreated: " + fe.getFile());
227
                sb.append("FolderCreated");
228
            }
229
230
            public void fileDataCreated(FileEvent fe) {
231
                LOG.info("DataCreated: " + fe.getFile());
232
                sb.append("DataCreated");
233
            }
234
235
            public void fileChanged(FileEvent fe) {
236
                LOG.info("Changed: " + fe.getFile());
237
                sb.append("Changed");
238
            }
239
240
            public void fileDeleted(FileEvent fe) {
241
                LOG.info("Deleted: " + fe.getFile());
242
                sb.append("Deleted");
243
            }
244
245
            public void fileRenamed(FileRenameEvent fe) {
246
                LOG.info("Renamed: " + fe.getFile());
247
                sb.append("Renamed");
248
            }
249
250
            public void fileAttributeChanged(FileAttributeEvent fe) {
251
                if (fe.getName().startsWith("DataEditorSupport.read-only.refresh")) {
252
                    return;
253
                }
254
                LOG.info("AttributeChanged: " + fe.getFile());
255
                sb.append("AttributeChanged");
256
            }
257
258
            public void assertMessages(String txt, String msg) {
259
                assertEquals(txt, msg, sb.toString());
260
                sb.setLength(0);
261
            }
262
        }
263
        L recursive = new L();
264
        L flat = new L();
265
266
        sub.addFileChangeListener(flat);
267
        LOG.info("Adding listener");
268
        sub.addRecursiveListener(recursive);
269
        LOG.info("Adding listener finished");
270
271
        Thread.sleep(1000);
272
273
        File fo = new File(fobj.getParentFile(), "sibling.java");
274
        fo.createNewFile();
275
        LOG.info("sibling created, now refresh");
276
        FileUtil.refreshAll();
277
        LOG.info("sibling refresh finished");
278
279
        recursive.assertMessages("Creation", "DataCreated");
280
        flat.assertMessages("No messages in flat mode", "");
281
282
        Thread.sleep(1000);
283
284
        final OutputStream os = new FileOutputStream(fo);
285
        os.write(10);
286
        os.close();
287
        LOG.info("Before refresh");
288
        FileUtil.refreshAll();
289
        LOG.info("After refresh");
290
291
        flat.assertMessages("No messages in flat mode", "");
292
        recursive.assertMessages("written", "Changed");
293
294
        fo.delete();
295
        FileUtil.refreshAll();
296
297
        flat.assertMessages("No messages in flat mode", "");
298
        recursive.assertMessages("gone", "Deleted");
299
300
        new File(fsub, "testFolder").mkdirs();
301
        FileUtil.refreshAll();
302
303
        flat.assertMessages("Direct Folder notified", "FolderCreated");
304
        recursive.assertMessages("Direct Folder notified", "FolderCreated");
305
306
        new File(fsub.getParentFile(), "unimportant.txt").createNewFile();
307
        FileUtil.refreshAll();
308
309
        flat.assertMessages("No messages in flat mode", "");
310
        recursive.assertMessages("No messages in recursive mode", "");
311
312
        File deepest = new File(new File(new File(fsub, "deep"), "deeper"), "deepest");
313
        deepest.mkdirs();
314
        FileUtil.refreshAll();
315
316
        flat.assertMessages("Folder in flat mode", "FolderCreated");
317
        recursive.assertMessages("Folder detected", "FolderCreated");
318
319
        File hidden = new File(deepest, "hide.me");
320
        hidden.createNewFile();
321
        FileUtil.refreshAll();
322
323
        flat.assertMessages("No messages in flat mode", "");
324
        recursive.assertMessages("Folder detected", "DataCreated");
325
326
327
        sub.removeRecursiveListener(recursive);
328
329
        new File(fsub, "test.data").createNewFile();
330
        FileUtil.refreshAll();
331
332
        flat.assertMessages("Direct file notified", "DataCreated");
333
        recursive.assertMessages("No longer active", "");
334
335
        WeakReference<L> ref = new WeakReference<L>(recursive);
336
        recursive = null;
337
        assertGC("Listener can be GCed", ref);
338
    }
339
340
}
(-)a/openide.filesystems/apichanges.xml (+18 lines)
Lines 46-51 Link Here
46
        <apidef name="filesystems">Filesystems API</apidef>
46
        <apidef name="filesystems">Filesystems API</apidef>
47
    </apidefs>
47
    </apidefs>
48
    <changes>
48
    <changes>
49
          <change id="recursive-listener">
50
             <api name="filesystems"/>
51
             <summary>Support for recursive listeners</summary>
52
             <version major="7" minor="28"/>
53
             <date day="18" month="9" year="2009"/>
54
             <author login="jtulach"/>
55
             <compatibility addition="yes" binary="compatible" source="compatible" semantic="compatible" deprecation="no" deletion="no" modification="no"/>
56
             <description>
57
                 <p>
58
                     One can register a recursive listener on a file object by
59
                     calling
60
                     <a href="@TOP@/org/openide/filesystems/FileObject.html#addRecursiveListener(org.openide.filesystems.FileChangeListener)">FileObject.addRecursiveListener(FileChangeListener)</a>.
61
                 </p>
62
             </description>
63
             <class package="org.openide.filesystems" name="FileObject"/>
64
             <class package="org.openide.filesystems" name="FileUtil"/>
65
             <issue number="170862"/>
66
         </change>
49
        <change id="LayerBuilder.instanceFile">
67
        <change id="LayerBuilder.instanceFile">
50
            <api name="filesystems"/>
68
            <api name="filesystems"/>
51
            <summary>Non-initializing <code>LayerBuilder.instanceFile</code> added</summary>
69
            <summary>Non-initializing <code>LayerBuilder.instanceFile</code> added</summary>
(-)a/openide.filesystems/nbproject/project.properties (-1 / +1 lines)
Lines 44-47 Link Here
44
javadoc.main.page=org/openide/filesystems/doc-files/api.html
44
javadoc.main.page=org/openide/filesystems/doc-files/api.html
45
javadoc.arch=${basedir}/arch.xml
45
javadoc.arch=${basedir}/arch.xml
46
javadoc.apichanges=${basedir}/apichanges.xml
46
javadoc.apichanges=${basedir}/apichanges.xml
47
spec.version.base=7.27.0
47
spec.version.base=7.28.0
(-)70eda1b69061 (+201 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * If you wish your version of this file to be governed by only the CDDL
25
 * or only the GPL Version 2, indicate your decision by adding
26
 * "[Contributor] elects to include this software in this distribution
27
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
28
 * single choice of license, a recipient has the option to distribute
29
 * your version of this file under either the CDDL, the GPL Version 2 or
30
 * to extend the choice of license to its licensees as provided above.
31
 * However, if you add GPL Version 2 code and therefore, elected the GPL
32
 * Version 2 license, then the option applies only if the new code is
33
 * made subject to such option by the copyright holder.
34
 *
35
 * Contributor(s):
36
 *
37
 * Portions Copyrighted 2009 Sun Microsystems, Inc.
38
 */
39
40
package org.openide.filesystems;
41
42
import java.io.File;
43
import java.lang.ref.WeakReference;
44
import java.util.ArrayList;
45
import java.util.List;
46
import java.util.Set;
47
import org.openide.util.Utilities;
48
import org.openide.util.WeakSet;
49
50
/**
51
 *
52
 * @author Jaroslav Tulach <jtulach@netbeans.org>
53
 */
54
final class DeepListener extends WeakReference<FileChangeListener>
55
implements FileChangeListener, Runnable {
56
    private final File path;
57
    private FileObject watching;
58
    private boolean removed;
59
    private static List<DeepListener> keep = new ArrayList<DeepListener>();
60
61
    public DeepListener(FileChangeListener listener, File path) {
62
        super(listener, Utilities.activeReferenceQueue());
63
        this.path = path;
64
        relisten();
65
        keep.add(this);
66
    }
67
68
    public void run() {
69
        FileObject fo = FileUtil.toFileObject(path);
70
        if (fo != null) {
71
            fo.removeRecursiveListener(this);
72
        }
73
        removed = true;
74
        keep.remove(this);
75
    }
76
77
    private synchronized void relisten() {
78
        FileObject fo = FileUtil.toFileObject(path);
79
        if (fo == watching) {
80
            return;
81
        }
82
        if (watching != null) {
83
            watching.removeRecursiveListener(this);
84
            watching = null;
85
        }
86
        if (fo != null) {
87
            watching = fo;
88
            fo.addRecursiveListener(this);
89
        }
90
    }
91
92
    public void fileRenamed(FileRenameEvent fe) {
93
        fileRenamed(fe, false);
94
    }
95
    public void fileRenamed(FileRenameEvent fe, boolean fromHolder) {
96
        relisten();
97
        FileChangeListener listener = get(fe, fromHolder);
98
        if (listener == null) {
99
            return;
100
        }
101
        listener.fileRenamed(fe);
102
    }
103
104
    public void fileFolderCreated(FileEvent fe) {
105
        relisten();
106
        fileFolderCreated(fe, false);
107
    }
108
    public void fileFolderCreated(FileEvent fe, boolean fromHolder) {
109
        relisten();
110
        FileChangeListener listener = get(fe, fromHolder);
111
        if (listener == null) {
112
            return;
113
        }
114
        listener.fileFolderCreated(fe);
115
    }
116
117
    public void fileDeleted(FileEvent fe) {
118
        fileDeleted(fe, false);
119
    }
120
    public void fileDeleted(FileEvent fe, boolean fromHolder) {
121
        relisten();
122
        FileChangeListener listener = get(fe, fromHolder);
123
        if (listener == null) {
124
            return;
125
        }
126
        listener.fileDeleted(fe);
127
    }
128
129
    public void fileDataCreated(FileEvent fe) {
130
        fileDataCreated(fe, false);
131
    }
132
    public void fileDataCreated(FileEvent fe, boolean fromHolder) {
133
        relisten();
134
        FileChangeListener listener = get(fe, fromHolder);
135
        if (listener == null) {
136
            return;
137
        }
138
        listener.fileDataCreated(fe);
139
    }
140
141
    public void fileChanged(FileEvent fe) {
142
        fileChanged(fe, false);
143
    }
144
    public void fileChanged(FileEvent fe, boolean fromHolder) {
145
        FileChangeListener listener = get(fe, fromHolder);
146
        if (listener == null) {
147
            return;
148
        }
149
        listener.fileChanged(fe);
150
    }
151
152
    public void fileAttributeChanged(FileAttributeEvent fe) {
153
        FileChangeListener listener = get(fe, false);
154
        if (listener == null) {
155
            return;
156
        }
157
        listener.fileAttributeChanged(fe);
158
    }
159
160
    @Override
161
    public boolean equals(Object obj) {
162
        if (obj == null) {
163
            return false;
164
        }
165
        if (getClass() != obj.getClass()) {
166
            return false;
167
        }
168
        final DeepListener other = (DeepListener) obj;
169
        FileChangeListener thisListener = get();
170
        FileChangeListener otherListener = other.get();
171
        if (thisListener != otherListener && (thisListener == null || !thisListener.equals(otherListener))) {
172
            return false;
173
        }
174
        return true;
175
    }
176
177
    @Override
178
    public int hashCode() {
179
        FileChangeListener thisListener = get();
180
        int hash = 7;
181
        hash = 11 * hash + (thisListener != null ? thisListener.hashCode() : 0);
182
        return hash;
183
    }
184
185
    private Set<FileEvent> delivered = new WeakSet<FileEvent>();
186
    private FileChangeListener get(FileEvent fe, boolean fromHolder) {
187
        if (removed) {
188
            return null;
189
        }
190
        if (fromHolder) {
191
            if (fe.getFile() != fe.getSource()) {
192
                return null;
193
            }
194
        }
195
        if (!delivered.add(fe)) {
196
            return null;
197
        }
198
        return get();
199
    }
200
201
}
(-)a/openide.filesystems/src/org/openide/filesystems/ExternalUtil.java (-1 / +1 lines)
Lines 90-96 Link Here
90
        return orig;
90
        return orig;
91
    }
91
    }
92
92
93
    private static Logger LOG = Logger.getLogger("org.openide.filesystems"); // NOI18N
93
    final static Logger LOG = Logger.getLogger("org.openide.filesystems"); // NOI18N
94
    /** Logs a text.
94
    /** Logs a text.
95
     */
95
     */
96
    public static void log(String msg) {
96
    public static void log(String msg) {
(-)a/openide.filesystems/src/org/openide/filesystems/FileObject.java (+57 lines)
Lines 58-63 Link Here
58
import java.util.LinkedList;
58
import java.util.LinkedList;
59
import java.util.List;
59
import java.util.List;
60
import java.util.StringTokenizer;
60
import java.util.StringTokenizer;
61
import java.util.logging.Level;
61
import org.openide.util.Enumerations;
62
import org.openide.util.Enumerations;
62
import org.openide.util.NbBundle;
63
import org.openide.util.NbBundle;
63
import org.openide.util.UserQuestionException;
64
import org.openide.util.UserQuestionException;
Lines 413-418 Link Here
413
    */
414
    */
414
    public abstract void removeFileChangeListener(FileChangeListener fcl);
415
    public abstract void removeFileChangeListener(FileChangeListener fcl);
415
416
417
418
    /** Adds a listener to this {@link FileObject} and all its children and
419
     * children or its children.
420
     * It is guaranteed that whenever a change
421
     * is made via the FileSystem API itself under this {@link FileObject}
422
     * that it is notified to the <code>fcl</code> listener. Whether external
423
     * changes (if they make sense) are detected and
424
     * notified depends on actual implementation. As some implementations may
425
     * need to perform non-trivial amount of work during initialization of
426
     * listeners, this methods can take long time. Usage of this method may
427
     * consume a lot of system resources and as such it shall be used with care.
428
     * Traditional {@link #addFileChangeListener(org.openide.filesystems.FileChangeListener)}
429
     * is definitely preferred variant.
430
     * <p class="nonnormative">
431
     * If you are running with the MasterFS module enabled, it guarantees
432
     * that for files backed with real {@link File}, the system initializes
433
     * itself to detect external changes on the whole subtree.
434
     * This requires non-trivial amount of work and especially on slow
435
     * disks (aka networks ones) may take a long time to add the listener
436
     * and also refresh the system when {@link FileObject#refresh()}
437
     * and especially {@link FileUtil#refreshAll()} is requested.
438
     * </p>
439
     *
440
     * @param fcl the listener to register
441
     * @since 7.28
442
     */
443
    public void addRecursiveListener(FileChangeListener fcl) {
444
        if (!isFolder()) {
445
            addFileChangeListener(fcl);
446
            return;
447
        }
448
        try {
449
            boolean allowsExternalChanges = getFileSystem() instanceof LocalFileSystem;
450
            getFileSystem().addFileChangeListener(new RecursiveListener(this, fcl, allowsExternalChanges));
451
        } catch (FileStateInvalidException ex) {
452
            ExternalUtil.LOG.log(Level.FINE, "Cannot remove listener from " + this, ex);
453
        }
454
    }
455
456
    /** Removes listener previously added by {@link #addRecursiveListener(org.openide.filesystems.FileChangeListener)}
457
     *
458
     * @param fcl the listener to remove
459
     * @since 7.28
460
     */
461
    public void removeRecursiveListener(FileChangeListener fcl) {
462
        if (!isFolder()) {
463
            removeFileChangeListener(fcl);
464
            return;
465
        }
466
        try {
467
            getFileSystem().removeFileChangeListener(new RecursiveListener(this, fcl, false));
468
        } catch (FileStateInvalidException ex) {
469
            ExternalUtil.LOG.log(Level.FINE, "Cannot remove listener from " + this, ex);
470
        }
471
    }
472
416
    /** Fire data creation event.
473
    /** Fire data creation event.
417
    * @param en listeners that should receive the event
474
    * @param en listeners that should receive the event
418
    * @param fe the event to fire in this object
475
    * @param fe the event to fire in this object
(-)a/openide.filesystems/src/org/openide/filesystems/FileUtil.java (-6 / +69 lines)
Lines 257-262 Link Here
257
     * @since org.openide.filesystems 7.20
257
     * @since org.openide.filesystems 7.20
258
     */
258
     */
259
    public static void removeFileChangeListener(FileChangeListener listener, File path) {
259
    public static void removeFileChangeListener(FileChangeListener listener, File path) {
260
        removeFileChangeListenerImpl(listener, path);
261
    }
262
263
    private static FileChangeListener removeFileChangeListenerImpl(FileChangeListener listener, File path) {
260
        assert path.equals(FileUtil.normalizeFile(path)) : "Need to normalize " + path + "!";  //NOI18N
264
        assert path.equals(FileUtil.normalizeFile(path)) : "Need to normalize " + path + "!";  //NOI18N
261
        synchronized (holders) {
265
        synchronized (holders) {
262
            Map<File, Holder> f2H = holders.get(listener);
266
            Map<File, Holder> f2H = holders.get(listener);
Lines 267-275 Link Here
267
                throw new IllegalArgumentException(listener + " was not listening to " + path + "; only to " + f2H.keySet()); // NOI18N
271
                throw new IllegalArgumentException(listener + " was not listening to " + path + "; only to " + f2H.keySet()); // NOI18N
268
            }
272
            }
269
            // remove Holder instance from map and call run to unregister its current listener
273
            // remove Holder instance from map and call run to unregister its current listener
270
            f2H.remove(path).run();
274
            Holder h = f2H.remove(path);
275
            h.run();
276
            return h.get();
271
        }
277
        }
272
    }
278
    }
279
    /**
280
     * Adds a listener to changes under given path. It permits you to listen to a file
281
     * which does not yet exist, or continue listening to it after it is deleted and recreated, etc.
282
     * <br/>
283
     * When given path represents a file ({@code path.isDirectory() == false}), this
284
     * code behaves exectly like {@link #addFileChangeListener(org.openide.filesystems.FileChangeListener, java.io.File)}.
285
     * Usually the path shall represent a folder ({@code path.isDirectory() == true})
286
     * <ul>
287
     * <li>fileFolderCreated event is fired when the folder is created or a child folder created</li>
288
     * <li>fileDataCreated event is fired when a child file is created</li>
289
     * <li>fileDeleted event is fired when the folder is deleted or a child file/folder removed</li>
290
     * <li>fileChanged event is fired when a child file is modified</li>
291
     * <li>fileRenamed event is fired when the folder is renamed or a child file/folder is renamed</li>
292
     * <li>fileAttributeChanged is fired when FileObject's attribute is changed</li>
293
     *</ul>
294
     * The above events are delivered for changes in all subdirectories (recursively).
295
     * It is guaranteed that with each change at least one event is generated.
296
     * For example adding a folder does not notify about content of the folder,
297
     * hence one event is delivered.
298
     *
299
     * Can only add a given [listener, path] pair once. However a listener can
300
     * listen to any number of paths. Note that listeners are always held weakly
301
     * - if the listener is collected, it is quietly removed.
302
     *
303
     * @param listener FileChangeListener to listen to changes in path
304
     * @param path File path to listen to (even not existing)
305
     *
306
     * @see FileObject#addRecursiveListener
307
     * @since org.openide.filesystems 7.28
308
     */
309
    public static void addRecursiveListener(FileChangeListener listener, File path) {
310
        addFileChangeListener(new DeepListener(listener, path), path);
311
    }
312
313
    /**
314
     * Removes a listener to changes under given path.
315
     * @param listener FileChangeListener to be removed
316
     * @param path File path in which listener was listening
317
     * @throws IllegalArgumentException if listener was not listening to given path
318
     *
319
     * @see FileObject#removeRecursiveListener
320
     * @since org.openide.filesystems 7.28
321
     */
322
    public static void removeRecursiveListener(FileChangeListener listener, File path) {
323
        DeepListener dl = (DeepListener)removeFileChangeListenerImpl(new DeepListener(listener, path), path);
324
        dl.run();
325
    }
273
326
274
    /** Holds FileChangeListener and File pair and handle movement of auxiliary
327
    /** Holds FileChangeListener and File pair and handle movement of auxiliary
275
     * FileChangeListener to the first existing upper folder and firing appropriate events.
328
     * FileChangeListener to the first existing upper folder and firing appropriate events.
Lines 346-352 Link Here
346
            if (fe.getSource() == current) {
399
            if (fe.getSource() == current) {
347
                if (isOnTarget) {
400
                if (isOnTarget) {
348
                    FileChangeListener listener = get();
401
                    FileChangeListener listener = get();
349
                    if (listener != null) {
402
                    if (listener instanceof DeepListener) {
403
                        ((DeepListener)listener).fileChanged(fe, true);
404
                    } else if (listener != null) {
350
                        listener.fileChanged(fe);
405
                        listener.fileChanged(fe);
351
                    }
406
                    }
352
                } else {
407
                } else {
Lines 359-365 Link Here
359
            if (fe.getSource() == current) {
414
            if (fe.getSource() == current) {
360
                if (isOnTarget) {
415
                if (isOnTarget) {
361
                    FileChangeListener listener = get();
416
                    FileChangeListener listener = get();
362
                    if (listener != null) {
417
                    if (listener instanceof DeepListener) {
418
                        ((DeepListener)listener).fileDeleted(fe, true);
419
                    } else if (listener != null) {
363
                        listener.fileDeleted(fe);
420
                        listener.fileDeleted(fe);
364
                    }
421
                    }
365
                }
422
                }
Lines 371-377 Link Here
371
            if (fe.getSource() == current) {
428
            if (fe.getSource() == current) {
372
                if (isOnTarget) {
429
                if (isOnTarget) {
373
                    FileChangeListener listener = get();
430
                    FileChangeListener listener = get();
374
                    if (listener != null) {
431
                    if (listener instanceof DeepListener) {
432
                        ((DeepListener)listener).fileDataCreated(fe, true);
433
                    } else if (listener != null) {
375
                        listener.fileDataCreated(fe);
434
                        listener.fileDataCreated(fe);
376
                    }
435
                    }
377
                } else {
436
                } else {
Lines 384-390 Link Here
384
            if (fe.getSource() == current) {
443
            if (fe.getSource() == current) {
385
                if (isOnTarget) {
444
                if (isOnTarget) {
386
                    FileChangeListener listener = get();
445
                    FileChangeListener listener = get();
387
                    if (listener != null) {
446
                    if (listener instanceof DeepListener) {
447
                        ((DeepListener)listener).fileFolderCreated(fe, true);
448
                    } else if (listener != null) {
388
                        listener.fileFolderCreated(fe);
449
                        listener.fileFolderCreated(fe);
389
                    }
450
                    }
390
                } else {
451
                } else {
Lines 397-403 Link Here
397
            if (fe.getSource() == current) {
458
            if (fe.getSource() == current) {
398
                if (isOnTarget) {
459
                if (isOnTarget) {
399
                    FileChangeListener listener = get();
460
                    FileChangeListener listener = get();
400
                    if (listener != null) {
461
                    if (listener instanceof DeepListener) {
462
                        ((DeepListener)listener).fileRenamed(fe, true);
463
                    } else if (listener != null) {
401
                        listener.fileRenamed(fe);
464
                        listener.fileRenamed(fe);
402
                    }
465
                    }
403
                }
466
                }
(-)70eda1b69061 (+154 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * If you wish your version of this file to be governed by only the CDDL
25
 * or only the GPL Version 2, indicate your decision by adding
26
 * "[Contributor] elects to include this software in this distribution
27
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
28
 * single choice of license, a recipient has the option to distribute
29
 * your version of this file under either the CDDL, the GPL Version 2 or
30
 * to extend the choice of license to its licensees as provided above.
31
 * However, if you add GPL Version 2 code and therefore, elected the GPL
32
 * Version 2 license, then the option applies only if the new code is
33
 * made subject to such option by the copyright holder.
34
 *
35
 * Contributor(s):
36
 *
37
 * Portions Copyrighted 2009 Sun Microsystems, Inc.
38
 */
39
40
package org.openide.filesystems;
41
42
import java.lang.ref.WeakReference;
43
import java.util.Enumeration;
44
import java.util.HashSet;
45
import java.util.Set;
46
47
/**
48
 *
49
 * @author Jaroslav Tulach <jtulach@netbeans.org>
50
 */
51
final class RecursiveListener extends WeakReference<FileObject>
52
implements FileChangeListener {
53
    private final FileChangeListener fcl;
54
    private final Set<FileObject> kept;
55
56
    public RecursiveListener(FileObject source, FileChangeListener fcl, boolean keep) {
57
        super(source);
58
        this.fcl = fcl;
59
        this.kept = keep ? new HashSet<FileObject>() : null;
60
        addAll(source);
61
    }
62
63
    private void addAll(FileObject folder) {
64
        if (kept != null) {
65
            kept.add(folder);
66
            Enumeration<? extends FileObject> en = folder.getChildren(true);
67
            while (en.hasMoreElements()) {
68
                FileObject fo = en.nextElement();
69
                kept.add(fo);
70
            }
71
        }
72
    }
73
74
    public void fileRenamed(FileRenameEvent fe) {
75
        FileObject thisFo = this.get();
76
        if (thisFo != null && FileUtil.isParentOf(thisFo, fe.getFile())) {
77
            fcl.fileRenamed(fe);
78
        }
79
    }
80
81
    public void fileFolderCreated(FileEvent fe) {
82
        FileObject thisFo = this.get();
83
        final FileObject file = fe.getFile();
84
        if (thisFo != null && FileUtil.isParentOf(thisFo, file)) {
85
            fcl.fileFolderCreated(fe);
86
            addAll(file);
87
        }
88
    }
89
90
    public void fileDeleted(FileEvent fe) {
91
        FileObject thisFo = this.get();
92
        final FileObject file = fe.getFile();
93
        if (thisFo != null && FileUtil.isParentOf(thisFo, file)) {
94
            fcl.fileDeleted(fe);
95
            if (kept != null) {
96
                kept.remove(file);
97
            }
98
        }
99
    }
100
101
    public void fileDataCreated(FileEvent fe) {
102
        FileObject thisFo = this.get();
103
        final FileObject file = fe.getFile();
104
        if (thisFo != null && FileUtil.isParentOf(thisFo, file)) {
105
            fcl.fileDataCreated(fe);
106
            if (kept != null) {
107
                kept.add(file);
108
            }
109
        }
110
    }
111
112
    public void fileChanged(FileEvent fe) {
113
        FileObject thisFo = this.get();
114
        if (thisFo != null && FileUtil.isParentOf(thisFo, fe.getFile())) {
115
            fcl.fileChanged(fe);
116
        }
117
    }
118
119
    public void fileAttributeChanged(FileAttributeEvent fe) {
120
        FileObject thisFo = this.get();
121
        if (thisFo != null && FileUtil.isParentOf(thisFo, fe.getFile())) {
122
            fcl.fileAttributeChanged(fe);
123
        }
124
    }
125
    
126
    @Override
127
    public boolean equals(Object obj) {
128
        if (obj == null) {
129
            return false;
130
        }
131
        if (getClass() != obj.getClass()) {
132
            return false;
133
        }
134
        final RecursiveListener other = (RecursiveListener) obj;
135
        if (this.fcl != other.fcl && (this.fcl == null || !this.fcl.equals(other.fcl))) {
136
            return false;
137
        }
138
        final FileObject otherFo = other.get();
139
        final FileObject thisFo = this.get();
140
        if (thisFo != otherFo && (thisFo == null || !thisFo.equals(otherFo))) {
141
            return false;
142
        }
143
        return true;
144
    }
145
146
    @Override
147
    public int hashCode() {
148
        final FileObject thisFo = this.get();
149
        int hash = 3;
150
        hash = 37 * hash + (this.fcl != null ? this.fcl.hashCode() : 0);
151
        hash = 13 * hash + (thisFo != null ? thisFo.hashCode() : 0);
152
        return hash;
153
    }
154
}
(-)a/openide.filesystems/test/unit/src/org/openide/filesystems/FileObjectTestHid.java (+103 lines)
Lines 1614-1619 Link Here
1614
            assertNotNull(child);        
1614
            assertNotNull(child);        
1615
        }
1615
        }
1616
    }
1616
    }
1617
1618
    public void testRecursiveListener() throws IOException {
1619
        checkSetUp();
1620
1621
        FileObject folder1 = getTestFolder1 (root);
1622
        /** delete first time*/
1623
        try {
1624
            FileObject obj = FileUtil.createData(folder1, "my/sub/children/children.java");
1625
            FileObject sub = obj.getParent().getParent();
1626
1627
            class L implements FileChangeListener {
1628
                StringBuilder sb = new StringBuilder();
1629
1630
                public void fileFolderCreated(FileEvent fe) {
1631
                    sb.append("FolderCreated");
1632
                }
1633
1634
                public void fileDataCreated(FileEvent fe) {
1635
                    sb.append("DataCreated");
1636
                }
1637
1638
                public void fileChanged(FileEvent fe) {
1639
                    sb.append("Changed");
1640
                }
1641
1642
                public void fileDeleted(FileEvent fe) {
1643
                    sb.append("Deleted");
1644
                }
1645
1646
                public void fileRenamed(FileRenameEvent fe) {
1647
                    sb.append("Renamed");
1648
                }
1649
1650
                public void fileAttributeChanged(FileAttributeEvent fe) {
1651
                    sb.append("AttributeChanged");
1652
                }
1653
1654
                public void assertMessages(String txt, String msg) {
1655
                    assertEquals(txt, msg, sb.toString());
1656
                    sb.setLength(0);
1657
                }
1658
            }
1659
            L recursive = new L();
1660
            L flat = new L();
1661
1662
            sub.addFileChangeListener(flat);
1663
            sub.addRecursiveListener(recursive);
1664
1665
            FileObject fo = obj.getParent().createData("sibling.java");
1666
1667
            flat.assertMessages("No messages in flat mode", "");
1668
            recursive.assertMessages("Creation", "DataCreated");
1669
1670
            fo.setAttribute("jarda", "hello");
1671
1672
            flat.assertMessages("No messages in flat mode", "");
1673
            recursive.assertMessages("attr", "AttributeChanged");
1674
1675
            final OutputStream os = fo.getOutputStream();
1676
            os.write(10);
1677
            os.close();
1678
1679
            flat.assertMessages("No messages in flat mode", "");
1680
            recursive.assertMessages("written", "Changed");
1681
1682
            fo.delete();
1683
1684
            flat.assertMessages("No messages in flat mode", "");
1685
            recursive.assertMessages("gone", "Deleted");
1686
1687
            FileObject subdir = sub.createFolder("testFolder");
1688
1689
            flat.assertMessages("Direct Folder notified", "FolderCreated");
1690
            recursive.assertMessages("Direct Folder notified", "FolderCreated");
1691
1692
            subdir.createData("subchild.txt");
1693
1694
            recursive.assertMessages("SubFolder's change notified", "DataCreated");
1695
            flat.assertMessages("SubFolder's change not important", "");
1696
1697
            sub.getParent().createData("unimportant.txt");
1698
1699
            flat.assertMessages("No messages in flat mode", "");
1700
            recursive.assertMessages("No messages in recursive mode", "");
1701
1702
            sub.removeRecursiveListener(recursive);
1703
1704
            sub.createData("test.data");
1705
1706
            flat.assertMessages("Direct file notified", "DataCreated");
1707
            recursive.assertMessages("No longer active", "");
1708
1709
            WeakReference<L> ref = new WeakReference<L>(recursive);
1710
            recursive = null;
1711
            assertGC("Listener can be GCed", ref);
1712
1713
        } catch (IOException iex) {
1714
            if (fs.isReadOnly() || root.isReadOnly()) return;
1715
            throw iex;
1716
        } finally {
1717
        }
1718
    }
1719
1617
    
1720
    
1618
    /** Test of delete method, of class org.openide.filesystems.FileObject. */    
1721
    /** Test of delete method, of class org.openide.filesystems.FileObject. */    
1619
    public void testCreateDeleteFolderCreate () throws IOException {
1722
    public void testCreateDeleteFolderCreate () throws IOException {
(-)70eda1b69061 (+338 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2009 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
package org.openide.filesystems;
42
43
import java.io.File;
44
import java.io.FileOutputStream;
45
import java.io.OutputStream;
46
import java.lang.ref.Reference;
47
import java.lang.ref.WeakReference;
48
import java.util.logging.Level;
49
import java.util.logging.Logger;
50
import org.netbeans.junit.NbTestCase;
51
52
public class LocalFileSystemExternalTouchTest extends NbTestCase {
53
    private Logger LOG;
54
    private FileObject testFolder;
55
    private LocalFileSystem lfs;
56
57
    public LocalFileSystemExternalTouchTest(String testName) {
58
        super(testName);
59
    }
60
61
    @Override
62
    protected Level logLevel() {
63
        return Level.FINE;
64
    }
65
    
66
    @Override
67
    protected void setUp() throws Exception {
68
        clearWorkDir();
69
        
70
        LOG = Logger.getLogger("test." + getName());
71
        Logger.getLogger("org.openide.util.Mutex").setUseParentHandlers(false);
72
73
        File dir = new File(getWorkDir(), "test");
74
        dir.mkdirs();
75
76
        lfs = new LocalFileSystem();
77
        lfs.setRootDirectory(dir);
78
79
        testFolder = lfs.getRoot();
80
        assertNotNull("Test folder created", testFolder);
81
82
    }
83
84
    public void testChangeInChildrenNoticed() throws Exception {
85
        long lm = System.currentTimeMillis();
86
        FileObject fileObject1 = testFolder.createData("fileObject1");
87
        assertNotNull("Just to initialize the stamp", lm);
88
        FileObject[] arr = testFolder.getChildren();
89
        assertEquals("One child", 1, arr.length);
90
        assertEquals("Right child", fileObject1, arr[0]);
91
92
        File file = FileUtil.toFile(fileObject1);
93
        assertNotNull("File found", file);
94
        Reference<FileObject> ref = new WeakReference<FileObject>(fileObject1);
95
        arr = null;
96
        fileObject1 = null;
97
        assertGC("File Object can disappear", ref);
98
99
100
        class L extends FileChangeAdapter {
101
            int cnt;
102
            FileEvent event;
103
            
104
            @Override
105
            public void fileChanged(FileEvent fe) {
106
                LOG.info("file change " + fe.getFile());
107
                cnt++;
108
                event = fe;
109
            }
110
        }
111
        L listener = new L();
112
        testFolder.addRecursiveListener(listener);
113
114
        Thread.sleep(1000);
115
116
        FileOutputStream os = new FileOutputStream(file);
117
        os.write(10);
118
        os.close();
119
120
        if (lm > file.lastModified() - 50) {
121
            fail("New modification time shall be at last 50ms after the original one: " + (file.lastModified() - lm));
122
        }
123
124
        testFolder.refresh();
125
126
        assertEquals("Change notified", 1, listener.cnt);
127
        assertEquals("Right file", file, FileUtil.toFile(listener.event.getFile()));
128
        assertEquals("Right source", file, FileUtil.toFile((FileObject)listener.event.getSource()));
129
    }
130
    public void testNewChildNoticed() throws Exception {
131
        FileObject fileObject1 = testFolder.createData("fileObject1");
132
        FileObject[] arr = testFolder.getChildren();
133
        assertEquals("One child", 1, arr.length);
134
        assertEquals("Right child", fileObject1, arr[0]);
135
136
        File file = FileUtil.toFile(fileObject1);
137
        assertNotNull("File found", file);
138
        arr = null;
139
        fileObject1 = null;
140
        Reference<FileObject> ref = new WeakReference<FileObject>(fileObject1);
141
        assertGC("File Object can disappear", ref);
142
143
        Thread.sleep(100);
144
145
        class L extends FileChangeAdapter {
146
            int cnt;
147
            FileEvent event;
148
149
            @Override
150
            public void fileDataCreated(FileEvent fe) {
151
                cnt++;
152
                event = fe;
153
            }
154
155
        }
156
        L listener = new L();
157
        testFolder.addRecursiveListener(listener);
158
159
        File nfile = new File(file.getParentFile(), "new.txt");
160
        nfile.createNewFile();
161
162
        testFolder.refresh();
163
164
        assertEquals("Change notified", 1, listener.cnt);
165
        assertEquals("Right file", nfile, FileUtil.toFile(listener.event.getFile()));
166
    }
167
    public void testDeleteOfAChildNoticed() throws Exception {
168
        FileObject fileObject1 = testFolder.createData("fileObject1");
169
        FileObject[] arr = testFolder.getChildren();
170
        assertEquals("One child", 1, arr.length);
171
        assertEquals("Right child", fileObject1, arr[0]);
172
173
        File file = FileUtil.toFile(fileObject1);
174
        assertNotNull("File found", file);
175
        arr = null;
176
        fileObject1 = null;
177
        Reference<FileObject> ref = new WeakReference<FileObject>(fileObject1);
178
        assertGC("File Object can disappear", ref);
179
180
        Thread.sleep(100);
181
182
        class L extends FileChangeAdapter {
183
            int cnt;
184
            FileEvent event;
185
186
            @Override
187
            public void fileDeleted(FileEvent fe) {
188
                cnt++;
189
                event = fe;
190
            }
191
192
        }
193
        L listener = new L();
194
        testFolder.addRecursiveListener(listener);
195
196
        file.delete();
197
198
        testFolder.refresh();
199
200
        assertEquals("Change notified", 1, listener.cnt);
201
        assertEquals("Right file", file, FileUtil.toFile(listener.event.getFile()));
202
    }
203
204
    public void testRecursiveListener() throws Exception {
205
        FileObject sub;
206
        File fobj;
207
        File fsub;
208
        {
209
            FileObject obj = FileUtil.createData(testFolder, "my/sub/children/children.java");
210
            fobj = FileUtil.toFile(obj);
211
            assertNotNull("File found", fobj);
212
            sub = obj.getParent().getParent();
213
            fsub = FileUtil.toFile(sub);
214
215
            WeakReference<Object> ref = new WeakReference<Object>(obj);
216
            obj = null;
217
            assertGC("File object can disappear", ref);
218
        }
219
220
        class L implements FileChangeListener {
221
            StringBuilder sb = new StringBuilder();
222
223
            public void fileFolderCreated(FileEvent fe) {
224
                LOG.info("FolderCreated: " + fe.getFile());
225
                sb.append("FolderCreated");
226
            }
227
228
            public void fileDataCreated(FileEvent fe) {
229
                LOG.info("DataCreated: " + fe.getFile());
230
                sb.append("DataCreated");
231
            }
232
233
            public void fileChanged(FileEvent fe) {
234
                LOG.info("Changed: " + fe.getFile());
235
                sb.append("Changed");
236
            }
237
238
            public void fileDeleted(FileEvent fe) {
239
                LOG.info("Deleted: " + fe.getFile());
240
                sb.append("Deleted");
241
            }
242
243
            public void fileRenamed(FileRenameEvent fe) {
244
                LOG.info("Renamed: " + fe.getFile());
245
                sb.append("Renamed");
246
            }
247
248
            public void fileAttributeChanged(FileAttributeEvent fe) {
249
                if (fe.getName().startsWith("DataEditorSupport.read-only.refresh")) {
250
                    return;
251
                }
252
                LOG.info("AttributeChanged: " + fe.getFile());
253
                sb.append("AttributeChanged");
254
            }
255
256
            public void assertMessages(String txt, String msg) {
257
                assertEquals(txt, msg, sb.toString());
258
                sb.setLength(0);
259
            }
260
        }
261
        L recursive = new L();
262
        L flat = new L();
263
264
        sub.addFileChangeListener(flat);
265
        LOG.info("Adding listener");
266
        sub.addRecursiveListener(recursive);
267
        LOG.info("Adding listener finished");
268
269
        Thread.sleep(1000);
270
271
        File fo = new File(fobj.getParentFile(), "sibling.java");
272
        fo.createNewFile();
273
        LOG.info("sibling created, now refresh");
274
        lfs.refresh(true);
275
        LOG.info("sibling refresh finished");
276
277
        recursive.assertMessages("Creation", "DataCreated");
278
        flat.assertMessages("No messages in flat mode", "");
279
280
        Thread.sleep(1000);
281
282
        final OutputStream os = new FileOutputStream(fo);
283
        os.write(10);
284
        os.close();
285
        LOG.info("Before refresh");
286
        lfs.refresh(true);
287
        LOG.info("After refresh");
288
289
        flat.assertMessages("No messages in flat mode", "");
290
        recursive.assertMessages("written", "Changed");
291
292
        fo.delete();
293
        lfs.refresh(true);
294
295
        flat.assertMessages("No messages in flat mode", "");
296
        recursive.assertMessages("gone", "Deleted");
297
298
        new File(fsub, "testFolder").mkdirs();
299
        lfs.refresh(true);
300
301
        flat.assertMessages("Direct Folder notified", "FolderCreated");
302
        recursive.assertMessages("Direct Folder notified", "FolderCreated");
303
304
        new File(fsub.getParentFile(), "unimportant.txt").createNewFile();
305
        lfs.refresh(true);
306
307
        flat.assertMessages("No messages in flat mode", "");
308
        recursive.assertMessages("No messages in recursive mode", "");
309
310
        File deepest = new File(new File(new File(fsub, "deep"), "deeper"), "deepest");
311
        deepest.mkdirs();
312
        lfs.refresh(true);
313
314
        flat.assertMessages("Folder in flat mode", "FolderCreated");
315
        recursive.assertMessages("Folder detected", "FolderCreated");
316
317
        File hidden = new File(deepest, "hide.me");
318
        hidden.createNewFile();
319
        lfs.refresh(true);
320
321
        flat.assertMessages("No messages in flat mode", "");
322
        recursive.assertMessages("Folder detected", "DataCreated");
323
324
325
        sub.removeRecursiveListener(recursive);
326
327
        new File(fsub, "test.data").createNewFile();
328
        lfs.refresh(true);
329
330
        flat.assertMessages("Direct file notified", "DataCreated");
331
        recursive.assertMessages("No longer active", "");
332
333
        WeakReference<L> ref = new WeakReference<L>(recursive);
334
        recursive = null;
335
        assertGC("Listener can be GCed", ref);
336
    }
337
338
}

Return to bug 170862