2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
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]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
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.
42
package org.netbeans.core.projects;
44
import java.beans.PropertyChangeEvent;
45
import java.beans.PropertyChangeListener;
46
import java.io.IOException;
47
import java.lang.ref.WeakReference;
48
import java.util.HashMap;
49
import java.util.Iterator;
50
import java.util.LinkedList;
51
import java.util.Map.Entry;
52
import java.util.WeakHashMap;
53
import org.netbeans.core.startup.layers.SessionManager;
54
import org.openide.filesystems.FileChangeAdapter;
55
import org.openide.filesystems.FileChangeListener;
56
import org.openide.filesystems.FileEvent;
57
import org.openide.filesystems.FileLock;
58
import org.openide.filesystems.FileObject;
59
import org.openide.filesystems.FileRenameEvent;
60
import org.openide.filesystems.FileStateInvalidException;
61
import org.openide.filesystems.FileSystem;
62
import org.openide.filesystems.FileUtil;
63
import org.openide.filesystems.Repository;
65
/** Scans positions of FileObject-delegates for FileObjects from SystemFileSystem. Each
67
* @author Vitezslav Stejskal
69
final class FileStateManager {
71
/** Identification of filesystem representing Session */
72
public static final int LAYER_SESSION = 1;
73
/** Identification of filesystem representing XML-layers from all installed modules */
74
public static final int LAYER_MODULES = 2;
76
/** File State - file is defined on the layer (top-most layer containing the file) */
77
public static final int FSTATE_DEFINED = 0;
78
/** File State - file is ignored on the layer (higher layer contains file too) */
79
public static final int FSTATE_IGNORED = 1;
80
/** File State - file is inherited on the layer (file doesn't exist on the layer and exists on lower layer) */
81
public static final int FSTATE_INHERITED = 2;
82
/** File State - file is not defined on the layer (file doesn't exist on the layer and exists on higher layer) */
83
public static final int FSTATE_UNDEFINED = 3;
85
/** Singleton instance of FileStateManager */
86
private static FileStateManager manager = null;
87
/** Cache of collected information */
88
private WeakHashMap<FileObject, FileInfo> info = new WeakHashMap<FileObject, FileInfo> ();
89
/** Number of layers on {@link SystemFileSystem} */
90
private static final int LAYERS_COUNT = 3;
91
/** Layers of {@link SystemFileSystem}, LAYER_* constants can be used as indexes. */
92
private FileSystem layers [] = new FileSystem [LAYERS_COUNT];
93
/** List of listeners listening on changes in file state */
94
private HashMap<FileStatusListener,LinkedList<FileObject>> listeners = new HashMap<FileStatusListener,LinkedList<FileObject>> (10);
95
/** Listener attached to SessionManager, it refreshes list of layers if some are added or removed */
96
private PropertyChangeListener propL = null;
98
public static synchronized FileStateManager getDefault () {
99
if (manager == null) {
100
manager = new FileStateManager ();
105
/** Creates new FileStateManager */
106
private FileStateManager () {
110
// listen on changes of layers made through the SessionManager
111
propL = new PropL ();
112
SessionManager.getDefault ().addPropertyChangeListener (
113
org.openide.util.WeakListeners.propertyChange (propL, SessionManager.getDefault ()));
116
public void define (final FileObject mfo, int layer, boolean revert) throws IOException {
117
// ignore request when file is already defined on layer
118
if (FSTATE_DEFINED == getFileState (mfo, layer))
121
FileSystem fsLayer = getLayer (layer);
123
throw new IllegalArgumentException ("Invalid layer " + layer); //NOI18N
125
// find file on specified layer
126
FileObject fo = fsLayer.findResource (mfo.getPath());
128
// remove the file if it exists and current definition should be preserved
129
if (fo != null && !revert) {
130
deleteImpl (mfo, fsLayer);
134
// create file on specified layer if it doesn't exist
136
String parent = mfo.getParent ().getPath();
137
final FileObject fparent = FileUtil.createFolder (fsLayer.getRoot (), parent);
138
fparent.getFileSystem().runAtomicAction(new FileSystem.AtomicAction() {
139
public void run () throws IOException {
140
mfo.copy (fparent, mfo.getName (), mfo.getExt ());
145
// remove above defined files
146
for (int i = 0; i < layer; i++) {
147
FileSystem fsl = getLayer (i);
149
deleteImpl (mfo, fsl);
153
public void delete (FileObject mfo, int layer) throws IOException {
154
FileSystem fsLayer = getLayer (layer);
156
throw new IllegalArgumentException ("Invalid layer " + layer); //NOI18N
158
deleteImpl (mfo, fsLayer);
161
public int getFileState (FileObject mfo, int layer) {
162
// check if the FileObject is from SystemFileSystem
163
FileSystem fs = null;
164
FileInfo finf = null;
167
fs = mfo.getFileSystem ();
168
} catch (FileStateInvalidException e) {
169
// ignore, will be handled later
172
if (fs == null || !Repository.getDefault ().getDefaultFileSystem ().equals (fs))
173
throw new IllegalArgumentException ("FileObject has to be from DefaultFileSystem - " + mfo);
175
synchronized (info) {
176
if (null == (finf = info.get(mfo))) {
177
finf = new FileInfo(mfo);
182
return finf.getState (layer);
185
public final void addFileStatusListener (FileStatusListener l, FileObject mfo) {
186
synchronized (listeners) {
187
LinkedList<FileObject> lst = null;
188
if (!listeners.containsKey (l)) {
189
lst = new LinkedList<FileObject> ();
190
listeners.put (l, lst);
193
lst = listeners.get (l);
195
if (!lst.contains (mfo))
200
public final void removeFileStatusListener (FileStatusListener l, FileObject mfo) {
201
synchronized (listeners) {
203
listeners.remove (l);
205
LinkedList<FileObject> lst = listeners.get (l);
209
listeners.remove (l);
215
@SuppressWarnings("unchecked")
216
private void fireFileStatusChanged (FileObject mfo) {
217
HashMap<FileStatusListener,LinkedList<FileObject>> h = null;
219
synchronized (listeners) {
220
h = (HashMap<FileStatusListener,LinkedList<FileObject>>)listeners.clone ();
223
for (Entry<FileStatusListener,LinkedList<FileObject>> entry: h.entrySet()) {
224
FileStatusListener l = entry.getKey();
225
LinkedList<FileObject> lst = entry.getValue();
226
if (lst.contains (mfo))
227
l.fileStatusChanged (mfo);
231
private void deleteImpl (FileObject mfo, FileSystem fsLayer) throws IOException {
232
FileObject fo = fsLayer.findResource (mfo.getPath());
234
FileLock lock = null;
245
private void discard (FileObject mfo) {
246
synchronized (info) {
251
private void getLayers () {
252
layers [LAYER_SESSION] = SessionManager.getDefault ().getLayer (SessionManager.LAYER_SESSION);
253
layers [LAYER_MODULES] = SessionManager.getDefault ().getLayer (SessionManager.LAYER_INSTALL);
256
private FileSystem getLayer (int layer) {
257
return layers [layer];
260
private class PropL implements PropertyChangeListener {
262
public void propertyChange (PropertyChangeEvent evt) {
263
if (SessionManager.PROP_OPEN.equals (evt.getPropertyName ())) {
264
FileObject mfos [] = null;
266
synchronized (info) {
267
mfos = (FileObject [])info.keySet ().toArray (new FileObject [info.size()]);
269
// invalidate all existing FileInfos
270
for (int i = 0; i < mfos.length; i++) {
271
FileInfo finf = info.get(mfos[i]);
280
// [PENDING] this should be better synchronized
284
for (int i = 0; i < mfos.length; i++)
285
fireFileStatusChanged (mfos [i]);
290
public static interface FileStatusListener {
291
public void fileStatusChanged (FileObject mfo);
294
private class FileInfo extends FileChangeAdapter {
295
private WeakReference<FileObject> file = null;
297
private int state [] = new int [LAYERS_COUNT];
298
private final Object LOCK = new Object ();
300
private FileObject notifiers [] = new FileObject [LAYERS_COUNT];
301
private FileChangeListener weakL [] = new FileChangeListener [LAYERS_COUNT];
303
public FileInfo (FileObject mfo) {
304
file = new WeakReference<FileObject> (mfo);
307
for (int i = 0; i < LAYERS_COUNT; i++) {
308
state [i] = getStateImpl (mfo, i);
311
// attach FileInfo to interesting FileObject on each layer
312
for (int i = 0; i < LAYERS_COUNT; i++) {
313
attachNotifier (mfo, i);
317
public void invalidate () {
318
detachAllNotifiers ();
319
synchronized (LOCK) {
320
for (int i = 0; i < LAYERS_COUNT; i++)
321
state [i] = FSTATE_UNDEFINED;
325
public int getState (int layer) {
326
synchronized (LOCK) {
327
return state [layer];
331
private void rescan (FileObject mfo) {
332
boolean changed = false;
334
synchronized (LOCK) {
335
for (int i = 0; i < LAYERS_COUNT; i++) {
336
int ns = getStateImpl (mfo, i);
337
if (state [i] != ns) {
345
fireFileStatusChanged (mfo);
348
private int getStateImpl (FileObject mfo, int layer) {
349
boolean above = false;
350
boolean below = false;
352
// scan higher layers
353
for (int i = 0; i < layer; i++) {
354
if (isOnLayer (mfo, i)) {
361
for (int i = layer + 1; i < LAYERS_COUNT; i++) {
362
if (isOnLayer (mfo, i)) {
368
if (isOnLayer (mfo, layer)) {
369
return above ? FSTATE_IGNORED : FSTATE_DEFINED;
372
return below && !above ? FSTATE_INHERITED : FSTATE_UNDEFINED;
376
private boolean isOnLayer (FileObject mfo, int layer) {
377
FileSystem fsLayer = getLayer (layer);
378
return fsLayer == null ? false : null != fsLayer.findResource (mfo.getPath());
382
* @param mfo FileObject from default file system
383
* @param layer the layer where notifier will be searched on
384
* @return true if attached notifier is the delegate FO
386
private synchronized boolean attachNotifier (FileObject mfo, int layer) {
387
FileSystem fsLayer = getLayer (layer);
388
String fn = mfo.getPath();
389
FileObject fo = null;
390
boolean isDelegate = true;
395
// find new notifier - the FileObject with closest match to getFile ()
396
while (fn.length () > 0 && null == (fo = fsLayer.findResource (fn))) {
397
int pos = fn.lastIndexOf ('/');
403
fn = fn.substring (0, pos);
407
fo = fsLayer.getRoot ();
409
if (fo != notifiers [layer]) {
410
// remove listener from existing notifier if any
411
if (notifiers [layer] != null)
412
notifiers [layer].removeFileChangeListener (weakL [layer]);
414
// create new listener and attach it to new notifier
415
weakL [layer] = FileUtil.weakFileChangeListener (this, fo);
416
fo.addFileChangeListener (weakL [layer]);
417
notifiers [layer] = fo;
423
private synchronized void detachAllNotifiers () {
424
for (int i = 0; i < LAYERS_COUNT; i++) {
425
if (notifiers [i] != null) {
426
notifiers [i].removeFileChangeListener (weakL [i]);
427
notifiers [i] = null;
433
private int layerOfFile (FileObject fo) {
435
FileSystem fs = fo.getFileSystem ();
436
for (int i = 0; i < LAYERS_COUNT; i++) {
437
if (fs.equals (getLayer (i)))
440
} catch (FileStateInvalidException e) {
441
throw (IllegalStateException) new IllegalStateException("Invalid file - " + fo).initCause(e); // NOI18N
444
// throw new IllegalStateException ("File isn't from any layer in DefaultFileSystem - " + fo); // NOI18N
447
// ---------------------- FileChangeListener events -----------------------------
449
public void fileRenamed (FileRenameEvent fe) {
450
// rename can be caused either by renaming fo or by deleting mfo,
451
// thus the safe way is to discard this FileInfo from the map and
452
// notify listeners about the change
453
FileObject mfo = file.get ();
454
if (mfo != null && mfo.isValid ()) {
456
fireFileStatusChanged (mfo);
459
detachAllNotifiers ();
462
public void fileDataCreated (FileEvent fe) {
463
FileObject mfo = file.get ();
464
if (mfo != null && mfo.isValid ()) {
465
String created = fe.getFile ().getPath();
466
String mfoname = mfo.getPath();
468
if (created.equals (mfoname)) {
470
if (-1 != (layer = layerOfFile (fe.getFile ())))
471
attachNotifier (mfo, layer);
477
detachAllNotifiers ();
480
public void fileFolderCreated (FileEvent fe) {
481
FileObject mfo = file.get ();
482
if (mfo != null && mfo.isValid ()) {
483
String created = fe.getFile ().getPath();
484
String mfoname = mfo.getPath();
486
if (mfoname.startsWith (created)) {
488
if (-1 != (layer = layerOfFile (fe.getFile ())))
489
if (attachNotifier (mfo, layer)) {
490
// delegate was created -> rescan
496
detachAllNotifiers ();
499
public void fileDeleted (FileEvent fe) {
500
FileObject mfo = file.get ();
501
if (mfo != null && mfo.isValid ()) {
502
String deleted = fe.getFile ().getPath();
503
String mfoname = mfo.getPath();
505
if (deleted.equals (mfoname)) {
507
if (-1 != (layer = layerOfFile (fe.getFile ())))
508
attachNotifier (mfo, layer);
514
detachAllNotifiers ();