001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.project.ant;
043:
044: import java.io.File;
045: import java.util.Map;
046: import java.util.WeakHashMap;
047: import org.openide.filesystems.FileObject;
048: import java.lang.ref.WeakReference;
049: import java.util.HashMap;
050:
051: import org.openide.filesystems.FileChangeListener;
052: import org.openide.filesystems.FileAttributeEvent;
053: import org.openide.filesystems.FileEvent;
054: import org.openide.filesystems.FileRenameEvent;
055: import org.openide.filesystems.FileUtil;
056: import org.openide.util.Utilities;
057:
058: // XXX current implementation is not efficient for listening to a large # of files
059:
060: /**
061: * Utility class to notify clients of changes in the existence or timestamp
062: * of a named file or directory.
063: * Unlike the Filesystems API, permits you to listen to a file which does not
064: * yet exist, or continue listening to it after it is deleted and recreated, etc.
065: * @author Jesse Glick
066: * @see "Blockers: #44213, #44628, #42147, etc."
067: * @see "#33162: hierarchical listeners"
068: */
069: public final class FileChangeSupport {
070:
071: public static final FileChangeSupport DEFAULT = new FileChangeSupport();
072:
073: private FileChangeSupport() {
074: }
075:
076: private final Map<FileChangeSupportListener, Map<File, Holder>> holders = new WeakHashMap<FileChangeSupportListener, Map<File, Holder>>();
077:
078: /**
079: * Add a listener to changes in a given path.
080: * Can only add a given listener x path pair once.
081: * However a listener can listen to any number of paths.
082: * Note that listeners are always held weakly - if the listener is collected,
083: * it is quietly removed.
084: */
085: public void addListener(FileChangeSupportListener listener,
086: File path) {
087: assert path.equals(FileUtil.normalizeFile(path)) : "Need to normalize "
088: + path + " before passing to FCS!";
089: synchronized (holders) {
090: Map<File, Holder> f2H = holders.get(listener);
091: if (f2H == null) {
092: f2H = new HashMap<File, Holder>();
093: holders.put(listener, f2H);
094: }
095: if (f2H.containsKey(path)) {
096: throw new IllegalArgumentException(
097: "Already listening to " + path); // NOI18N
098: }
099: f2H.put(path, new Holder(listener, path));
100: }
101: }
102:
103: /**
104: * Remove a listener to changes in a given path.
105: */
106: public void removeListener(FileChangeSupportListener listener,
107: File path) {
108: assert path.equals(FileUtil.normalizeFile(path)) : "Need to normalize "
109: + path + " before passing to FCS!";
110: synchronized (holders) {
111: Map<File, Holder> f2H = holders.get(listener);
112: if (f2H == null) {
113: throw new IllegalArgumentException(
114: "Was not listening to " + path); // NOI18N
115: }
116: if (!f2H.containsKey(path)) {
117: throw new IllegalArgumentException(listener
118: + " was not listening to " + path
119: + "; only to " + f2H.keySet()); // NOI18N
120: }
121: f2H.remove(path);
122: }
123: }
124:
125: private static final class Holder extends
126: WeakReference<FileChangeSupportListener> implements
127: FileChangeListener, Runnable {
128:
129: private final File path;
130: private FileObject current;
131: private File currentF;
132:
133: public Holder(FileChangeSupportListener listener, File path) {
134: super (listener, Utilities.activeReferenceQueue());
135: assert path != null;
136: this .path = path;
137: locateCurrent();
138: }
139:
140: private void locateCurrent() {
141: FileObject oldCurrent = current;
142: currentF = path;
143: while (true) {
144: try {
145: current = FileUtil.toFileObject(currentF);
146: } catch (IllegalArgumentException x) {
147: // #73526: was originally normalized, but now is not. E.g. file changed case.
148: currentF = FileUtil.normalizeFile(currentF);
149: current = FileUtil.toFileObject(currentF);
150: }
151: if (current != null) {
152: break;
153: }
154: currentF = currentF.getParentFile();
155: if (currentF == null) {
156: // #47320: can happen on Windows in case the drive does not exist.
157: // (Inside constructor for Holder.) In that case skip it.
158: return;
159: }
160: }
161: // XXX what happens with UNC paths?
162: assert current != null;
163: if (current != oldCurrent) {
164: if (oldCurrent != null) {
165: oldCurrent.removeFileChangeListener(this );
166: }
167: current.addFileChangeListener(this );
168: current.getChildren();//to get events about children
169: }
170: }
171:
172: private void someChange(FileObject modified) {
173: FileChangeSupportListener listener;
174: FileObject oldCurrent, nueCurrent;
175: File oldCurrentF, nueCurrentF;
176: synchronized (this ) {
177: if (current == null) {
178: return;
179: }
180: listener = get();
181: if (listener == null) {
182: return;
183: }
184: oldCurrent = current;
185: oldCurrentF = currentF;
186: locateCurrent();
187: nueCurrent = current;
188: nueCurrentF = currentF;
189: }
190: if (modified != null && modified == nueCurrent) {
191: FileChangeSupportEvent event = new FileChangeSupportEvent(
192: DEFAULT, FileChangeSupportEvent.EVENT_MODIFIED,
193: path);
194: listener.fileModified(event);
195: } else {
196: boolean oldWasCorrect = path.equals(oldCurrentF);
197: boolean nueIsCorrect = path.equals(nueCurrentF);
198: if (oldWasCorrect && !nueIsCorrect) {
199: FileChangeSupportEvent event = new FileChangeSupportEvent(
200: DEFAULT,
201: FileChangeSupportEvent.EVENT_DELETED, path);
202: listener.fileDeleted(event);
203: } else if (nueIsCorrect && !oldWasCorrect) {
204: FileChangeSupportEvent event = new FileChangeSupportEvent(
205: DEFAULT,
206: FileChangeSupportEvent.EVENT_CREATED, path);
207: listener.fileCreated(event);
208: }
209: }
210: }
211:
212: public void fileChanged(FileEvent fe) {
213: someChange(fe.getFile());
214: }
215:
216: public void fileDeleted(FileEvent fe) {
217: someChange(null);
218: }
219:
220: public void fileDataCreated(FileEvent fe) {
221: someChange(null);
222: }
223:
224: public void fileFolderCreated(FileEvent fe) {
225: someChange(null);
226: }
227:
228: public void fileRenamed(FileRenameEvent fe) {
229: someChange(null);
230: }
231:
232: public void fileAttributeChanged(FileAttributeEvent fe) {
233: // ignore
234: }
235:
236: public synchronized void run() {
237: if (current != null) {
238: current.removeFileChangeListener(this);
239: current = null;
240: }
241: }
242:
243: }
244:
245: }
|