1 19 20 package org.netbeans.modules.javacore.classpath; 21 22 import java.beans.PropertyChangeEvent ; 23 import java.beans.PropertyChangeListener ; 24 import java.beans.PropertyChangeSupport ; 25 import java.lang.ref.Reference ; 26 import java.lang.ref.WeakReference ; 27 import java.net.URL ; 28 import java.util.ArrayList ; 29 import java.util.Arrays ; 30 import java.util.Collection ; 31 import java.util.Collections ; 32 import java.util.HashMap ; 33 import java.util.HashSet ; 34 import java.util.Iterator ; 35 import java.util.List ; 36 import java.util.Map ; 37 import java.util.Set ; 38 import java.util.WeakHashMap ; 39 import java.util.logging.Logger ; 40 import javax.swing.event.ChangeEvent ; 41 import javax.swing.event.ChangeListener ; 42 import org.netbeans.api.java.classpath.ClassPath; 43 import org.netbeans.api.java.classpath.GlobalPathRegistry; 44 import org.netbeans.api.java.classpath.GlobalPathRegistryEvent; 45 import org.netbeans.api.java.classpath.GlobalPathRegistryListener; 46 import org.netbeans.api.java.queries.SourceForBinaryQuery; 47 import org.netbeans.spi.java.classpath.ClassPathImplementation; 48 import org.netbeans.spi.java.classpath.PathResourceImplementation; 49 import org.netbeans.spi.java.classpath.support.ClassPathSupport; 50 import org.openide.ErrorManager; 51 import org.openide.filesystems.FileObject; 52 import org.openide.filesystems.FileStateInvalidException; 53 import org.openide.filesystems.URLMapper; 54 import org.openide.util.Utilities; 55 import org.openide.util.WeakListeners; 56 57 public class MergedClassPathImplementation implements ClassPathImplementation { 58 59 public static final String PROP_UNRESOLVED_ROOTS = "unresolvedRoots"; 61 private long eventCounter; 62 private Set roots; 63 private PropertyChangeSupport support; 64 private GlobalPathRegistry reg; 65 private final ArrayList cachedResources; 66 private List unresolvedRoots; 67 private List missingRoots; 68 private ClassPathMap resourceMap; 69 private GlobalPathRegistryListener gprListener; 70 private PropertyChangeListener pcListener = new PropertyChangeListener () { 71 public void propertyChange(PropertyChangeEvent event) { 72 assert event != null : "event == null"; long id; 74 synchronized (MergedClassPathImplementation.this) { 75 roots = null; 76 id = (++eventCounter); 77 } 78 if (ClassPath.PROP_ENTRIES.equals(event.getPropertyName())) { 79 MergedClassPathImplementation.this.updateEntries((ClassPath)event.getSource(),id); 80 } 81 } 82 }; 83 84 private WeakHashMap sfbResultListeners = new WeakHashMap (100); 85 86 private static MergedClassPathImplementation instance; 87 88 private MergedClassPathImplementation () { 89 this.support = new PropertyChangeSupport (this); 90 this.cachedResources = new ArrayList (); 91 this.missingRoots = new ArrayList (); 92 this.reg = GlobalPathRegistry.getDefault(); 93 this.gprListener = new GlobalPathRegistryListener() { 94 public void pathsAdded(GlobalPathRegistryEvent event) { 95 assert event != null : "event == null"; synchronized (MergedClassPathImplementation.this) { 97 roots = null; 98 } 99 MergedClassPathImplementation.this.updateEntries(event); 100 MergedClassPathImplementation.this.firePropertyChange (PROP_UNRESOLVED_ROOTS); 101 } 102 103 public void pathsRemoved(GlobalPathRegistryEvent event) { 104 } 105 }; 106 this.reg.addGlobalPathRegistryListener ((GlobalPathRegistryListener) 107 WeakListeners.create(GlobalPathRegistryListener.class,this.gprListener,this.reg)); 108 assert this.reg != null : "GloabalPathRegistry.getDefault()==null"; } 111 112 113 public synchronized List getResources() { 114 return Collections.unmodifiableList((List ) this.cachedResources.clone()); 115 } 116 117 public void addPropertyChangeListener(PropertyChangeListener listener) { 118 assert listener != null : "gprListener == null"; this.support.addPropertyChangeListener(listener); 120 } 121 122 public void removePropertyChangeListener(PropertyChangeListener listener) { 123 assert listener != null : "gprListener == null"; this.support.removePropertyChangeListener(listener); 125 } 126 127 128 public void addClassPaths (ClassPath[] cps) { 129 assert cps != null : "addClassPath called with null"; for (int i = 0; i < cps.length; i++) { 131 if (cps[i]!=null) { 132 this.addClassPath(cps[i]); 133 } 134 } 135 synchronized (this) { 136 roots = null; 137 } 138 this.firePropertyChange(PROP_UNRESOLVED_ROOTS); 139 } 140 141 public void addRoot(URL url) { 142 synchronized (this) { 143 if (this.unresolvedRoots == null) { 144 this.initEntries(); 145 } 146 PathResourceImplementation root = ClassPathSupport.createResource(url); 147 unresolvedRoots.add(root); 148 roots = null; 149 } 150 this.firePropertyChange(PROP_UNRESOLVED_ROOTS); 151 } 152 153 public synchronized PathResourceImplementation[] getUnresolvedRoots () { 154 if (this.unresolvedRoots == null) { 155 this.initEntries(); 156 } 157 return (PathResourceImplementation[]) this.unresolvedRoots.toArray (new PathResourceImplementation[this.unresolvedRoots.size()]); 158 } 159 160 public void classPathRootResolved (PathResourceImplementation impl) { 161 synchronized (this) { 162 if (this.unresolvedRoots.remove(impl)) { 163 this.cachedResources.add(impl); 164 } 165 roots = null; 166 } 167 this.firePropertyChange (PROP_RESOURCES); 168 } 169 170 public synchronized void removeRoot(PathResourceImplementation impl) { 171 this.unresolvedRoots.remove(impl); 172 this.missingRoots.add(impl.getRoots()[0]); 173 roots = null; 174 } 175 176 public boolean removeMissingRoot(URL url) { 177 boolean result = false; 178 synchronized (this) { 179 while (missingRoots.remove(url)) { 180 PathResourceImplementation resource = ClassPathSupport.createResource(url); 181 assert resource != null : "ClassPathSupport.createResource() returned null"; unresolvedRoots.add(resource); 183 result = true; 184 roots = null; 185 } 186 } 187 if (result) { 188 firePropertyChange(PROP_UNRESOLVED_ROOTS); 189 } 190 return result; 191 } 192 193 public boolean addMissingRoot(URL url) { 194 boolean result = false; 195 synchronized (this) { 196 PathResourceImplementation resource = ClassPathSupport.createResource(url); 197 while (this.cachedResources.remove(resource)) { 198 missingRoots.add(url); 199 result = true; 200 roots = null; 201 } 202 } 203 if (result) { 204 this.firePropertyChange(PROP_RESOURCES); 205 } 206 return result; 207 } 208 209 public boolean updateRoot(URL url) { 210 boolean result = false; 211 synchronized (this) { 212 PathResourceImplementation resource = ClassPathSupport.createResource(url); 213 while (this.cachedResources.remove(resource)) { 214 unresolvedRoots.add(resource); 215 result = true; 216 roots = null; 217 } 218 } 219 if (result) { 220 firePropertyChange(PROP_RESOURCES); 221 firePropertyChange(PROP_UNRESOLVED_ROOTS); 222 } 223 return result; 224 } 225 226 public void addUnresolvedRoots (List unresolvedRoots) { 227 synchronized (this) { 228 this.unresolvedRoots.addAll (unresolvedRoots); 229 roots = null; 230 } 231 this.firePropertyChange(PROP_UNRESOLVED_ROOTS); 232 } 233 234 239 public void classPathRootResolved (URL url) { 240 synchronized (this) { 241 for (Iterator it = unresolvedRoots.iterator(); it.hasNext();) { 242 PathResourceImplementation resource = (PathResourceImplementation) it.next (); 243 if (resource.getRoots()[0].equals(url)) { 244 it.remove(); 245 this.cachedResources.add(resource); 246 break; 247 } 248 } 249 roots = null; 250 } 251 this.firePropertyChange (PROP_RESOURCES); 252 } 253 254 private synchronized void initEntries () { 255 Set classPaths = new HashSet (); 256 roots = null; 257 this.unresolvedRoots = new ArrayList (); 258 this.resourceMap = new ClassPathMap(); 259 classPaths.addAll(this.reg.getPaths(ClassPath.SOURCE)); 260 classPaths.addAll(this.reg.getPaths(ClassPath.COMPILE)); 261 classPaths.addAll(this.reg.getPaths(ClassPath.BOOT)); 262 for(Iterator it = classPaths.iterator();it.hasNext();) { 263 ClassPath cp = (ClassPath) it.next (); 264 this.addClassPath (cp); 265 } 266 } 267 268 private synchronized void updateEntries (GlobalPathRegistryEvent event) { 269 if (this.cachedResources == null) 270 return; 271 for (Iterator it = event.getChangedPaths().iterator(); it.hasNext();) { 272 ClassPath cp = (ClassPath) it.next(); 273 this.addClassPath (cp); 274 } 275 } 276 277 private void updateEntries (final ClassPath cp, final long eid) { 278 279 boolean fire = false; 280 List oldResources; 281 Logger.getLogger("TEST-"+MergedClassPathImplementation.class.getName()).finest("testRaceCondition84603"); List newResources = addClassPathResources (cp); 283 synchronized (this) { 284 if (eid == eventCounter) { 285 oldResources = (List ) this.resourceMap.remove (cp); 286 assert oldResources != null : "Change in unknown classpath"; Collection toRemove = new HashSet (oldResources); 288 toRemove.removeAll (newResources); 289 newResources.removeAll (oldResources); for (Iterator it = toRemove.iterator(); it.hasNext();) { 291 PathResourceImplementation resource = (PathResourceImplementation) it.next(); 292 oldResources.remove(resource); 293 if (!this.cachedResources.remove(resource)) { 294 if (!this.unresolvedRoots.remove (resource)) { 295 missingRoots.remove(resource.getRoots()[0]); 296 } 297 } 298 else { 299 fire = true; 300 } 301 } 302 for (Iterator it = newResources.iterator(); it.hasNext();) { 303 PathResourceImplementation resource = (PathResourceImplementation) it.next(); 304 oldResources.add (resource); 305 } 306 this.resourceMap.put (cp,oldResources); 307 this.unresolvedRoots.addAll(newResources); 308 } 309 } 310 if (fire) { 311 this.firePropertyChange(PROP_RESOURCES); 312 } 313 this.firePropertyChange(PROP_UNRESOLVED_ROOTS); 314 } 315 316 private void addClassPath (ClassPath cp) { 317 synchronized (this) { 318 if (this.resourceMap == null) { 319 initEntries(); 320 } 321 if (this.resourceMap.containsKey(cp)) { 322 return; 323 } 324 } 325 List c = addClassPathResources(cp); 326 synchronized (this) { 327 if (!this.resourceMap.containsKey(cp)) { 328 this.resourceMap.put(cp,c); 329 this.unresolvedRoots.addAll(c); 330 cp.addPropertyChangeListener((PropertyChangeListener )WeakListeners.create(PropertyChangeListener .class,this.pcListener,cp)); 331 } 332 } 333 } 334 335 private void firePropertyChange (String propName) { 336 this.support.firePropertyChange(propName,null,null); 337 } 338 339 private class SFBQListener implements ChangeListener , PropertyChangeListener { 340 private final WeakReference cp; 341 private Map results; 342 343 public SFBQListener(ClassPath cp) { 344 this.cp = new WeakReference (cp); 345 cp.addPropertyChangeListener(this); 349 } 350 351 public void propertyChange(PropertyChangeEvent event) { 352 } 354 355 public SourceForBinaryQuery.Result getResult(URL url) { 356 if (results == null) { 357 results = new HashMap (); 358 } 359 SourceForBinaryQuery.Result result; 360 Reference ref = (Reference ) results.get(url); 361 if (ref == null || (result = (SourceForBinaryQuery.Result) ref.get ()) == null) { 362 result = SourceForBinaryQuery.findSourceRoots(url); 363 results.put(url, new WeakReference (result)); 364 result.addChangeListener(WeakListeners.change(this, result)); 365 } 366 return result; 367 } 368 369 public void stateChanged(ChangeEvent e) { 370 ClassPath classPath = (ClassPath) cp.get(); 372 if (classPath != null) { 373 long id; 374 synchronized (MergedClassPathImplementation.this) { 375 id = (++eventCounter); 376 } 377 updateEntries(classPath,id); 378 } 379 } 380 } 381 382 private List addClassPathResources (final ClassPath cp) { 383 List entries = null; 384 SFBQListener listener = null; 385 synchronized (this) { 386 WeakReference wr = (WeakReference ) sfbResultListeners.get(cp); 387 listener = (SFBQListener) (wr == null ? null : wr.get()); 388 if (listener == null) { 389 listener = new SFBQListener(cp); 390 sfbResultListeners.put(cp, new WeakReference (listener)); 391 } 392 } 393 entries = cp.entries(); 394 List c = new ArrayList (); 395 for (Iterator et = entries.iterator(); et.hasNext();) { 396 ClassPath.Entry entry = (ClassPath.Entry)et.next(); 397 URL url = entry.getURL(); 398 assert url != null : "ClassPath.Entry.getURL() returned null"; addResources(url, listener, c); 400 } 401 return c; 402 } 403 404 private static void addResources (URL url, SFBQListener sfbResultListener, List resourceList) { 405 PathResourceImplementation resource = ClassPathSupport.createResource(url); 406 assert resource != null : "ClassPathSupport.createResource() returned null"; boolean needsBinary = true; 408 SourceForBinaryQuery.Result result = sfbResultListener.getResult(url); 409 FileObject[] sources = result.getRoots(); 410 List sourcesL = Arrays.asList(sources); 411 if (sourcesL.contains(null)) { 412 ErrorManager.getDefault().log(ErrorManager.WARNING, "Warning: " + result.getClass().getName() + " illegally returned a null element from getResult(URL): " + sourcesL); 414 return; 415 } 416 List tempResult = new ArrayList (sources.length); 417 for (int i=0; i< sources.length; i++) { 418 try { 419 URL surl = sources[i].getURL(); 420 if ("file".equals(surl.getProtocol())) needsBinary = false; PathResourceImplementation sresource = ClassPathSupport.createResource(surl); 422 assert sresource != null : "ClassPathSupport.createResource() returned null"; tempResult.add(sresource); 424 } catch (FileStateInvalidException e) { 425 ErrorManager.getDefault().notify(e); 426 } 427 } 428 if (needsBinary) resourceList.add(resource); 429 resourceList.addAll(tempResult); 430 } 431 432 433 public synchronized static MergedClassPathImplementation getDefault () { 434 if (instance == null ) { 435 instance = new MergedClassPathImplementation(); 436 } 437 return instance; 438 } 439 440 441 private class ClassPathMap { 442 443 private List data; 444 445 public ClassPathMap () { 446 this.data = new ArrayList (); 447 } 448 449 public void put (Object key, Object value ) { 450 synchronized (MergedClassPathImplementation.this) { 451 WeakPair wp = new WeakPair (key, value); 452 data.add (wp); 453 } 454 } 455 456 public Object remove (Object key) { 457 if (key == null) { 458 return null; 459 } 460 synchronized (MergedClassPathImplementation.this) { 461 for (Iterator it = this.data.iterator(); it.hasNext();) { 462 WeakPair pair = (WeakPair) it.next (); 463 Object wpk = pair.getKey(); 464 if (key.equals(wpk)) { 465 it.remove(); 466 return pair.getValue(); 467 } 468 } 469 } 470 return null; 471 } 472 473 public boolean containsKey (Object key) { 474 if (key == null) { 475 return false; 476 } 477 Iterator it; 478 synchronized (MergedClassPathImplementation.this) { 479 it = new ArrayList (data).iterator(); 480 } 481 while (it.hasNext()) { 482 WeakPair pair = (WeakPair) it.next(); 483 Object pk = pair.getKey(); 484 if (key.equals(pk)) { 485 return true; 486 } 487 } 488 return false; 489 } 490 491 private void cleanUp (WeakPair toClean) { 492 boolean fire = false; 493 synchronized (MergedClassPathImplementation.this) { 494 for (Iterator it = this.data.iterator(); it.hasNext();) { 495 WeakPair pair = (WeakPair) it.next (); 496 if (pair == toClean) { 497 it.remove(); 498 for (Iterator resIt= ((Collection )pair.getValue()).iterator(); resIt.hasNext();) { 499 PathResourceImplementation resource = (PathResourceImplementation) resIt.next(); 500 if (!MergedClassPathImplementation.this.cachedResources.remove(resource)) { 501 if (!MergedClassPathImplementation.this.unresolvedRoots.remove(resource)) { 502 missingRoots.remove(resource.getRoots()[0]); 503 } 504 } else { 505 fire = true; 506 roots = null; 507 } 508 } 509 break; 510 } 511 } 512 } 513 if (fire) { 514 firePropertyChange(PROP_RESOURCES); 515 } 516 } 517 518 private class WeakPair extends WeakReference implements Runnable { 519 520 private Object value; 521 522 public WeakPair (Object key, Object value) { 523 super (key, Utilities.activeReferenceQueue()); 524 this.value = value; 525 } 526 527 public Object getKey () { 528 return get (); 529 } 530 531 public Object getValue () { 532 return value; 533 } 534 535 public void run() { 536 cleanUp(this); 537 } 538 } 539 540 } 541 542 public Set getRoots() { 543 List _cachedResources; 544 synchronized (this) { 545 if (this.roots != null) { 546 return this.roots; 547 } 548 _cachedResources = this.getResources(); } 550 Set _roots = new HashSet (); 551 for (Iterator it = _cachedResources.iterator(); it.hasNext();) { 552 PathResourceImplementation res = (PathResourceImplementation) it.next(); 553 URL [] urls = res.getRoots(); 554 for (int i = 0; i < urls.length; i++) { 555 FileObject obj = URLMapper.findFileObject(urls[i]); 556 if (obj != null) { 557 _roots.add(obj); 558 } 559 } 560 } 561 synchronized (this) { 562 if (this.roots == null) { 563 this.roots = Collections.unmodifiableSet(_roots); 564 } 565 return roots; 566 } 567 568 } 569 570 public boolean contains(FileObject fo) { 571 return findOwnerRoot(fo) != null; 572 } 573 574 public FileObject findOwnerRoot(FileObject fo) { 575 Set roots = getRoots(); 576 for (FileObject f = fo; f != null; f = f.getParent()) { 577 if (roots.contains(f)) { 578 return f; 579 } 580 } 581 return null; 582 } 583 } 584 | Popular Tags |