1 19 20 package org.netbeans.api.project; 21 22 import java.io.IOException ; 23 import java.lang.ref.Reference ; 24 import java.util.Arrays ; 25 import java.util.HashSet ; 26 import java.util.Iterator ; 27 import java.util.Map ; 28 import java.util.Set ; 29 import java.util.WeakHashMap ; 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 51 public final class ProjectManager { 52 53 59 61 private static final ErrorManager ERR = ErrorManager.getDefault().getInstance(ProjectManager.class.getName()); 62 private static final int ERR_LVL = Boolean.getBoolean(ProjectManager.class.getName() + ".LOG_WARN") ? ErrorManager.WARNING : ErrorManager.INFORMATIONAL; 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 82 public static ProjectManager getDefault() { 83 return DEFAULT; 84 } 85 86 private static final Mutex MUTEX = new Mutex(); 87 99 public static Mutex mutex() { 100 return MUTEX; 101 } 102 103 private static enum LoadStatus { 104 107 NO_SUCH_PROJECT, 108 111 SOME_SUCH_PROJECT, 112 116 LOADING_PROJECT; 117 118 public boolean is(Union2<Reference <Project>,LoadStatus> o) { 119 return o != null && o.hasSecond() && o.second() == this; 120 } 121 122 public Union2<Reference <Project>,LoadStatus> wrap() { 123 return Union2.createSecond(this); 124 } 125 } 126 127 131 private final Map <FileObject,Union2<Reference <Project>,LoadStatus>> dir2Proj = new WeakHashMap <FileObject,Union2<Reference <Project>,LoadStatus>>(); 132 133 136 private final Set <Project> modifiedProjects = new HashSet <Project>(); 137 138 private final Set <Project> removedProjects = new WeakSet<Project>(); 139 140 143 private final Map <Project,ProjectFactory> proj2Factory = new WeakHashMap <Project,ProjectFactory>(); 144 145 148 private final FileChangeListener projectDeletionListener = new ProjectDeletionListener(); 149 150 153 private Thread loadingThread = null; 154 155 159 void reset() { 160 dir2Proj.clear(); 161 modifiedProjects.clear(); 162 proj2Factory.clear(); 163 } 164 165 166 static boolean quiet = false; 167 168 192 public Project findProject(final FileObject projectDirectory) throws IOException , IllegalArgumentException { 193 if (projectDirectory == null) { 194 throw new IllegalArgumentException ("Attempted to pass a null directory to findProject"); } 196 if (!projectDirectory.isFolder()) { 197 throw new IllegalArgumentException ("Attempted to pass a non-directory to findProject: " + projectDirectory); } 199 try { 200 return mutex().readAccess(new Mutex.ExceptionAction<Project>() { 201 public Project run() throws IOException { 202 try { 205 boolean wasSomeSuchProject; 206 synchronized (dir2Proj) { 207 Union2<Reference <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 ("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)"); } 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 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 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 } 257 synchronized (dir2Proj) { 258 dir2Proj.notifyAll(); 259 projectDirectory.addFileChangeListener(projectDeletionListener); 260 if (p != null) { 261 dir2Proj.put(projectDirectory, Union2.<Reference <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 e) { 274 if (ERR.isLoggable(ERR_LVL)) { 275 ERR.log(ERR_LVL, "findProject(" + projectDirectory + ") in " + Thread.currentThread().getName() + ": error loading project: " + e); 276 } 277 throw e; 282 } finally { 283 loadingThread = null; 284 if (!resetLP) { 285 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(); } 294 } 295 } 296 } catch (Error e) { 309 ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); 310 throw e; 311 } catch (RuntimeException e) { 312 ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); 313 throw e; 314 } catch (IOException e) { 315 if (!quiet) ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); 316 throw e; 317 } 318 } 319 }); 320 } catch (MutexException e) { 321 throw (IOException )e.getException(); 322 } 323 } 324 325 331 private Project createProject(FileObject dir) throws IOException { 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 368 public boolean isProject(final FileObject projectDirectory) throws IllegalArgumentException { 369 if (projectDirectory == null) { 370 throw new IllegalArgumentException ("Attempted to pass a null directory to isProject"); } 372 if (!projectDirectory.isFolder() ) { 373 if (projectDirectory.isValid()) { 376 throw new IllegalArgumentException ("Attempted to pass a non-directory to isProject: " + projectDirectory); } else { 378 return false; 379 } 380 } 381 return mutex().readAccess(new Mutex.Action<Boolean >() { 382 public Boolean run() { 383 synchronized (dir2Proj) { 384 Union2<Reference <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 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 return true; 401 } 402 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 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 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 450 public void clearNonProjectCache() { 451 synchronized (dir2Proj) { 452 dir2Proj.values().removeAll(Arrays.asList(new Object [] { 453 LoadStatus.NO_SUCH_PROJECT.wrap(), 454 LoadStatus.SOME_SUCH_PROJECT.wrap(), 455 })); 456 } 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 >() { 479 public Void run() { 480 if (!proj2Factory.containsKey(p)) { 481 throw new IllegalStateException ("An attempt to call ProjectState.markModified on a deleted project: " + p.getProjectDirectory()); } 483 modifiedProjects.add(p); 484 return null; 485 } 486 }); 487 } 488 489 public void notifyDeleted() throws IllegalStateException { 490 assert p != null; 491 mutex().writeAccess(new Mutex.Action<Void >() { 492 public Void run() { 493 if (proj2Factory.get(p) == null) { 494 throw new IllegalStateException ("An attempt to call notifyDeleted more than once. Project: " + p.getProjectDirectory()); } 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 513 public Set <Project> getModifiedProjects() { 514 return mutex().readAccess(new Mutex.Action<Set <Project>>() { 515 public Set <Project> run() { 516 return new HashSet <Project>(modifiedProjects); 517 } 518 }); 519 } 520 521 528 public boolean isModified(final Project p) throws IllegalArgumentException { 529 return mutex().readAccess(new Mutex.Action<Boolean >() { 530 public Boolean run() { 531 synchronized (dir2Proj) { 532 if (!proj2Factory.containsKey(p)) { 533 throw new IllegalArgumentException ("Project " + p + " not created by " + ProjectManager.this + " or was already deleted"); } 535 } 536 return modifiedProjects.contains(p); 537 } 538 }); 539 } 540 541 557 public void saveProject(final Project p) throws IOException , IllegalArgumentException { 558 try { 559 mutex().writeAccess(new Mutex.ExceptionAction<Void >() { 560 public Void run() throws IOException { 561 if (removedProjects.contains(p)) { 563 return null; 564 } 565 if (!proj2Factory.containsKey(p)) { 566 throw new IllegalArgumentException ("Project " + p + " not created by " + ProjectManager.this + " or was already deleted"); } 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 if (!p.getProjectDirectory().canWrite()) { 583 throw new IOException ("Project folder is not writeable."); 584 } 585 throw (IOException )e.getException(); 586 } 587 } 588 589 594 public void saveAllProjects() throws IOException { 595 try { 596 mutex().writeAccess(new Mutex.ExceptionAction<Void >() { 597 public Void run() throws IOException { 598 Iterator <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 )e.getException(); 614 } 615 } 616 617 626 public boolean isValid(final Project p) { 627 return mutex().readAccess(new Mutex.Action<Boolean >() { 628 public Boolean run() { 629 synchronized (dir2Proj) { 630 return proj2Factory.containsKey(p); 631 } 632 } 633 }); 634 } 635 636 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 |