1 19 20 package org.netbeans.modules.java.source.classpath; 21 22 import java.beans.PropertyChangeEvent ; 23 import java.beans.PropertyChangeListener ; 24 import java.beans.PropertyChangeListener ; 25 import java.beans.PropertyChangeListener ; 26 import java.lang.ref.WeakReference ; 27 import java.net.URL ; 28 import java.util.ArrayList ; 29 import java.util.ArrayList ; 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.TooManyListenersException ; 39 import java.util.concurrent.CopyOnWriteArrayList ; 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.util.Utilities; 54 import org.openide.util.WeakListeners; 55 56 60 public class GlobalSourcePath { 61 62 public static final String PROP_INCLUDES = ClassPath.PROP_INCLUDES; 63 64 private static GlobalSourcePath instance; 65 66 private final GlobalPathRegistry gpr; 67 private List <? extends PathResourceImplementation> resources; 68 private List <? extends PathResourceImplementation> unknownResources; 69 private List <? extends PathResourceImplementation> binaryResources; 70 private Set <ClassPath> activeCps; 71 private Map <URL , SourceForBinaryQuery.Result> sourceResults; 72 private Map <URL , URL []> translatedRoots; 73 private Map <URL , WeakValue> unknownRoots; 74 private long timeStamp; private Runnable debugCallBack; 76 77 private final SourcePathImplementation sourcePath; 78 private final BinaryPathImplementation binaryPath; 79 private final UnknownSourcePathImplementation unknownSourcePath; 80 81 private final Listener listener; 82 83 private PropertyChangeListener excludesListener; 84 85 86 private GlobalSourcePath() { 87 this.listener = new Listener (); 88 this.sourcePath = new SourcePathImplementation (); 89 this.binaryPath = new BinaryPathImplementation (); 90 this.unknownSourcePath = new UnknownSourcePathImplementation (); 91 this.timeStamp = -1; 92 this.gpr = GlobalPathRegistry.getDefault(); 93 this.activeCps = Collections.emptySet(); 94 this.sourceResults = Collections.emptyMap(); 95 this.unknownRoots = new HashMap <URL , WeakValue>(); 96 this.translatedRoots = new HashMap <URL , URL []> (); 97 this.gpr.addGlobalPathRegistryListener ((GlobalPathRegistryListener)WeakListeners.create(GlobalPathRegistryListener.class,this.listener,this.gpr)); 98 } 99 100 101 public void setExcludesListener (final PropertyChangeListener listener) throws TooManyListenersException { 102 if (this.excludesListener != null) { 103 throw new TooManyListenersException (); 104 } 105 this.excludesListener=listener; 106 } 107 108 public URL [] getSourceRootForBinaryRoot (final URL binaryRoot, final ClassPath definingClassPath, final boolean fire) { 109 URL [] result = this.translatedRoots.get(binaryRoot); 110 if (result != null) { 111 if (result.length > 0) { 112 return result; 113 } 114 else { 115 return null; 116 } 117 } 118 else { 119 List <URL > cacheRoots = new ArrayList <URL > (); 120 Collection <? extends PathResourceImplementation> unknownRes = getSources(SourceForBinaryQuery.findSourceRoots(binaryRoot).getRoots(),cacheRoots,null); 121 if (unknownRes.isEmpty()) { 122 return null; 123 } 124 else { 125 result = new URL [cacheRoots.size()]; 126 synchronized (this) { 127 Iterator <URL > it = cacheRoots.iterator(); 128 for (int i=0; it.hasNext(); i++) { 129 result[i] = it.next (); 130 unknownRoots.put(result[i],new WeakValue(definingClassPath,result[i])); 131 } 132 } 133 if (fire) { 134 this.resetCacheAndFire(); 135 } 136 return result; 137 } 138 } 139 } 140 141 public ClassPathImplementation getSourcePath () { 142 return this.sourcePath; 143 } 144 145 public ClassPathImplementation getUnknownSourcePath () { 146 return this.unknownSourcePath; 147 } 148 149 public ClassPathImplementation getBinaryPath () { 150 return this.binaryPath; 151 } 152 153 private void resetCacheAndFire () { 154 synchronized (this) { 155 this.resources = null; 156 this.binaryResources = null; 157 this.unknownResources = null; 158 this.timeStamp++; 159 } 160 this.sourcePath.firePropertyChange (); 161 this.binaryPath.firePropertyChange (); 162 this.unknownSourcePath.firePropertyChange(); 163 } 164 165 private static Result createResources (final Request r) { 166 assert r != null; 167 Set <PathResourceImplementation> result = new HashSet <PathResourceImplementation> (); 168 Set <PathResourceImplementation> unknownResult = new HashSet <PathResourceImplementation> (); 169 Set <PathResourceImplementation> binaryResult = new HashSet <PathResourceImplementation> (); 170 final Map <URL ,URL []> translatedRoots = new HashMap <URL , URL []>(); 171 Set <ClassPath> newCps = new HashSet <ClassPath> (); 172 for (ClassPath cp : r.sourceCps) { 173 boolean isNew = !r.oldCps.remove(cp); 174 for (ClassPath.Entry entry : cp.entries()) { 175 result.add(ClassPathSupport.createResource(entry.getURL())); 176 } 177 boolean notContained = newCps.add (cp); 178 if (isNew && notContained) { 179 cp.addPropertyChangeListener(r.propertyListener); 180 } 181 } 182 Map <URL ,SourceForBinaryQuery.Result> newSR = new HashMap <URL ,SourceForBinaryQuery.Result> (); 183 for (ClassPath cp : r.bootCps) { 184 boolean isNew = !r.oldCps.remove(cp); 185 for (ClassPath.Entry entry : cp.entries()) { 186 URL url = entry.getURL(); 187 if (!translatedRoots.containsKey(url)) { 188 SourceForBinaryQuery.Result sr = r.oldSR.remove (url); 189 boolean isNewSR; 190 if (sr == null) { 191 sr = SourceForBinaryQuery.findSourceRoots(url); 192 isNewSR = true; 193 } 194 else { 195 isNewSR = false; 196 } 197 assert !newSR.containsKey(url); 198 newSR.put(url,sr); 199 List <URL > cacheURLs = new ArrayList <URL > (); 200 Collection <? extends PathResourceImplementation> srcRoots = getSources (sr.getRoots(), cacheURLs, r.unknownRoots); 201 if (srcRoots.isEmpty()) { 202 binaryResult.add (ClassPathSupport.createResource(url)); 203 } 204 else { 205 result.addAll(srcRoots); 206 } 207 translatedRoots.put(url, cacheURLs.toArray(new URL [cacheURLs.size()])); 208 if (isNewSR) { 209 sr.addChangeListener(r.changeListener); 210 } 211 } 212 } 213 boolean notContained = newCps.add (cp); 214 if (isNew && notContained) { 215 cp.addPropertyChangeListener(r.propertyListener); 216 } 217 } 218 219 for (ClassPath cp : r.compileCps) { 220 boolean isNew = !r.oldCps.remove(cp); 221 for (ClassPath.Entry entry : cp.entries()) { 222 URL url = entry.getURL(); 223 if (!translatedRoots.containsKey(url)) { 224 SourceForBinaryQuery.Result sr = r.oldSR.remove (url); 225 boolean isNewSR; 226 if (sr == null) { 227 sr = SourceForBinaryQuery.findSourceRoots(url); 228 isNewSR = true; 229 } 230 else { 231 isNewSR = false; 232 } 233 assert !newSR.containsKey(url); 234 newSR.put(url,sr); 235 List <URL > cacheURLs = new ArrayList <URL > (); 236 Collection <? extends PathResourceImplementation> srcRoots = getSources(sr.getRoots(),cacheURLs, r.unknownRoots); 237 if (srcRoots.isEmpty()) { 238 binaryResult.add(ClassPathSupport.createResource(url)); 239 } 240 else { 241 result.addAll(srcRoots); 242 } 243 translatedRoots.put(url, cacheURLs.toArray(new URL [cacheURLs.size()])); 244 if (isNewSR) { 245 sr.addChangeListener(r.changeListener); 246 } 247 } 248 } 249 boolean notContained = newCps.add (cp); 250 if (isNew && notContained) { 251 cp.addPropertyChangeListener(r.propertyListener); 252 } 253 } 254 255 for (ClassPath cp : r.oldCps) { 256 cp.removePropertyChangeListener(r.propertyListener); 257 } 258 259 for (Map.Entry <URL ,SourceForBinaryQuery.Result> entry : r.oldSR.entrySet()) { 260 entry.getValue().removeChangeListener(r.changeListener); 261 } 262 for (URL unknownRoot : r.unknownRoots.keySet()) { 263 unknownResult.add (ClassPathSupport.createResource(unknownRoot)); 264 } 265 return new Result (r.timeStamp, new ArrayList <PathResourceImplementation> (result), new ArrayList (binaryResult), new ArrayList <PathResourceImplementation>(unknownResult), 266 newCps,newSR,translatedRoots, r.unknownRoots); 267 } 268 269 277 void setDebugCallBack (final Runnable callBack) { 278 this.debugCallBack = callBack; 279 } 280 281 private static Collection <? extends PathResourceImplementation> getSources (final FileObject[] roots, final List <URL > cacheDirs, final Map <URL , WeakValue> unknownRoots) { 282 assert roots != null; 283 URL [] urls = new URL [roots.length]; 284 boolean add = true; 285 for (int i=0; i<roots.length; i++) { 286 try { 287 URL url = roots[i].getURL(); 288 if (!"file".equals(url.getProtocol())) { add = false; 290 break; 291 } 292 urls[i] = url; 293 } catch (FileStateInvalidException e) { 294 ErrorManager.getDefault().notify(e); 295 } 296 } 297 if (add) { 298 List <PathResourceImplementation> result = new ArrayList <PathResourceImplementation> (roots.length); 299 for (int i=0; i<urls.length; i++) { 300 if (cacheDirs != null) { 301 cacheDirs.add (urls[i]); 302 } 303 if (unknownRoots != null) { 304 unknownRoots.remove (urls[i]); 305 } 306 result.add(ClassPathSupport.createResource(urls[i])); 307 } 308 return result; 309 } 310 return Collections.<PathResourceImplementation>emptySet(); 311 } 312 313 private class WeakValue extends WeakReference <ClassPath> implements Runnable { 314 315 private URL key; 316 317 public WeakValue (ClassPath ref, URL key) { 318 super (ref, Utilities.activeReferenceQueue()); 319 assert key != null; 320 this.key = key; 321 } 322 323 public void run () { 324 boolean fire = false; 325 synchronized (GlobalSourcePath.this) { 326 fire = (GlobalSourcePath.this.unknownRoots.remove (key) != null); 327 } 328 if (fire) { 329 GlobalSourcePath.this.resetCacheAndFire(); 330 } 331 } 332 } 333 334 private long getTimeStamp () { 335 return this.timeStamp; 336 } 337 338 private static class Request { 339 340 final long timeStamp; 341 final Set <ClassPath> sourceCps; 342 final Set <ClassPath> bootCps; 343 final Set <ClassPath> compileCps; 344 final Set <ClassPath> oldCps; 345 final Map <URL , SourceForBinaryQuery.Result> oldSR; 346 final Map <URL , WeakValue> unknownRoots; 347 final PropertyChangeListener propertyListener; 348 final ChangeListener changeListener; 349 350 public Request (final long timeStamp, final Set <ClassPath> sourceCps, final Set <ClassPath> bootCps, final Set <ClassPath> compileCps, 351 final Set <ClassPath> oldCps, final Map <URL , SourceForBinaryQuery.Result> oldSR, final Map <URL , WeakValue> unknownRoots, 352 final PropertyChangeListener propertyListener, final ChangeListener changeListener) { 353 assert sourceCps != null; 354 assert bootCps != null; 355 assert compileCps != null; 356 assert oldCps != null; 357 assert oldSR != null; 358 assert unknownRoots != null; 359 assert propertyListener != null; 360 assert changeListener != null; 361 362 this.timeStamp = timeStamp; 363 this.sourceCps = sourceCps; 364 this.bootCps = bootCps; 365 this.compileCps = compileCps; 366 this.oldCps = oldCps; 367 this.oldSR = oldSR; 368 this.unknownRoots = unknownRoots; 369 this.propertyListener = propertyListener; 370 this.changeListener = changeListener; 371 } 372 } 373 374 private static class Result { 375 376 final long timeStamp; 377 final List <? extends PathResourceImplementation> resources; 378 final List <? extends PathResourceImplementation> binaryResources; 379 final List <? extends PathResourceImplementation> unknownResources; 380 final Set <ClassPath> newCps; 381 final Map <URL , SourceForBinaryQuery.Result> newSR; 382 final Map <URL , URL []> translatedRoots; 383 final Map <URL , WeakValue> unknownRoots; 384 385 public Result (final long timeStamp, final List <? extends PathResourceImplementation> resources, 386 final List <? extends PathResourceImplementation> binaryResources, 387 final List <? extends PathResourceImplementation> unknownResources, 388 final Set <ClassPath> newCps, 389 final Map <URL , SourceForBinaryQuery.Result> newSR, final Map <URL , URL []> translatedRoots, 390 final Map <URL , WeakValue> unknownRoots) { 391 assert resources != null; 392 assert binaryResources != null; 393 assert unknownResources != null; 394 assert newCps != null; 395 assert newSR != null; 396 assert translatedRoots != null; 397 this.timeStamp = timeStamp; 398 this.resources = resources; 399 this.binaryResources = binaryResources; 400 this.unknownResources = unknownResources; 401 this.newCps = newCps; 402 this.newSR = newSR; 403 this.translatedRoots = translatedRoots; 404 this.unknownRoots = unknownRoots; 405 } 406 } 407 408 private class SourcePathImplementation implements ClassPathImplementation { 409 410 private List <PropertyChangeListener > listeners = new ArrayList <PropertyChangeListener >(); 411 412 413 public List <? extends PathResourceImplementation> getResources() { 414 Request request; 415 synchronized (GlobalSourcePath.this) { 416 if (GlobalSourcePath.this.resources != null) { 417 return GlobalSourcePath.this.resources; 418 } 419 request = new Request ( 420 GlobalSourcePath.this.getTimeStamp(), 421 GlobalSourcePath.this.gpr.getPaths(ClassPath.SOURCE), 422 GlobalSourcePath.this.gpr.getPaths(ClassPath.BOOT), 423 GlobalSourcePath.this.gpr.getPaths(ClassPath.COMPILE), 424 new HashSet (GlobalSourcePath.this.activeCps), 425 new HashMap (GlobalSourcePath.this.sourceResults), 426 new HashMap <URL ,WeakValue> (GlobalSourcePath.this.unknownRoots), 427 GlobalSourcePath.this.listener, 428 GlobalSourcePath.this.listener); 429 } 430 Result res = createResources (request); 431 if (GlobalSourcePath.this.debugCallBack != null) { 432 GlobalSourcePath.this.debugCallBack.run(); 433 } 434 synchronized (this) { 435 if (GlobalSourcePath.this.getTimeStamp() == res.timeStamp) { 436 if (GlobalSourcePath.this.resources == null) { 437 GlobalSourcePath.this.resources = res.resources; 438 GlobalSourcePath.this.binaryResources = res.binaryResources; 439 GlobalSourcePath.this.unknownResources = res.unknownResources; 440 GlobalSourcePath.this.activeCps = res.newCps; 441 GlobalSourcePath.this.sourceResults = res.newSR; 442 GlobalSourcePath.this.translatedRoots = res.translatedRoots; 443 GlobalSourcePath.this.unknownRoots = res.unknownRoots; 444 } 445 return GlobalSourcePath.this.resources; 446 } 447 else { 448 return res.resources; 449 } 450 } 451 } 452 453 public synchronized void addPropertyChangeListener(PropertyChangeListener listener) { 454 assert listener != null; 455 if (this.listeners == null) { 456 this.listeners = new ArrayList <PropertyChangeListener > (); 457 } 458 this.listeners.add (listener); 459 } 460 461 public void removePropertyChangeListener(PropertyChangeListener listener) { 462 assert listener != null; 463 if (this.listeners == null) { 464 return; 465 } 466 this.listeners.remove(listener); 467 } 468 469 void firePropertyChange () { 470 PropertyChangeListener [] _listeners; 471 synchronized (this) { 472 if (this.listeners == null) { 473 return; 474 } 475 _listeners = this.listeners.toArray(new PropertyChangeListener [this.listeners.size()]); 476 } 477 PropertyChangeEvent event = new PropertyChangeEvent (this,PROP_RESOURCES,null,null); 478 for (PropertyChangeListener l : _listeners) { 479 l.propertyChange (event); 480 } 481 } 482 } 483 484 private class UnknownSourcePathImplementation implements ClassPathImplementation { 485 486 private List <PropertyChangeListener > listeners = new CopyOnWriteArrayList <PropertyChangeListener > (); 487 488 public List <? extends PathResourceImplementation> getResources() { 489 Request request; 490 synchronized (GlobalSourcePath.this) { 491 if (GlobalSourcePath.this.unknownResources != null) { 492 return GlobalSourcePath.this.unknownResources; 493 } 494 request = new Request ( 495 GlobalSourcePath.this.getTimeStamp(), 496 GlobalSourcePath.this.gpr.getPaths(ClassPath.SOURCE), 497 GlobalSourcePath.this.gpr.getPaths(ClassPath.BOOT), 498 GlobalSourcePath.this.gpr.getPaths(ClassPath.COMPILE), 499 new HashSet (GlobalSourcePath.this.activeCps), 500 new HashMap (GlobalSourcePath.this.sourceResults), 501 new HashMap <URL , WeakValue> (GlobalSourcePath.this.unknownRoots), 502 GlobalSourcePath.this.listener, 503 GlobalSourcePath.this.listener); 504 } 505 Result res = createResources (request); 506 if (GlobalSourcePath.this.debugCallBack != null) { 507 GlobalSourcePath.this.debugCallBack.run(); 508 } 509 synchronized (this) { 510 if (GlobalSourcePath.this.getTimeStamp() == res.timeStamp) { 511 if (GlobalSourcePath.this.binaryResources == null) { 512 GlobalSourcePath.this.resources = res.resources; 513 GlobalSourcePath.this.binaryResources = res.binaryResources; 514 GlobalSourcePath.this.unknownResources = res.unknownResources; 515 GlobalSourcePath.this.activeCps = res.newCps; 516 GlobalSourcePath.this.sourceResults = res.newSR; 517 GlobalSourcePath.this.translatedRoots = res.translatedRoots; 518 GlobalSourcePath.this.unknownRoots = res.unknownRoots; 519 } 520 return GlobalSourcePath.this.unknownResources; 521 } 522 else { 523 return res.unknownResources; 524 } 525 } 526 } 527 528 public void addPropertyChangeListener(PropertyChangeListener listener) { 529 assert listener != null; 530 this.listeners.add (listener); 531 } 532 533 public void removePropertyChangeListener(PropertyChangeListener listener) { 534 assert listener != null; 535 this.listeners.remove (listener); 536 } 537 538 539 void firePropertyChange () { 540 PropertyChangeEvent event = new PropertyChangeEvent (this,PROP_RESOURCES,null,null); 541 for (PropertyChangeListener l : this.listeners) { 542 l.propertyChange (event); 543 } 544 } 545 } 546 547 private class BinaryPathImplementation implements ClassPathImplementation { 548 private List <PropertyChangeListener > listeners = new ArrayList <PropertyChangeListener >(); 549 550 public List <? extends PathResourceImplementation> getResources() { 551 Request request; 552 synchronized (GlobalSourcePath.this) { 553 if (GlobalSourcePath.this.binaryResources != null) { 554 return GlobalSourcePath.this.binaryResources; 555 } 556 request = new Request ( 557 GlobalSourcePath.this.getTimeStamp(), 558 GlobalSourcePath.this.gpr.getPaths(ClassPath.SOURCE), 559 GlobalSourcePath.this.gpr.getPaths(ClassPath.BOOT), 560 GlobalSourcePath.this.gpr.getPaths(ClassPath.COMPILE), 561 new HashSet (GlobalSourcePath.this.activeCps), 562 new HashMap (GlobalSourcePath.this.sourceResults), 563 new HashMap <URL , WeakValue> (GlobalSourcePath.this.unknownRoots), 564 GlobalSourcePath.this.listener, 565 GlobalSourcePath.this.listener); 566 } 567 Result res = createResources (request); 568 if (GlobalSourcePath.this.debugCallBack != null) { 569 GlobalSourcePath.this.debugCallBack.run(); 570 } 571 synchronized (this) { 572 if (GlobalSourcePath.this.getTimeStamp() == res.timeStamp) { 573 if (GlobalSourcePath.this.binaryResources == null) { 574 GlobalSourcePath.this.resources = res.resources; 575 GlobalSourcePath.this.binaryResources = res.binaryResources; 576 GlobalSourcePath.this.unknownResources = res.unknownResources; 577 GlobalSourcePath.this.activeCps = res.newCps; 578 GlobalSourcePath.this.sourceResults = res.newSR; 579 GlobalSourcePath.this.translatedRoots = res.translatedRoots; 580 GlobalSourcePath.this.unknownRoots = res.unknownRoots; 581 } 582 return GlobalSourcePath.this.binaryResources; 583 } 584 else { 585 return res.binaryResources; 586 } 587 } 588 } 589 590 public synchronized void addPropertyChangeListener(PropertyChangeListener listener) { 591 assert listener != null; 592 if (this.listeners == null) { 593 this.listeners = new ArrayList <PropertyChangeListener > (); 594 } 595 this.listeners.add (listener); 596 } 597 598 public void removePropertyChangeListener(PropertyChangeListener listener) { 599 assert listener != null; 600 if (this.listeners == null) { 601 return; 602 } 603 this.listeners.remove(listener); 604 } 605 606 void firePropertyChange () { 607 PropertyChangeListener [] _listeners; 608 synchronized (this) { 609 if (this.listeners == null) { 610 return; 611 } 612 _listeners = this.listeners.toArray(new PropertyChangeListener [this.listeners.size()]); 613 } 614 PropertyChangeEvent event = new PropertyChangeEvent (this,PROP_RESOURCES,null,null); 615 for (PropertyChangeListener l : _listeners) { 616 l.propertyChange (event); 617 } 618 } 619 } 620 621 622 private class Listener implements GlobalPathRegistryListener, PropertyChangeListener , ChangeListener { 623 624 public void pathsAdded(GlobalPathRegistryEvent event) { 625 resetCacheAndFire (); 626 } 627 628 public void pathsRemoved(GlobalPathRegistryEvent event) { 629 resetCacheAndFire (); 630 } 631 632 public void propertyChange(PropertyChangeEvent evt) { 633 String propName = evt.getPropertyName(); 634 if (ClassPath.PROP_ENTRIES.equals(propName)) { 635 resetCacheAndFire (); 636 } 637 else if (ClassPath.PROP_INCLUDES.equals(propName)) { 638 if (excludesListener != null) { 639 PropertyChangeEvent event = new PropertyChangeEvent (this,PROP_INCLUDES,evt.getSource(),evt.getSource()); 640 excludesListener.propertyChange(event); 641 } 642 } 643 } 644 645 public void stateChanged (ChangeEvent event) { 646 resetCacheAndFire(); 647 } 648 } 649 650 public static synchronized GlobalSourcePath getDefault () { 651 if (instance == null) { 652 instance = new GlobalSourcePath (); 653 } 654 return instance; 655 } 656 657 } 658 | Popular Tags |