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.core.projects;
043:
044: import java.beans.PropertyChangeEvent;
045: import java.beans.PropertyChangeListener;
046: import java.io.IOException;
047: import java.lang.ref.WeakReference;
048: import java.util.HashMap;
049: import java.util.Iterator;
050: import java.util.LinkedList;
051: import java.util.Map.Entry;
052: import java.util.WeakHashMap;
053: import org.netbeans.core.startup.layers.SessionManager;
054: import org.openide.filesystems.FileChangeAdapter;
055: import org.openide.filesystems.FileChangeListener;
056: import org.openide.filesystems.FileEvent;
057: import org.openide.filesystems.FileLock;
058: import org.openide.filesystems.FileObject;
059: import org.openide.filesystems.FileRenameEvent;
060: import org.openide.filesystems.FileStateInvalidException;
061: import org.openide.filesystems.FileSystem;
062: import org.openide.filesystems.FileUtil;
063: import org.openide.filesystems.Repository;
064:
065: /** Scans positions of FileObject-delegates for FileObjects from SystemFileSystem. Each
066: *
067: * @author Vitezslav Stejskal
068: */
069: final class FileStateManager {
070:
071: /** Identification of filesystem representing Session */
072: public static final int LAYER_SESSION = 1;
073: /** Identification of filesystem representing XML-layers from all installed modules */
074: public static final int LAYER_MODULES = 2;
075:
076: /** File State - file is defined on the layer (top-most layer containing the file) */
077: public static final int FSTATE_DEFINED = 0;
078: /** File State - file is ignored on the layer (higher layer contains file too) */
079: public static final int FSTATE_IGNORED = 1;
080: /** File State - file is inherited on the layer (file doesn't exist on the layer and exists on lower layer) */
081: public static final int FSTATE_INHERITED = 2;
082: /** File State - file is not defined on the layer (file doesn't exist on the layer and exists on higher layer) */
083: public static final int FSTATE_UNDEFINED = 3;
084:
085: /** Singleton instance of FileStateManager */
086: private static FileStateManager manager = null;
087: /** Cache of collected information */
088: private WeakHashMap<FileObject, FileInfo> info = new WeakHashMap<FileObject, FileInfo>();
089: /** Number of layers on {@link SystemFileSystem} */
090: private static final int LAYERS_COUNT = 3;
091: /** Layers of {@link SystemFileSystem}, LAYER_* constants can be used as indexes. */
092: private FileSystem layers[] = new FileSystem[LAYERS_COUNT];
093: /** List of listeners listening on changes in file state */
094: private HashMap<FileStatusListener, LinkedList<FileObject>> listeners = new HashMap<FileStatusListener, LinkedList<FileObject>>(
095: 10);
096: /** Listener attached to SessionManager, it refreshes list of layers if some are added or removed */
097: private PropertyChangeListener propL = null;
098:
099: public static synchronized FileStateManager getDefault() {
100: if (manager == null) {
101: manager = new FileStateManager();
102: }
103: return manager;
104: }
105:
106: /** Creates new FileStateManager */
107: private FileStateManager() {
108: // set layers
109: getLayers();
110:
111: // listen on changes of layers made through the SessionManager
112: propL = new PropL();
113: SessionManager.getDefault().addPropertyChangeListener(
114: org.openide.util.WeakListeners.propertyChange(propL,
115: SessionManager.getDefault()));
116: }
117:
118: public void define(final FileObject mfo, int layer, boolean revert)
119: throws IOException {
120: // ignore request when file is already defined on layer
121: if (FSTATE_DEFINED == getFileState(mfo, layer))
122: return;
123:
124: FileSystem fsLayer = getLayer(layer);
125: if (fsLayer == null)
126: throw new IllegalArgumentException("Invalid layer " + layer); //NOI18N
127:
128: // find file on specified layer
129: FileObject fo = fsLayer.findResource(mfo.getPath());
130:
131: // remove the file if it exists and current definition should be preserved
132: if (fo != null && !revert) {
133: deleteImpl(mfo, fsLayer);
134: fo = null;
135: }
136:
137: // create file on specified layer if it doesn't exist
138: if (fo == null) {
139: String parent = mfo.getParent().getPath();
140: final FileObject fparent = FileUtil.createFolder(fsLayer
141: .getRoot(), parent);
142: fparent.getFileSystem().runAtomicAction(
143: new FileSystem.AtomicAction() {
144: public void run() throws IOException {
145: mfo.copy(fparent, mfo.getName(), mfo
146: .getExt());
147: }
148: });
149: }
150:
151: // remove above defined files
152: for (int i = 0; i < layer; i++) {
153: FileSystem fsl = getLayer(i);
154: if (fsl != null)
155: deleteImpl(mfo, fsl);
156: }
157: }
158:
159: public void delete(FileObject mfo, int layer) throws IOException {
160: FileSystem fsLayer = getLayer(layer);
161: if (fsLayer == null)
162: throw new IllegalArgumentException("Invalid layer " + layer); //NOI18N
163:
164: deleteImpl(mfo, fsLayer);
165: }
166:
167: public int getFileState(FileObject mfo, int layer) {
168: // check if the FileObject is from SystemFileSystem
169: FileSystem fs = null;
170: FileInfo finf = null;
171:
172: try {
173: fs = mfo.getFileSystem();
174: } catch (FileStateInvalidException e) {
175: // ignore, will be handled later
176: }
177:
178: if (fs == null
179: || !Repository.getDefault().getDefaultFileSystem()
180: .equals(fs))
181: throw new IllegalArgumentException(
182: "FileObject has to be from DefaultFileSystem - "
183: + mfo);
184:
185: synchronized (info) {
186: if (null == (finf = info.get(mfo))) {
187: finf = new FileInfo(mfo);
188: info.put(mfo, finf);
189: }
190: }
191:
192: return finf.getState(layer);
193: }
194:
195: public final void addFileStatusListener(FileStatusListener l,
196: FileObject mfo) {
197: synchronized (listeners) {
198: LinkedList<FileObject> lst = null;
199: if (!listeners.containsKey(l)) {
200: lst = new LinkedList<FileObject>();
201: listeners.put(l, lst);
202: } else
203: lst = listeners.get(l);
204:
205: if (!lst.contains(mfo))
206: lst.add(mfo);
207: }
208: }
209:
210: public final void removeFileStatusListener(FileStatusListener l,
211: FileObject mfo) {
212: synchronized (listeners) {
213: if (mfo == null)
214: listeners.remove(l);
215: else {
216: LinkedList<FileObject> lst = listeners.get(l);
217: if (lst != null) {
218: lst.remove(mfo);
219: if (lst.isEmpty())
220: listeners.remove(l);
221: }
222: }
223: }
224: }
225:
226: @SuppressWarnings("unchecked")
227: private void fireFileStatusChanged(FileObject mfo) {
228: HashMap<FileStatusListener, LinkedList<FileObject>> h = null;
229:
230: synchronized (listeners) {
231: h = (HashMap<FileStatusListener, LinkedList<FileObject>>) listeners
232: .clone();
233: }
234:
235: for (Entry<FileStatusListener, LinkedList<FileObject>> entry : h
236: .entrySet()) {
237: FileStatusListener l = entry.getKey();
238: LinkedList<FileObject> lst = entry.getValue();
239: if (lst.contains(mfo))
240: l.fileStatusChanged(mfo);
241: }
242: }
243:
244: private void deleteImpl(FileObject mfo, FileSystem fsLayer)
245: throws IOException {
246: FileObject fo = fsLayer.findResource(mfo.getPath());
247: if (fo != null) {
248: FileLock lock = null;
249: try {
250: lock = fo.lock();
251: fo.delete(lock);
252: } finally {
253: if (lock != null)
254: lock.releaseLock();
255: }
256: }
257: }
258:
259: private void discard(FileObject mfo) {
260: synchronized (info) {
261: info.remove(mfo);
262: }
263: }
264:
265: private void getLayers() {
266: layers[LAYER_SESSION] = SessionManager.getDefault().getLayer(
267: SessionManager.LAYER_SESSION);
268: layers[LAYER_MODULES] = SessionManager.getDefault().getLayer(
269: SessionManager.LAYER_INSTALL);
270: }
271:
272: private FileSystem getLayer(int layer) {
273: return layers[layer];
274: }
275:
276: private class PropL implements PropertyChangeListener {
277: PropL() {
278: }
279:
280: public void propertyChange(PropertyChangeEvent evt) {
281: if (SessionManager.PROP_OPEN.equals(evt.getPropertyName())) {
282: FileObject mfos[] = null;
283:
284: synchronized (info) {
285: mfos = (FileObject[]) info.keySet().toArray(
286: new FileObject[info.size()]);
287:
288: // invalidate all existing FileInfos
289: for (int i = 0; i < mfos.length; i++) {
290: FileInfo finf = info.get(mfos[i]);
291:
292: if (finf != null)
293: finf.invalidate();
294: }
295:
296: // clear the cache
297: info.clear();
298:
299: // [PENDING] this should be better synchronized
300: getLayers();
301: }
302:
303: for (int i = 0; i < mfos.length; i++)
304: fireFileStatusChanged(mfos[i]);
305: }
306: }
307: }
308:
309: public static interface FileStatusListener {
310: public void fileStatusChanged(FileObject mfo);
311: }
312:
313: private class FileInfo extends FileChangeAdapter {
314: private WeakReference<FileObject> file = null;
315:
316: private int state[] = new int[LAYERS_COUNT];
317: private final Object LOCK = new Object();
318:
319: private FileObject notifiers[] = new FileObject[LAYERS_COUNT];
320: private FileChangeListener weakL[] = new FileChangeListener[LAYERS_COUNT];
321:
322: public FileInfo(FileObject mfo) {
323: file = new WeakReference<FileObject>(mfo);
324:
325: // get initial state
326: for (int i = 0; i < LAYERS_COUNT; i++) {
327: state[i] = getStateImpl(mfo, i);
328: }
329:
330: // attach FileInfo to interesting FileObject on each layer
331: for (int i = 0; i < LAYERS_COUNT; i++) {
332: attachNotifier(mfo, i);
333: }
334: }
335:
336: public void invalidate() {
337: detachAllNotifiers();
338: synchronized (LOCK) {
339: for (int i = 0; i < LAYERS_COUNT; i++)
340: state[i] = FSTATE_UNDEFINED;
341: }
342: }
343:
344: public int getState(int layer) {
345: synchronized (LOCK) {
346: return state[layer];
347: }
348: }
349:
350: private void rescan(FileObject mfo) {
351: boolean changed = false;
352:
353: synchronized (LOCK) {
354: for (int i = 0; i < LAYERS_COUNT; i++) {
355: int ns = getStateImpl(mfo, i);
356: if (state[i] != ns) {
357: state[i] = ns;
358: changed = true;
359: }
360: }
361: }
362:
363: if (changed)
364: fireFileStatusChanged(mfo);
365: }
366:
367: private int getStateImpl(FileObject mfo, int layer) {
368: boolean above = false;
369: boolean below = false;
370:
371: // scan higher layers
372: for (int i = 0; i < layer; i++) {
373: if (isOnLayer(mfo, i)) {
374: above = true;
375: break;
376: }
377: }
378:
379: // scan lower layers
380: for (int i = layer + 1; i < LAYERS_COUNT; i++) {
381: if (isOnLayer(mfo, i)) {
382: below = true;
383: break;
384: }
385: }
386:
387: if (isOnLayer(mfo, layer)) {
388: return above ? FSTATE_IGNORED : FSTATE_DEFINED;
389: } else {
390: return below && !above ? FSTATE_INHERITED
391: : FSTATE_UNDEFINED;
392: }
393: }
394:
395: private boolean isOnLayer(FileObject mfo, int layer) {
396: FileSystem fsLayer = getLayer(layer);
397: return fsLayer == null ? false : null != fsLayer
398: .findResource(mfo.getPath());
399: }
400:
401: /**
402: * @param mfo FileObject from default file system
403: * @param layer the layer where notifier will be searched on
404: * @return true if attached notifier is the delegate FO
405: */
406: private synchronized boolean attachNotifier(FileObject mfo,
407: int layer) {
408: FileSystem fsLayer = getLayer(layer);
409: String fn = mfo.getPath();
410: FileObject fo = null;
411: boolean isDelegate = true;
412:
413: if (fsLayer == null)
414: return false;
415:
416: // find new notifier - the FileObject with closest match to getFile ()
417: while (fn.length() > 0
418: && null == (fo = fsLayer.findResource(fn))) {
419: int pos = fn.lastIndexOf('/');
420: isDelegate = false;
421:
422: if (-1 == pos)
423: break;
424:
425: fn = fn.substring(0, pos);
426: }
427:
428: if (fo == null)
429: fo = fsLayer.getRoot();
430:
431: if (fo != notifiers[layer]) {
432: // remove listener from existing notifier if any
433: if (notifiers[layer] != null)
434: notifiers[layer]
435: .removeFileChangeListener(weakL[layer]);
436:
437: // create new listener and attach it to new notifier
438: weakL[layer] = FileUtil
439: .weakFileChangeListener(this , fo);
440: fo.addFileChangeListener(weakL[layer]);
441: notifiers[layer] = fo;
442: }
443:
444: return isDelegate;
445: }
446:
447: private synchronized void detachAllNotifiers() {
448: for (int i = 0; i < LAYERS_COUNT; i++) {
449: if (notifiers[i] != null) {
450: notifiers[i].removeFileChangeListener(weakL[i]);
451: notifiers[i] = null;
452: weakL[i] = null;
453: }
454: }
455: }
456:
457: private int layerOfFile(FileObject fo) {
458: try {
459: FileSystem fs = fo.getFileSystem();
460: for (int i = 0; i < LAYERS_COUNT; i++) {
461: if (fs.equals(getLayer(i)))
462: return i;
463: }
464: } catch (FileStateInvalidException e) {
465: throw (IllegalStateException) new IllegalStateException(
466: "Invalid file - " + fo).initCause(e); // NOI18N
467: }
468: return -1;
469: // throw new IllegalStateException ("File isn't from any layer in DefaultFileSystem - " + fo); // NOI18N
470: }
471:
472: // ---------------------- FileChangeListener events -----------------------------
473:
474: public void fileRenamed(FileRenameEvent fe) {
475: // rename can be caused either by renaming fo or by deleting mfo,
476: // thus the safe way is to discard this FileInfo from the map and
477: // notify listeners about the change
478: FileObject mfo = file.get();
479: if (mfo != null && mfo.isValid()) {
480: discard(mfo);
481: fireFileStatusChanged(mfo);
482: } else
483: detachAllNotifiers();
484: }
485:
486: public void fileDataCreated(FileEvent fe) {
487: FileObject mfo = file.get();
488: if (mfo != null && mfo.isValid()) {
489: String created = fe.getFile().getPath();
490: String mfoname = mfo.getPath();
491:
492: if (created.equals(mfoname)) {
493: int layer;
494: if (-1 != (layer = layerOfFile(fe.getFile())))
495: attachNotifier(mfo, layer);
496:
497: rescan(mfo);
498: }
499: } else
500: detachAllNotifiers();
501: }
502:
503: public void fileFolderCreated(FileEvent fe) {
504: FileObject mfo = file.get();
505: if (mfo != null && mfo.isValid()) {
506: String created = fe.getFile().getPath();
507: String mfoname = mfo.getPath();
508:
509: if (mfoname.startsWith(created)) {
510: int layer;
511: if (-1 != (layer = layerOfFile(fe.getFile())))
512: if (attachNotifier(mfo, layer)) {
513: // delegate was created -> rescan
514: rescan(mfo);
515: }
516: }
517: } else
518: detachAllNotifiers();
519: }
520:
521: public void fileDeleted(FileEvent fe) {
522: FileObject mfo = file.get();
523: if (mfo != null && mfo.isValid()) {
524: String deleted = fe.getFile().getPath();
525: String mfoname = mfo.getPath();
526:
527: if (deleted.equals(mfoname)) {
528: int layer;
529: if (-1 != (layer = layerOfFile(fe.getFile())))
530: attachNotifier(mfo, layer);
531:
532: rescan(mfo);
533: }
534: } else
535: detachAllNotifiers();
536: }
537: }
538: }
|