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/test/unit/src/org/netbeans/modules/masterfs/filebasedfs/FileUtilTest.java (+143 lines)
Lines 239-244 Link Here
239
        assertGC("FileChangeListener not collected.", ref);
239
        assertGC("FileChangeListener not collected.", ref);
240
    }
240
    }
241
241
242
    public void testAddRecursiveListener() throws IOException, InterruptedException {
243
        clearWorkDir();
244
        File rootF = getWorkDir();
245
        File dirF = new File(rootF, "dir");
246
        File fileF = new File(dirF, "subdir");
247
248
        // adding listeners
249
        TestFileChangeListener fcl = new TestFileChangeListener();
250
        FileUtil.addRecursiveListener(fcl, fileF);
251
        try {
252
            FileUtil.addRecursiveListener(fcl, fileF);
253
            fail("Should not be possible to add listener for the same path.");
254
        } catch (IllegalArgumentException iae) {
255
            // ok
256
        }
257
        TestFileChangeListener fcl2 = new TestFileChangeListener();
258
        try {
259
            FileUtil.removeRecursiveListener(fcl2, fileF);
260
            fail("Should not be possible to remove listener which is not registered.");
261
        } catch (IllegalArgumentException iae) {
262
            // ok
263
        }
264
        FileUtil.addRecursiveListener(fcl2, fileF);
265
266
        // creation
267
        final FileObject rootFO = FileUtil.toFileObject(rootF);
268
        FileObject dirFO = rootFO.createFolder("dir");
269
        assertEquals("Event fired when just parent dir created.", 0, fcl.checkAll());
270
        FileObject fileFO = FileUtil.createData(dirFO, "subdir/subsubdir/file");
271
        assertEquals("Event not fired when file was created.", 3, fcl.check(EventType.FOLDER_CREATED));
272
        assertEquals("Event not fired when file was created.", 3, fcl2.check(EventType.FOLDER_CREATED));
273
        FileObject fileFO2 = FileUtil.createData(dirFO, "subdir/subsubdir/file2");
274
        assertEquals("Event not fired when file was created.", 2, fcl.check(EventType.DATA_CREATED));
275
        assertEquals("Event not fired when file was created.", 2, fcl2.check(EventType.DATA_CREATED));
276
        FileObject fileAFO = FileUtil.createData(dirFO, "fileA");
277
        assertEquals("No other events should be fired.", 0, fcl.checkAll());
278
279
        // remove listener
280
        FileUtil.removeRecursiveListener(fcl2, fileF);
281
        fcl2.disabled = true;
282
        assertEquals("No other events should be fired.", 0, fcl2.checkAll());
283
284
        // modification
285
        fileFO.getOutputStream().close();
286
        fileFO.getOutputStream().close();
287
        assertEquals("Event not fired when file was modified.", 2, fcl.check(EventType.CHANGED));
288
        // no event fired when other file modified
289
        fileAFO.getOutputStream().close();
290
        assertEquals("No other events should be fired.", 0, fcl.checkAll());
291
292
        // deletion
293
        fileFO.delete();
294
        assertEquals("Event not fired when file deleted.", 1, fcl.check(EventType.DELETED));
295
        dirFO.delete();
296
        assertEquals("Event not fired when parent dir deleted.", 2, fcl.checkAll());
297
        dirFO = rootFO.createFolder("dir");
298
        fileFO = FileUtil.createData(dirFO, "subdir/subsubdir/file");
299
        assertEquals("Event not fired when file was created.", 1, fcl.check(EventType.DATA_CREATED));
300
        assertEquals("Event not fired when dirs created.", 3, fcl.check(EventType.FOLDER_CREATED));
301
        dirFO.delete();
302
        assertEquals("Event not fired when parent dir deleted.", 2, fcl.check(EventType.DELETED));
303
        assertEquals("No other events should be fired.", 0, fcl.checkAll());
304
305
        // atomic action
306
        FileUtil.runAtomicAction(new Runnable() {
307
308
            public void run() {
309
                FileObject dirFO;
310
                try {
311
                    dirFO = rootFO.createFolder("dir");
312
                    rootFO.createFolder("fakedir");
313
                    rootFO.setAttribute("fake", "fake");
314
                    rootFO.createData("fakefile");
315
                    FileUtil.createData(dirFO, "subdir/subsubdir/file");
316
                } catch (IOException ex) {
317
                    throw new RuntimeException(ex);
318
                }
319
320
            }
321
        });
322
        assertEquals("Notifying the folder creation only.", 1, fcl.check(EventType.FOLDER_CREATED));
323
        assertEquals("No other events should be fired.", 0, fcl.checkAll());
324
325
        // rename
326
        dirFO = FileUtil.toFileObject(dirF);
327
        fileFO = FileUtil.toFileObject(fileF);
328
        FileLock lock = dirFO.lock();
329
        dirFO.rename(lock, "dirRenamed", null);
330
        lock.releaseLock();
331
        assertEquals("Event fired when parent dir renamed.", 0, fcl.checkAll());
332
        lock = fileFO.lock();
333
        fileFO.rename(lock, "fileRenamed", null);
334
        lock.releaseLock();
335
        assertEquals("Renamed event not fired.", 2, fcl.check(EventType.RENAMED));
336
        assertEquals("No other events should be fired.", 0, fcl.checkAll());
337
338
        // disk changes
339
        dirF.mkdir();
340
        final File subdir = new File(fileF, "subdir");
341
        subdir.mkdirs();
342
        final File newfile = new File(subdir, "newfile");
343
        assertTrue(newfile.createNewFile());
344
        FileUtil.refreshAll();
345
        assertEquals("Event not fired when file was created.", 1, fcl.check(EventType.FOLDER_CREATED));
346
        Thread.sleep(1000); // make sure timestamp changes
347
        new FileOutputStream(newfile).close();
348
        FileUtil.refreshAll();
349
        assertEquals("Event not fired when file was modified.", 2, fcl.check(EventType.CHANGED));
350
        assertEquals("Attribute change event not fired (see #129178).", 2, fcl.check(EventType.ATTRIBUTE_CHANGED));
351
        newfile.delete();
352
        FileUtil.refreshAll();
353
        assertEquals("Event not fired when file deleted.", 1, fcl.check(EventType.DELETED));
354
        assertEquals("No other events should be fired.", 0, fcl.checkAll());
355
356
        // disk changes #66444
357
        File fileX = new File(fileF, "oscilating.file");
358
        for (int cntr = 0; cntr < 50; cntr++) {
359
            fileX.getParentFile().mkdirs();
360
            new FileOutputStream(fileX).close();
361
            FileUtil.refreshAll();
362
            assertEquals("Event not fired when file was created; count=" + cntr, 2, fcl.check(EventType.DATA_CREATED));
363
            fileX.delete();
364
            fileX.getParentFile().delete();
365
            FileUtil.refreshAll();
366
            assertEquals("Event not fired when file deleted; count=" + cntr, 2, 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 315-320 Link Here
315
    };
451
    };
316
452
317
    private static class TestFileChangeListener implements FileChangeListener {
453
    private static class TestFileChangeListener implements FileChangeListener {
454
        boolean disabled;
318
455
319
        private final Map<EventType, List<FileEvent>> type2Event = new HashMap<EventType, List<FileEvent>>();
456
        private final Map<EventType, List<FileEvent>> type2Event = new HashMap<EventType, List<FileEvent>>();
320
457
Lines 356-381 Link Here
356
        }
493
        }
357
494
358
        public void fileFolderCreated(FileEvent fe) {
495
        public void fileFolderCreated(FileEvent fe) {
496
            assertFalse("No changes expected", disabled);
359
            type2Event.get(EventType.FOLDER_CREATED).add(fe);
497
            type2Event.get(EventType.FOLDER_CREATED).add(fe);
360
        }
498
        }
361
499
362
        public void fileDataCreated(FileEvent fe) {
500
        public void fileDataCreated(FileEvent fe) {
501
            assertFalse("No changes expected", disabled);
363
            type2Event.get(EventType.DATA_CREATED).add(fe);
502
            type2Event.get(EventType.DATA_CREATED).add(fe);
364
        }
503
        }
365
504
366
        public void fileChanged(FileEvent fe) {
505
        public void fileChanged(FileEvent fe) {
506
            assertFalse("No changes expected", disabled);
367
            type2Event.get(EventType.CHANGED).add(fe);
507
            type2Event.get(EventType.CHANGED).add(fe);
368
        }
508
        }
369
509
370
        public void fileDeleted(FileEvent fe) {
510
        public void fileDeleted(FileEvent fe) {
511
            assertFalse("No changes expected", disabled);
371
            type2Event.get(EventType.DELETED).add(fe);
512
            type2Event.get(EventType.DELETED).add(fe);
372
        }
513
        }
373
514
374
        public void fileRenamed(FileRenameEvent fe) {
515
        public void fileRenamed(FileRenameEvent fe) {
516
            assertFalse("No changes expected", disabled);
375
            type2Event.get(EventType.RENAMED).add(fe);
517
            type2Event.get(EventType.RENAMED).add(fe);
376
        }
518
        }
377
519
378
        public void fileAttributeChanged(FileAttributeEvent fe) {
520
        public void fileAttributeChanged(FileAttributeEvent fe) {
521
            assertFalse("No changes expected", disabled);
379
            type2Event.get(EventType.ATTRIBUTE_CHANGED).add(fe);
522
            type2Event.get(EventType.ATTRIBUTE_CHANGED).add(fe);
380
        }
523
        }
381
    }
524
    }
(-)32b3d5254468 (+166 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 org.openide.util.Utilities;
47
48
/**
49
 *
50
 * @author Jaroslav Tulach <jtulach@netbeans.org>
51
 */
52
final class DeepListener extends WeakReference<FileChangeListener>
53
implements FileChangeListener, Runnable {
54
    private final File path;
55
    private FileObject watching;
56
    private static List<DeepListener> keep = new ArrayList<DeepListener>();
57
58
    public DeepListener(FileChangeListener listener, File path) {
59
        super(listener, Utilities.activeReferenceQueue());
60
        this.path = path;
61
        relisten();
62
        keep.add(this);
63
    }
64
65
    public void run() {
66
        FileObject fo = FileUtil.toFileObject(path);
67
        if (fo != null) {
68
            fo.removeRecursiveListener(this);
69
        }
70
        keep.remove(this);
71
    }
72
73
    private synchronized void relisten() {
74
        FileObject fo = FileUtil.toFileObject(path);
75
        if (fo == watching) {
76
            return;
77
        }
78
        if (watching != null) {
79
            watching.removeRecursiveListener(this);
80
            watching = null;
81
        }
82
        if (fo != null) {
83
            watching = fo;
84
            fo.addRecursiveListener(this);
85
        }
86
    }
87
88
    public void fileRenamed(FileRenameEvent fe) {
89
        relisten();
90
        FileChangeListener listener = get();
91
        if (listener == null) {
92
            return;
93
        }
94
        listener.fileRenamed(fe);
95
    }
96
97
    public void fileFolderCreated(FileEvent fe) {
98
        relisten();
99
        FileChangeListener listener = get();
100
        if (listener == null) {
101
            return;
102
        }
103
        listener.fileFolderCreated(fe);
104
    }
105
106
    public void fileDeleted(FileEvent fe) {
107
        relisten();
108
        FileChangeListener listener = get();
109
        if (listener == null) {
110
            return;
111
        }
112
        listener.fileDeleted(fe);
113
    }
114
115
    public void fileDataCreated(FileEvent fe) {
116
        relisten();
117
        FileChangeListener listener = get();
118
        if (listener == null) {
119
            return;
120
        }
121
        listener.fileDataCreated(fe);
122
    }
123
124
    public void fileChanged(FileEvent fe) {
125
        FileChangeListener listener = get();
126
        if (listener == null) {
127
            return;
128
        }
129
        listener.fileChanged(fe);
130
    }
131
132
    public void fileAttributeChanged(FileAttributeEvent fe) {
133
        FileChangeListener listener = get();
134
        if (listener == null) {
135
            return;
136
        }
137
        listener.fileAttributeChanged(fe);
138
    }
139
140
    @Override
141
    public boolean equals(Object obj) {
142
        if (obj == null) {
143
            return false;
144
        }
145
        if (getClass() != obj.getClass()) {
146
            return false;
147
        }
148
        final DeepListener other = (DeepListener) obj;
149
        FileChangeListener thisListener = get();
150
        FileChangeListener otherListener = other.get();
151
        if (thisListener != otherListener && (thisListener == null || !thisListener.equals(otherListener))) {
152
            return false;
153
        }
154
        return true;
155
    }
156
157
    @Override
158
    public int hashCode() {
159
        FileChangeListener thisListener = get();
160
        int hash = 7;
161
        hash = 11 * hash + (thisListener != null ? thisListener.hashCode() : 0);
162
        return hash;
163
    }
164
165
166
}
(-)a/openide.filesystems/src/org/openide/filesystems/FileUtil.java (-1 / +54 lines)
Lines 255-260 Link Here
255
     * @since org.openide.filesystems 7.20
255
     * @since org.openide.filesystems 7.20
256
     */
256
     */
257
    public static void removeFileChangeListener(FileChangeListener listener, File path) {
257
    public static void removeFileChangeListener(FileChangeListener listener, File path) {
258
        removeFileChangeListenerImpl(listener, path);
259
    }
260
261
    private static FileChangeListener removeFileChangeListenerImpl(FileChangeListener listener, File path) {
258
        assert path.equals(FileUtil.normalizeFile(path)) : "Need to normalize " + path + "!";  //NOI18N
262
        assert path.equals(FileUtil.normalizeFile(path)) : "Need to normalize " + path + "!";  //NOI18N
259
        synchronized (holders) {
263
        synchronized (holders) {
260
            Map<File, Holder> f2H = holders.get(listener);
264
            Map<File, Holder> f2H = holders.get(listener);
Lines 265-273 Link Here
265
                throw new IllegalArgumentException(listener + " was not listening to " + path + "; only to " + f2H.keySet()); // NOI18N
269
                throw new IllegalArgumentException(listener + " was not listening to " + path + "; only to " + f2H.keySet()); // NOI18N
266
            }
270
            }
267
            // remove Holder instance from map and call run to unregister its current listener
271
            // remove Holder instance from map and call run to unregister its current listener
268
            f2H.remove(path).run();
272
            Holder h = f2H.remove(path);
273
            h.run();
274
            return h.get();
269
        }
275
        }
270
    }
276
    }
277
    /**
278
     * Adds a listener to changes under given path. It permits you to listen to a file
279
     * which does not yet exist, or continue listening to it after it is deleted and recreated, etc.
280
     * <br/>
281
     * When given path represents a file ({@code path.isDirectory() == false}), this
282
     * code behaves exectly like {@link #addFileChangeListener(org.openide.filesystems.FileChangeListener, java.io.File)}.
283
     * Usually the path shall represent a folder ({@code path.isDirectory() == true})
284
     * <ul>
285
     * <li>fileFolderCreated event is fired when the folder is created or a child folder created</li>
286
     * <li>fileDataCreated event is fired when a child file is created</li>
287
     * <li>fileDeleted event is fired when the folder is deleted or a child file/folder removed</li>
288
     * <li>fileChanged event is fired when a child file is modified</li>
289
     * <li>fileRenamed event is fired when the folder is renamed or a child file/folder is renamed</li>
290
     * <li>fileAttributeChanged is fired when FileObject's attribute is changed</li>
291
     *</ul>
292
     * The above events are delivered for changes in all subdirectories (recursively).
293
     * It is guaranteed that with each change at least one event is generated.
294
     * For example adding a folder does not notify about content of the folder,
295
     * hence one event is delivered.
296
     *
297
     * Can only add a given [listener, path] pair once. However a listener can
298
     * listen to any number of paths. Note that listeners are always held weakly
299
     * - if the listener is collected, it is quietly removed.
300
     *
301
     * @param listener FileChangeListener to listen to changes in path
302
     * @param path File path to listen to (even not existing)
303
     *
304
     * @see FileObject#addRecursiveListener
305
     * @since org.openide.filesystems 7.24
306
     */
307
    public static void addRecursiveListener(FileChangeListener listener, File path) {
308
        addFileChangeListener(new DeepListener(listener, path), path);
309
    }
310
311
    /**
312
     * Removes a listener to changes under given path.
313
     * @param listener FileChangeListener to be removed
314
     * @param path File path in which listener was listening
315
     * @throws IllegalArgumentException if listener was not listening to given path
316
     *
317
     * @see FileObject#removeFileChangeListener
318
     * @since org.openide.filesystems 7.24
319
     */
320
    public static void removeRecursiveListener(FileChangeListener listener, File path) {
321
        DeepListener dl = (DeepListener)removeFileChangeListenerImpl(new DeepListener(listener, path), path);
322
        dl.run();
323
    }
271
324
272
    /** Holds FileChangeListener and File pair and handle movement of auxiliary
325
    /** Holds FileChangeListener and File pair and handle movement of auxiliary
273
     * FileChangeListener to the first existing upper folder and firing appropriate events.
326
     * FileChangeListener to the first existing upper folder and firing appropriate events.

Return to bug 170862