KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > api > project > ProjectManager


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.api.project;
21
22 import java.io.IOException JavaDoc;
23 import java.lang.ref.Reference JavaDoc;
24 import java.util.Arrays JavaDoc;
25 import java.util.HashSet JavaDoc;
26 import java.util.Iterator JavaDoc;
27 import java.util.Map JavaDoc;
28 import java.util.Set JavaDoc;
29 import java.util.WeakHashMap JavaDoc;
30 import org.netbeans.modules.projectapi.TimedWeakReference;
31 import org.netbeans.spi.project.ProjectFactory;
32 import org.netbeans.spi.project.ProjectState;
33 import org.openide.ErrorManager;
34 import org.openide.filesystems.FileChangeAdapter;
35 import org.openide.filesystems.FileChangeListener;
36 import org.openide.filesystems.FileEvent;
37 import org.openide.filesystems.FileObject;
38 import org.openide.filesystems.FileUtil;
39 import org.openide.util.Lookup;
40 import org.openide.util.LookupEvent;
41 import org.openide.util.LookupListener;
42 import org.openide.util.Mutex;
43 import org.openide.util.MutexException;
44 import org.openide.util.Union2;
45 import org.openide.util.WeakSet;
46
47 /**
48  * Manages loaded projects.
49  * @author Jesse Glick
50  */

51 public final class ProjectManager {
52     
53     // XXX need to figure out how to convince the system that a Project object is modified
54
// so that Save All and the exit dialog work... could temporarily use a DataLoader
55
// which recognizes project dirs and gives them a SaveCookie, perhaps
56
// see also #36280
57
// (but currently customizers always save the project on exit, so not so high priority)
58

59     // XXX change listeners?
60

61     private static final ErrorManager ERR = ErrorManager.getDefault().getInstance(ProjectManager.class.getName());
62     // for unit testing purposes:
63
private static final int ERR_LVL = Boolean.getBoolean(ProjectManager.class.getName() + ".LOG_WARN") ? ErrorManager.WARNING : ErrorManager.INFORMATIONAL; // NOI18N
64

65     private static final Lookup.Result<ProjectFactory> factories =
66         Lookup.getDefault().lookupResult(ProjectFactory.class);
67     
68     private ProjectManager() {
69         factories.addLookupListener(new LookupListener() {
70             public void resultChanged(LookupEvent e) {
71                 clearNonProjectCache();
72             }
73         });
74     }
75     
76     private static final ProjectManager DEFAULT = new ProjectManager();
77
78     /**
79      * Returns the singleton project manager instance.
80      * @return the default instance
81      */

82     public static ProjectManager getDefault() {
83         return DEFAULT;
84     }
85     
86     private static final Mutex MUTEX = new Mutex();
87     /**
88      * Get a read/write lock to be used for all project metadata accesses.
89      * All methods relating to recognizing and loading projects, saving them,
90      * getting or setting their metadata, etc. should be controlled by this
91      * mutex and be marked as read operations or write operations. Unless
92      * otherwise stated, project-related methods automatically acquire the
93      * mutex for you, so you do not necessarily need to pay attention to it;
94      * but you may directly acquire the mutex in order to ensure that a block
95      * of reads does not have any interspersed writes, or in order to ensure
96      * that a write is not clobbering an unrelated write, etc.
97      * @return a general read/write lock for project metadata operations of all sorts
98      */

99     public static Mutex mutex() {
100         return MUTEX;
101     }
102     
103     private static enum LoadStatus {
104         /**
105          * Marker for a directory which is known to not be a project.
106          */

107         NO_SUCH_PROJECT,
108         /**
109          * Marker for a directory which is known to (probably) be a project but is not loaded.
110          */

111         SOME_SUCH_PROJECT,
112         /**
113          * Marker for a directory which may currently be being loaded as a project.
114          * When this is the value, other reader threads should wait for the result.
115          */

116         LOADING_PROJECT;
117         
118         public boolean is(Union2<Reference JavaDoc<Project>,LoadStatus> o) {
119             return o != null && o.hasSecond() && o.second() == this;
120         }
121         
122         public Union2<Reference JavaDoc<Project>,LoadStatus> wrap() {
123             return Union2.createSecond(this);
124         }
125     }
126     
127     /**
128      * Cache of loaded projects (modified or not).
129      * Also caches a dir which is <em>not</em> a project.
130      */

131     private final Map JavaDoc<FileObject,Union2<Reference JavaDoc<Project>,LoadStatus>> dir2Proj = new WeakHashMap JavaDoc<FileObject,Union2<Reference JavaDoc<Project>,LoadStatus>>();
132     
133     /**
134      * Set of modified projects (subset of loaded projects).
135      */

136     private final Set JavaDoc<Project> modifiedProjects = new HashSet JavaDoc<Project>();
137     
138     private final Set JavaDoc<Project> removedProjects = new WeakSet<Project>();
139     
140     /**
141      * Mapping from projects to the factories that created them.
142      */

143     private final Map JavaDoc<Project,ProjectFactory> proj2Factory = new WeakHashMap JavaDoc<Project,ProjectFactory>();
144     
145     /**
146      * Checks for deleted projects.
147      */

148     private final FileChangeListener projectDeletionListener = new ProjectDeletionListener();
149     
150     /**
151      * The thread which is currently loading a project, if any.
152      */

153     private Thread JavaDoc loadingThread = null;
154     
155     /**
156      * Clear internal state.
157      * Useful from unit tests.
158      */

159     void reset() {
160         dir2Proj.clear();
161         modifiedProjects.clear();
162         proj2Factory.clear();
163     }
164     
165     /** For use from unit tests. */
166     static boolean quiet = false;
167     
168     /**
169      * Find an open project corresponding to a given project directory.
170      * Will be created in memory if necessary.
171      * <p>
172      * Acquires read access.
173      * </p>
174      * <p>
175      * It is <em>not</em> guaranteed that the returned instance will be identical
176      * to that which is created by the appropriate {@link ProjectFactory}. In
177      * particular, the project manager is free to return only wrapper <code>Project</code>
178      * instances which delegate to the factory's implementation. If you know your
179      * factory created a particular project, you cannot safely cast the return value
180      * of this method to your project type implementation class; you should instead
181      * place an implementation of some suitable private interface into your project's
182      * lookup, which would be safely proxied.
183      * </p>
184      * @param projectDirectory the project top directory
185      * @return the project (object identity may or may not vary between calls)
186      * or null if the directory is not recognized as a project by any
187      * registered {@link ProjectFactory}
188      * (might be null even if {@link #isProject} returns true)
189      * @throws IOException if the project was recognized but could not be loaded
190      * @throws IllegalArgumentException if the supplied file object is null or not a folder
191      */

192     public Project findProject(final FileObject projectDirectory) throws IOException JavaDoc, IllegalArgumentException JavaDoc {
193         if (projectDirectory == null) {
194             throw new IllegalArgumentException JavaDoc("Attempted to pass a null directory to findProject"); // NOI18N
195
}
196         if (!projectDirectory.isFolder()) {
197             throw new IllegalArgumentException JavaDoc("Attempted to pass a non-directory to findProject: " + projectDirectory); // NOI18N
198
}
199         try {
200             return mutex().readAccess(new Mutex.ExceptionAction<Project>() {
201                 public Project run() throws IOException JavaDoc {
202                     // Read access, but still needs to synch on the cache since there
203
// may be >1 reader.
204
try {
205                         boolean wasSomeSuchProject;
206                     synchronized (dir2Proj) {
207                         Union2<Reference JavaDoc<Project>,LoadStatus> o;
208                         do {
209                             o = dir2Proj.get(projectDirectory);
210                             if (LoadStatus.LOADING_PROJECT.is(o)) {
211                                 try {
212                                     if (Thread.currentThread() == loadingThread) {
213                                         throw new IllegalStateException JavaDoc("Attempt to call ProjectManager.findProject within the body of ProjectFactory.loadProject (hint: try using ProjectManager.mutex().postWriteRequest(...) within the body of your Project's constructor to prevent this)"); // NOI18N
214
}
215                                     if (ERR.isLoggable(ERR_LVL)) {
216                                         ERR.log(ERR_LVL, "findProject(" + projectDirectory + ") in " + Thread.currentThread().getName() + ": waiting for LOADING_PROJECT...");
217                                     }
218                                     dir2Proj.wait();
219                                     if (ERR.isLoggable(ERR_LVL)) {
220                                         ERR.log(ERR_LVL, "findProject(" + projectDirectory + ") in " + Thread.currentThread().getName() + ": ...done waiting for LOADING_PROJECT");
221                                     }
222                                 } catch (InterruptedException JavaDoc e) {
223                                     e.printStackTrace();
224                                 }
225                             }
226                         } while (LoadStatus.LOADING_PROJECT.is(o));
227                         assert !LoadStatus.LOADING_PROJECT.is(o);
228                         wasSomeSuchProject = LoadStatus.SOME_SUCH_PROJECT.is(o);
229                         if (LoadStatus.NO_SUCH_PROJECT.is(o)) {
230                             if (ERR.isLoggable(ERR_LVL)) {
231                                 ERR.log(ERR_LVL, "findProject(" + projectDirectory + ") in " + Thread.currentThread().getName() + ": NO_SUCH_PROJECT");
232                             }
233                             return null;
234                         } else if (o != null && !LoadStatus.SOME_SUCH_PROJECT.is(o)) {
235                             Project p = o.first().get();
236                             if (p != null) {
237                                 if (ERR.isLoggable(ERR_LVL)) {
238                                     ERR.log(ERR_LVL, "findProject(" + projectDirectory + ") in " + Thread.currentThread().getName() + ": cached project");
239                                 }
240                                 return p;
241                             }
242                         }
243                         // not in cache
244
dir2Proj.put(projectDirectory, LoadStatus.LOADING_PROJECT.wrap());
245                         loadingThread = Thread.currentThread();
246                         if (ERR.isLoggable(ERR_LVL)) {
247                             ERR.log(ERR_LVL, "findProject(" + projectDirectory + ") in " + Thread.currentThread().getName() + ": will load new project...");
248                         }
249                     }
250                     boolean resetLP = false;
251                     try {
252                         Project p = createProject(projectDirectory);
253                         if (ERR.isLoggable(ERR_LVL)) {
254                             ERR.log(ERR_LVL, "findProject(" + projectDirectory + ") in " + Thread.currentThread().getName() + ": created new project");
255                             //Thread.dumpStack();
256
}
257                         synchronized (dir2Proj) {
258                             dir2Proj.notifyAll();
259                             projectDirectory.addFileChangeListener(projectDeletionListener);
260                             if (p != null) {
261                                 dir2Proj.put(projectDirectory, Union2.<Reference JavaDoc<Project>,LoadStatus>createFirst(new TimedWeakReference<Project>(p)));
262                                 resetLP = true;
263                                 return p;
264                             } else {
265                                 dir2Proj.put(projectDirectory, LoadStatus.NO_SUCH_PROJECT.wrap());
266                                 resetLP = true;
267                                 if (wasSomeSuchProject) {
268                                     ERR.log(ErrorManager.WARNING, "Directory " + FileUtil.getFileDisplayName(projectDirectory) + " was initially claimed to be a project folder but really was not");
269                                 }
270                                 return null;
271                             }
272                         }
273                     } catch (IOException JavaDoc e) {
274                         if (ERR.isLoggable(ERR_LVL)) {
275                             ERR.log(ERR_LVL, "findProject(" + projectDirectory + ") in " + Thread.currentThread().getName() + ": error loading project: " + e);
276                         }
277                         // Do not cache the exception. Might be useful in some cases
278
// but would also cause problems if there were a project that was
279
// temporarily corrupted, fP is called, then it is fixed, then fP is
280
// called again (without anything being GC'd)
281
throw e;
282                     } finally {
283                         loadingThread = null;
284                         if (!resetLP) {
285                             // IOException or a runtime exception interrupted.
286
if (ERR.isLoggable(ERR_LVL)) {
287                                 ERR.log(ERR_LVL, "findProject(" + projectDirectory + ") in " + Thread.currentThread().getName() + ": cleaning up after error");
288                             }
289                             synchronized (dir2Proj) {
290                                 assert LoadStatus.LOADING_PROJECT.is(dir2Proj.get(projectDirectory));
291                                 dir2Proj.remove(projectDirectory);
292                                 dir2Proj.notifyAll(); // make sure other threads can continue
293
}
294                         }
295                     }
296     // Workaround for issue #51911:
297
// Log project creation exception here otherwise it can get lost
298
// in following scenario:
299
// If project creation calls ProjectManager.postWriteRequest() (what for
300
// example FreeformSources.initSources does) and then it throws an
301
// exception then this exception can get lost because leaving read mutex
302
// will immediately execute the runnable posted by
303
// ProjectManager.postWriteRequest() and if this runnable fails (what
304
// for FreeformSources.initSources will happen because
305
// AntBasedProjectFactorySingleton.getProjectFor() will not find project in
306
// its helperRef cache) then only this second fail is logged, but the cause -
307
// the failure to create project - is never logged. So, better log it here:
308
} catch (Error JavaDoc e) {
309                     ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
310                     throw e;
311                 } catch (RuntimeException JavaDoc e) {
312                     ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
313                     throw e;
314                 } catch (IOException JavaDoc e) {
315                     if (!quiet) ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
316                     throw e;
317                 }
318                 }
319             });
320         } catch (MutexException e) {
321             throw (IOException JavaDoc)e.getException();
322         }
323     }
324     
325     /**
326      * Create a project from a given directory.
327      * @param dir the project dir
328      * @return a project made from it, or null if it is not recognized
329      * @throws IOException if there was a problem loading the project
330      */

331     private Project createProject(FileObject dir) throws IOException JavaDoc {
332         assert dir != null;
333         assert dir.isFolder();
334         assert mutex().isReadAccess();
335         ProjectStateImpl state = new ProjectStateImpl();
336         for (ProjectFactory factory : factories.allInstances()) {
337             Project p = factory.loadProject(dir, state);
338             if (p != null) {
339                 proj2Factory.put(p, factory);
340                 state.attach(p);
341                 return p;
342             }
343         }
344         return null;
345     }
346     
347     /**
348      * Check whether a given directory is likely to contain a project without
349      * actually loading it.
350      * Should be faster and use less memory than {@link #findProject} when called
351      * on a large number of directories.
352      * <p>The result is not guaranteed to be accurate; there may be false positives
353      * (directories for which <code>isProject</code> is true but {@link #findProject}
354      * will return false), for example if there is trouble loading the project.
355      * False negatives are possible only if there are bugs in the project factory.</p>
356      * <p>Acquires read access.</p>
357      * <p class="nonnormative">
358      * You do <em>not</em> need to call this method if you just plan to call {@link #findProject}
359      * afterwards. It is intended for only those clients which would discard the
360      * result of {@link #findProject} other than to check for null, and which
361      * can also tolerate false positives.
362      * </p>
363      * @param projectDirectory a directory which may be some project's top directory
364      * @return true if the directory is likely to contain a project according to
365      * some registered {@link ProjectFactory}
366      * @throws IllegalArgumentException if the supplied file object is null or not a folder
367      */

368     public boolean isProject(final FileObject projectDirectory) throws IllegalArgumentException JavaDoc {
369         if (projectDirectory == null) {
370             throw new IllegalArgumentException JavaDoc("Attempted to pass a null directory to isProject"); // NOI18N
371
}
372         if (!projectDirectory.isFolder() ) {
373             //#78215 it can happen that a no longer existing folder is queried. throw
374
// exception only for real wrong usage..
375
if (projectDirectory.isValid()) {
376                 throw new IllegalArgumentException JavaDoc("Attempted to pass a non-directory to isProject: " + projectDirectory); // NOI18N
377
} else {
378                 return false;
379             }
380         }
381         return mutex().readAccess(new Mutex.Action<Boolean JavaDoc>() {
382             public Boolean JavaDoc run() {
383                 synchronized (dir2Proj) {
384                     Union2<Reference JavaDoc<Project>,LoadStatus> o;
385                     do {
386                         o = dir2Proj.get(projectDirectory);
387                         if (LoadStatus.LOADING_PROJECT.is(o)) {
388                             try {
389                                 dir2Proj.wait();
390                             } catch (InterruptedException JavaDoc e) {
391                                 e.printStackTrace();
392                             }
393                         }
394                     } while (LoadStatus.LOADING_PROJECT.is(o));
395                     assert !LoadStatus.LOADING_PROJECT.is(o);
396                     if (LoadStatus.NO_SUCH_PROJECT.is(o)) {
397                         return false;
398                     } else if (o != null) {
399                         // Reference<Project> or SOME_SUCH_PROJECT
400
return true;
401                     }
402                     // Not in cache.
403
dir2Proj.put(projectDirectory, LoadStatus.LOADING_PROJECT.wrap());
404                 }
405                 boolean resetLP = false;
406                 try {
407                     boolean p = checkForProject(projectDirectory);
408                     synchronized (dir2Proj) {
409                         resetLP = true;
410                         dir2Proj.notifyAll();
411                         if (p) {
412                             dir2Proj.put(projectDirectory, LoadStatus.SOME_SUCH_PROJECT.wrap());
413                             return true;
414                         } else {
415                             dir2Proj.put(projectDirectory, LoadStatus.NO_SUCH_PROJECT.wrap());
416                             return false;
417                         }
418                     }
419                 } finally {
420                     if (!resetLP) {
421                         // some runtime exception interrupted.
422
assert LoadStatus.LOADING_PROJECT.is(dir2Proj.get(projectDirectory));
423                         dir2Proj.remove(projectDirectory);
424                     }
425                 }
426             }
427         });
428     }
429     
430     private boolean checkForProject(FileObject dir) {
431         assert dir != null;
432         assert dir.isFolder() : dir;
433         assert mutex().isReadAccess();
434         Iterator JavaDoc it = factories.allInstances().iterator();
435         while (it.hasNext()) {
436             ProjectFactory factory = (ProjectFactory)it.next();
437             if (factory.isProject(dir)) {
438                 return true;
439             }
440         }
441         return false;
442     }
443     
444     /**
445      * Clear the cached list of folders thought <em>not</em> to be projects.
446      * This may be useful after creating project metadata in a folder, etc.
447      * Cached project objects, i.e. folders that <em>are</em> known to be
448      * projects, are not affected.
449      */

450     public void clearNonProjectCache() {
451         synchronized (dir2Proj) {
452             dir2Proj.values().removeAll(Arrays.asList(new Object JavaDoc[] {
453                 LoadStatus.NO_SUCH_PROJECT.wrap(),
454                 LoadStatus.SOME_SUCH_PROJECT.wrap(),
455             }));
456             // XXX remove everything too? but then e.g. AntProjectFactorySingleton
457
// will stay while its delegates are changed, which does no good
458
// XXX should there be any way to signal that a particular
459
// folder should be "reloaded" by a new factory?
460
}
461     }
462     
463     private final class ProjectStateImpl implements ProjectState {
464         
465         private Project p;
466         
467         void attach(Project p) {
468             assert p != null;
469             assert this.p == null;
470             this.p = p;
471         }
472         
473         public void markModified() {
474             assert p != null;
475             if (ERR.isLoggable(ERR_LVL)) {
476                 ERR.log(ERR_LVL, "markModified(" + p.getProjectDirectory()+ ")");
477             }
478             mutex().writeAccess(new Mutex.Action<Void JavaDoc>() {
479                 public Void JavaDoc run() {
480                     if (!proj2Factory.containsKey(p)) {
481                         throw new IllegalStateException JavaDoc("An attempt to call ProjectState.markModified on a deleted project: " + p.getProjectDirectory()); // NOI18N
482
}
483                     modifiedProjects.add(p);
484                     return null;
485                 }
486             });
487         }
488
489         public void notifyDeleted() throws IllegalStateException JavaDoc {
490             assert p != null;
491             mutex().writeAccess(new Mutex.Action<Void JavaDoc>() {
492                 public Void JavaDoc run() {
493                     if (proj2Factory.get(p) == null) {
494                         throw new IllegalStateException JavaDoc("An attempt to call notifyDeleted more than once. Project: " + p.getProjectDirectory()); // NOI18N
495
}
496                     
497                     dir2Proj.remove(p.getProjectDirectory());
498                     proj2Factory.remove(p);
499                     modifiedProjects.remove(p);
500                     removedProjects.add(p);
501                     return null;
502                 }
503             });
504         }
505
506     }
507     
508     /**
509      * Get a list of all projects which are modified and need to be saved.
510      * <p>Acquires read access.
511      * @return an immutable set of projects
512      */

513     public Set JavaDoc<Project> getModifiedProjects() {
514         return mutex().readAccess(new Mutex.Action<Set JavaDoc<Project>>() {
515             public Set JavaDoc<Project> run() {
516                 return new HashSet JavaDoc<Project>(modifiedProjects);
517             }
518         });
519     }
520     
521     /**
522      * Check whether a given project is current modified.
523      * <p>Acquires read access.
524      * @param p a project loaded by this manager
525      * @return true if it is modified, false if has been saved since the last modification
526      * @throws IllegalArgumentException if the project was not created through this manager
527      */

528     public boolean isModified(final Project p) throws IllegalArgumentException JavaDoc {
529         return mutex().readAccess(new Mutex.Action<Boolean JavaDoc>() {
530             public Boolean JavaDoc run() {
531                 synchronized (dir2Proj) {
532                     if (!proj2Factory.containsKey(p)) {
533                         throw new IllegalArgumentException JavaDoc("Project " + p + " not created by " + ProjectManager.this + " or was already deleted"); // NOI18N
534
}
535                 }
536                 return modifiedProjects.contains(p);
537             }
538         });
539     }
540     
541     /**
542      * Save one project (if it was in fact modified).
543      * <p>Acquires write access.</p>
544      * <p class="nonnormative">
545      * Although the project infrastructure permits a modified project to be saved
546      * at any time, current UI principles dictate that the "save project" concept
547      * should be internal only - i.e. a project customizer should automatically
548      * save the project when it is closed e.g. with an "OK" button. Currently there
549      * is no UI display of modified projects; this module does not ensure that modified projects
550      * are saved at system exit time the way modified files are, though the Project UI
551      * implementation module currently does this check.
552      * </p>
553      * @param p the project to save
554      * @throws IOException if it cannot be saved
555      * @throws IllegalArgumentException if the project was not created through this manager
556      */

557     public void saveProject(final Project p) throws IOException JavaDoc, IllegalArgumentException JavaDoc {
558         try {
559             mutex().writeAccess(new Mutex.ExceptionAction<Void JavaDoc>() {
560                 public Void JavaDoc run() throws IOException JavaDoc {
561                     //removed projects are the ones that cannot be mapped to an existing project type anymore.
562
if (removedProjects.contains(p)) {
563                         return null;
564                     }
565                     if (!proj2Factory.containsKey(p)) {
566                         throw new IllegalArgumentException JavaDoc("Project " + p + " not created by " + ProjectManager.this + " or was already deleted"); // NOI18N
567
}
568                     if (modifiedProjects.contains(p)) {
569                         ProjectFactory f = proj2Factory.get(p);
570                         f.saveProject(p);
571                         if (ERR.isLoggable(ERR_LVL)) {
572                             ERR.log(ERR_LVL, "saveProject(" + p.getProjectDirectory()+ ")");
573                         }
574                         modifiedProjects.remove(p);
575                     }
576                     return null;
577                 }
578             });
579         } catch (MutexException e) {
580             //##91398 have a more descriptive error message, in case of RO folders.
581
// the correct reporting still up to the specific project type.
582
if (!p.getProjectDirectory().canWrite()) {
583                 throw new IOException JavaDoc("Project folder is not writeable.");
584             }
585             throw (IOException JavaDoc)e.getException();
586         }
587     }
588     
589     /**
590      * Save all modified projects.
591      * <p>Acquires write access.
592      * @throws IOException if any of them cannot be saved
593      */

594     public void saveAllProjects() throws IOException JavaDoc {
595         try {
596             mutex().writeAccess(new Mutex.ExceptionAction<Void JavaDoc>() {
597                 public Void JavaDoc run() throws IOException JavaDoc {
598                     Iterator JavaDoc<Project> it = modifiedProjects.iterator();
599                     while (it.hasNext()) {
600                         Project p = it.next();
601                         ProjectFactory f = proj2Factory.get(p);
602                         assert f != null : p;
603                         f.saveProject(p);
604                         if (ERR.isLoggable(ERR_LVL)) {
605                             ERR.log(ERR_LVL, "saveProject(" + p.getProjectDirectory()+ ")");
606                         }
607                         it.remove();
608                     }
609                     return null;
610                 }
611             });
612         } catch (MutexException e) {
613             throw (IOException JavaDoc)e.getException();
614         }
615     }
616     
617     /**
618      * Checks whether a project is still valid.
619      * <p>Acquires read access.</p>
620      *
621      * @since 1.6
622      *
623      * @param p a project loaded by this manager
624      * @return true if the project is still valid, false if it has been deleted
625      */

626     public boolean isValid(final Project p) {
627         return mutex().readAccess(new Mutex.Action<Boolean JavaDoc>() {
628             public Boolean JavaDoc run() {
629                 synchronized (dir2Proj) {
630                     return proj2Factory.containsKey(p);
631                 }
632             }
633         });
634     }
635     
636     /**
637      * Removes cache entries for deleted projects.
638      */

639     private final class ProjectDeletionListener extends FileChangeAdapter {
640         
641         public ProjectDeletionListener() {}
642
643         public void fileDeleted(FileEvent fe) {
644             synchronized (dir2Proj) {
645                 dir2Proj.remove(fe.getFile());
646             }
647         }
648         
649     }
650     
651 }
652
Popular Tags