1 19 20 package org.netbeans.modules.java.source.classpath; 21 22 import java.beans.PropertyChangeSupport ; 23 import java.io.File ; 24 import java.io.IOException ; 25 import java.util.Iterator ; 26 import java.util.LinkedList ; 27 import java.util.concurrent.CountDownLatch ; 28 import java.util.concurrent.ExecutorService ; 29 import java.util.concurrent.Executors ; 30 import junit.framework.*; 31 import java.beans.PropertyChangeListener ; 32 import java.net.URL ; 33 import java.util.ArrayList ; 34 import java.util.HashMap ; 35 import java.util.HashSet ; 36 import java.util.List ; 37 import java.util.Map ; 38 import java.util.Set ; 39 import javax.swing.event.ChangeEvent ; 40 import javax.swing.event.ChangeListener ; 41 import org.netbeans.api.java.classpath.ClassPath; 42 import org.netbeans.api.java.classpath.GlobalPathRegistry; 43 import org.netbeans.api.java.queries.SourceForBinaryQuery; 44 import org.netbeans.junit.NbTestCase; 45 import org.netbeans.modules.java.source.usages.IndexUtil; 46 import org.netbeans.spi.java.classpath.ClassPathFactory; 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.netbeans.spi.java.queries.SourceForBinaryQueryImplementation; 51 import org.openide.filesystems.FileObject; 52 import org.openide.filesystems.FileUtil; 53 import org.openide.util.Lookup; 54 import org.openide.util.lookup.Lookups; 55 import org.openide.util.lookup.ProxyLookup; 56 57 61 public class GlobalSourcePathTest extends NbTestCase { 62 63 private FileObject srcRoot1; 64 private FileObject srcRoot2; 65 private FileObject srcRoot3; 66 private FileObject compRoot1; 67 private FileObject compRoot2; 68 private FileObject bootRoot1; 69 private FileObject bootRoot2; 70 private FileObject compSrc1; 71 private FileObject compSrc2; 72 private FileObject bootSrc1; 73 private FileObject unknown1; 74 private FileObject unknown2; 75 private FileObject unknownSrc2; 76 77 static { 78 GlobalSourcePathTest.class.getClassLoader().setDefaultAssertionStatus(true); 79 System.setProperty("org.openide.util.Lookup", GlobalSourcePathTest.Lkp.class.getName()); 80 Assert.assertEquals(GlobalSourcePathTest.Lkp.class, Lookup.getDefault().getClass()); 81 } 82 83 public static class Lkp extends ProxyLookup { 84 85 private static Lkp DEFAULT; 86 87 public Lkp () { 88 Assert.assertNull(DEFAULT); 89 DEFAULT = this; 90 ClassLoader l = Lkp.class.getClassLoader(); 91 this.setLookups( 92 new Lookup [] { 93 Lookups.metaInfServices(l), 94 Lookups.fixed (new Object [] {DeadLockSFBQImpl.getDefault (), SFBQImpl.getDefault (),l}), 95 }); 96 } 97 98 } 99 100 101 public GlobalSourcePathTest(String testName) { 102 super(testName); 103 } 104 105 protected @Override void setUp() throws Exception { 106 this.clearWorkDir(); 107 File _wd = this.getWorkDir(); 108 FileObject wd = FileUtil.toFileObject(_wd); 109 File cacheFolder = new File (_wd,"cache"); 110 cacheFolder.mkdirs(); 111 IndexUtil.setCacheFolder(cacheFolder); 112 assertNotNull("No masterfs",wd); 113 srcRoot1 = wd.createFolder("src1"); 114 assertNotNull(srcRoot1); 115 srcRoot2 = wd.createFolder("src2"); 116 assertNotNull(srcRoot2); 117 srcRoot3 = wd.createFolder("src3"); 118 assertNotNull (srcRoot3); 119 compRoot1 = wd.createFolder("comp1"); 120 assertNotNull (compRoot1); 121 compRoot2 = wd.createFolder("comp2"); 122 assertNotNull (compRoot2); 123 bootRoot1 = wd.createFolder("boot1"); 124 assertNotNull (bootRoot1); 125 bootRoot2 = wd.createFolder("boot2"); 126 assertNotNull (bootRoot2); 127 compSrc1 = wd.createFolder("cs1"); 128 assertNotNull (compSrc1); 129 compSrc2 = wd.createFolder("cs2"); 130 assertNotNull (compSrc2); 131 bootSrc1 = wd.createFolder("bs1"); 132 assertNotNull (bootSrc1); 133 unknown1 = wd.createFolder("uknw1"); 134 assertNotNull (unknown1); 135 unknown2 = wd.createFolder("uknw2"); 136 assertNotNull (unknown2); 137 unknownSrc2 = wd.createFolder("uknwSrc2"); 138 assertNotNull(unknownSrc2); 139 SFBQImpl q = SFBQImpl.getDefault(); 140 q.register (bootRoot1,bootSrc1); 141 q.register (compRoot1,compSrc1); 142 q.register (compRoot2,compSrc2); 143 q.register (unknown2,unknownSrc2); 144 } 145 146 protected @Override void tearDown() throws Exception { 147 } 148 149 public void testGlobalSourcePath () throws Exception { 150 GlobalPathRegistry regs = GlobalPathRegistry.getDefault(); 151 GlobalSourcePath gcp = GlobalSourcePath.getDefault(); 152 ClassPathImplementation cpi = ClassPathSupport.createProxyClassPathImplementation(new ClassPathImplementation[] {gcp.getSourcePath(), gcp.getUnknownSourcePath()}); 153 List <? extends PathResourceImplementation> impls = cpi.getResources(); 154 assertNotNull (impls); 155 assertEquals(0,impls.size()); 156 MutableClassPathImplementation mcpi1 = new MutableClassPathImplementation (); 158 mcpi1.addResource(this.srcRoot1); 159 ClassPath cp1 = ClassPathFactory.createClassPath(mcpi1); 160 regs.register(ClassPath.SOURCE,new ClassPath[]{cp1}); 161 impls = cpi.getResources(); 162 assertNotNull (impls); 163 assertEquals(1,impls.size()); 164 assertEquals(srcRoot1.getURL(),impls.get(0).getRoots()[0]); 165 mcpi1.addResource(srcRoot2); 167 impls = cpi.getResources(); 168 assertNotNull (impls); 169 assertEquals(2,impls.size()); 170 assertEquals(new FileObject[] {srcRoot1, srcRoot2},impls); 171 mcpi1.removeResource(srcRoot1); 172 impls = cpi.getResources(); 173 assertNotNull (impls); 174 assertEquals(1,impls.size()); 175 assertEquals(srcRoot2.getURL(),impls.get(0).getRoots()[0]); 176 MutableClassPathImplementation mcpi2 = new MutableClassPathImplementation (); 178 mcpi2.addResource(srcRoot1); 179 ClassPath cp2 = ClassPathFactory.createClassPath(mcpi2); 180 regs.register (ClassPath.SOURCE, new ClassPath[] {cp2}); 181 impls = cpi.getResources(); 182 assertNotNull (impls); 183 assertEquals(new FileObject[] {srcRoot2, srcRoot1},impls); 184 mcpi2.addResource(srcRoot3); 185 impls = cpi.getResources(); 186 assertNotNull (impls); 187 assertEquals(new FileObject[] {srcRoot2, srcRoot1, srcRoot3},impls); 188 regs.unregister(ClassPath.SOURCE,new ClassPath[] {cp2}); 190 impls = cpi.getResources(); 191 assertNotNull (impls); 192 assertEquals(new FileObject[] {srcRoot2},impls); 193 ClassPath cp3 = ClassPathSupport.createClassPath(new FileObject[] {bootRoot1,bootRoot2}); 195 regs.register(ClassPath.BOOT,new ClassPath[] {cp3}); 196 impls = cpi.getResources(); 197 assertNotNull (impls); 198 assertEquals(new FileObject[] {srcRoot2, bootSrc1},impls); 199 MutableClassPathImplementation mcpi4 = new MutableClassPathImplementation (); 200 mcpi4.addResource (compRoot1); 201 ClassPath cp4 = ClassPathFactory.createClassPath(mcpi4); 202 regs.register(ClassPath.COMPILE,new ClassPath[] {cp4}); 203 impls = cpi.getResources(); 204 assertNotNull (impls); 205 assertEquals(new FileObject[] {srcRoot2, bootSrc1, compSrc1},impls); 206 mcpi4.addResource(compRoot2); 207 impls = cpi.getResources(); 208 assertNotNull (impls); 209 assertEquals(new FileObject[] {srcRoot2, bootSrc1, compSrc1, compSrc2},impls); 210 mcpi4.removeResource(compRoot1); 211 impls = cpi.getResources(); 212 assertNotNull (impls); 213 assertEquals(new FileObject[] {srcRoot2, bootSrc1, compSrc2},impls); 214 regs.unregister(ClassPath.BOOT,new ClassPath[] {cp3}); 215 impls = cpi.getResources(); 216 assertNotNull (impls); 217 assertEquals(new FileObject[] {srcRoot2, compSrc2},impls); 218 SFBQImpl.getDefault().register(compRoot2,compSrc1); 220 impls = cpi.getResources(); 221 assertNotNull (impls); 222 assertEquals(new FileObject[] {srcRoot2, compSrc1},impls); 223 SFBQImpl.getDefault().register(compRoot2,compSrc2); 224 impls = cpi.getResources(); 225 assertNotNull (impls); 226 assertEquals(new FileObject[] {srcRoot2, compSrc2},impls); 227 ClassPath ucp = ClassPathSupport.createClassPath(new FileObject[] {unknown1, unknown2}); 229 gcp.getSourceRootForBinaryRoot(unknown1.getURL(),ucp,true); 230 gcp.getSourceRootForBinaryRoot(unknown2.getURL(),ucp,true); 231 impls = cpi.getResources(); 232 assertNotNull (impls); 233 assertEquals(new FileObject[] {srcRoot2, compSrc2, unknownSrc2},impls); 234 ucp = null; 236 gc(); gc(); 237 impls = cpi.getResources(); 238 assertNotNull (impls); 239 assertEquals(new FileObject[] {srcRoot2, compSrc2},impls); 240 ucp = ClassPathSupport.createClassPath(new FileObject[] {unknown1, unknown2}); 242 gcp.getSourceRootForBinaryRoot(unknown1.getURL(),ucp,true); 243 gcp.getSourceRootForBinaryRoot(unknown2.getURL(),ucp,true); 244 impls = cpi.getResources(); 245 assertNotNull (impls); 246 assertEquals(new FileObject[] {srcRoot2, compSrc2, unknownSrc2},impls); 247 ClassPath rcp = ClassPathSupport.createClassPath(new FileObject[] {unknown1, unknown2}); 248 regs.register(ClassPath.COMPILE,new ClassPath[] {rcp}); 249 impls = cpi.getResources(); 250 assertNotNull (impls); 251 assertEquals(new FileObject[] {srcRoot2, compSrc2, unknownSrc2},impls); 252 ucp = null; 253 gc(); gc(); 254 impls = cpi.getResources(); 255 assertNotNull (impls); 256 assertEquals(new FileObject[] {srcRoot2, compSrc2, unknownSrc2},impls); 257 regs.unregister(ClassPath.COMPILE,new ClassPath[] {rcp}); 258 impls = cpi.getResources(); 259 assertNotNull (impls); 260 assertEquals(new FileObject[] {srcRoot2, compSrc2},impls); 261 } 262 263 266 public void testProjectMutexDeadlock () throws Exception { 267 ExecutorService es = Executors.newSingleThreadExecutor(); 268 final CountDownLatch wk_ready = new CountDownLatch (1); 269 final CountDownLatch mt_ready = new CountDownLatch (1); 270 try { 271 es.submit(new Runnable () { 272 public void run () { 273 try { 274 Object lock = DeadLockSFBQImpl.getDefault().getLock(); 275 ClassPath cp = ClassPathSupport.createClassPath(new FileObject[] {unknown1}); 276 synchronized (lock) { 277 wk_ready.countDown(); 278 mt_ready.await(); 279 Thread.sleep(1000); 280 GlobalPathRegistry.getDefault().register(ClassPath.COMPILE, new ClassPath[] {cp}); 281 } 282 } catch (InterruptedException e) { 283 } 284 } 285 }); 286 ClassPath cp = ClassPathSupport.createClassPath(new FileObject[] {unknown1}); 287 wk_ready.await(); 288 mt_ready.countDown(); 289 GlobalPathRegistry.getDefault().register(ClassPath.COMPILE, new ClassPath[] {cp}); 290 } finally { 291 es.shutdownNow(); 292 } 293 } 294 295 296 public void testRaceCondition () throws Exception { 297 final GlobalPathRegistry regs = GlobalPathRegistry.getDefault(); 298 final GlobalSourcePath gcp = GlobalSourcePath.getDefault(); 299 final int initialSize = gcp.getSourcePath().getResources().size(); 300 final CountDownLatch state_1 = new CountDownLatch (1); 301 final CountDownLatch state_2 = new CountDownLatch (1); 302 final CountDownLatch state_3 = new CountDownLatch (1); 303 final CountDownLatch state_4 = new CountDownLatch (1); 304 final ExecutorService es = Executors.newSingleThreadExecutor(); 305 gcp.setDebugCallBack(new Runnable (){ 306 public void run () { 307 try { 308 state_2.countDown(); 309 state_3.await(); 310 } catch (InterruptedException ie) {} 311 } 312 }); 313 try { 314 es.submit(new Runnable () { 315 public void run () { 316 try { 317 state_1.await(); 318 gcp.getSourcePath().getResources(); 319 gcp.getSourcePath().getResources(); 320 state_4.countDown(); 321 } catch (InterruptedException ie) {} 322 } 323 }); 324 regs.register(ClassPath.SOURCE, new ClassPath[] {ClassPathSupport.createClassPath(new URL [] {new URL ("file:///foo1/")})}); 325 state_1.countDown(); 326 state_2.await(); 327 regs.register(ClassPath.SOURCE, new ClassPath[] {ClassPathSupport.createClassPath(new URL [] {new URL ("file:///foo2/")})}); 328 state_3.countDown(); 329 state_4.await(); 330 assertEquals("Race condition",initialSize+2,gcp.getSourcePath().getResources().size()); 331 } finally { 332 es.shutdownNow(); 333 } 334 } 335 336 public void testRaceCondition2 () throws Exception { 337 final GlobalPathRegistry regs = GlobalPathRegistry.getDefault(); 338 final GlobalSourcePath gcp = GlobalSourcePath.getDefault(); 339 final ClassPathImplementation cp = gcp.getSourcePath(); 340 final int initialSize = cp.getResources().size(); 341 final CountDownLatch state_1 = new CountDownLatch (1); 342 final CountDownLatch state_2 = new CountDownLatch (1); 343 final CountDownLatch state_3 = new CountDownLatch (1); 344 final CountDownLatch state_4 = new CountDownLatch (1); 345 final ExecutorService es = Executors.newSingleThreadExecutor(); 346 gcp.setDebugCallBack(new Runnable (){ 347 public void run () { 348 try { 349 state_2.countDown(); 350 state_3.await(); 351 } catch (InterruptedException ie) {} 352 } 353 }); 354 try { 355 es.submit(new Runnable () { 356 public void run () { 357 try { 358 state_1.await(); 359 cp.getResources(); 360 cp.getResources(); 361 state_4.countDown(); 362 } catch (InterruptedException ie) {} 363 } 364 }); 365 regs.register(ClassPath.SOURCE, new ClassPath[] {ClassPathSupport.createClassPath(new URL [] {new URL ("file:///foo3/")})}); 366 state_1.countDown(); 367 state_2.await(); 368 regs.register(ClassPath.SOURCE, new ClassPath[] {ClassPathSupport.createClassPath(new URL [] {new URL ("file:///foo4/")})}); 369 state_3.countDown(); 370 state_4.await(); 371 assertEquals("Race condition",initialSize+2, cp.getResources().size()); 372 } finally { 373 es.shutdownNow(); 374 } 375 } 376 377 public void testBinaryPath () throws Exception { 378 Set <ClassPath> cps = GlobalPathRegistry.getDefault().getPaths(ClassPath.SOURCE); 379 GlobalPathRegistry.getDefault().unregister(ClassPath.SOURCE, cps.toArray(new ClassPath[cps.size()])); 380 cps = GlobalPathRegistry.getDefault().getPaths(ClassPath.BOOT); 381 GlobalPathRegistry.getDefault().unregister(ClassPath.BOOT, cps.toArray(new ClassPath[cps.size()])); 382 cps = GlobalPathRegistry.getDefault().getPaths(ClassPath.COMPILE); 383 GlobalPathRegistry.getDefault().unregister(ClassPath.COMPILE, cps.toArray(new ClassPath[cps.size()])); 384 ClassPathImplementation sourcePath = GlobalSourcePath.getDefault().getSourcePath(); 385 ClassPathImplementation binaryPath = GlobalSourcePath.getDefault().getBinaryPath(); 386 assertEquals (0,sourcePath.getResources().size()); 387 assertEquals (0,binaryPath.getResources().size()); 388 389 390 ClassPath src = ClassPathSupport.createClassPath(new FileObject[] {srcRoot1, srcRoot2, srcRoot3}); 391 ClassPath compile = ClassPathSupport.createClassPath(new FileObject[] {compRoot1, compRoot2}); 392 ClassPath boot = ClassPathSupport.createClassPath(new FileObject[] {bootRoot1, bootRoot2}); 393 394 GlobalPathRegistry.getDefault().register(ClassPath.SOURCE, new ClassPath[] {src}); 395 GlobalPathRegistry.getDefault().register(ClassPath.COMPILE, new ClassPath[] {compile}); 396 GlobalPathRegistry.getDefault().register(ClassPath.BOOT, new ClassPath[] {boot}); 397 398 List <? extends PathResourceImplementation> res = sourcePath.getResources(); 399 assertEquals(new FileObject[] {srcRoot1, srcRoot2, srcRoot3, compSrc1, compSrc2, bootSrc1}, res); 400 res = binaryPath.getResources(); 401 assertEquals(new FileObject[] {bootRoot2}, res); 402 403 ClassPath compile2 = ClassPathSupport.createClassPath(new FileObject[] {unknown1, unknown2}); 404 GlobalPathRegistry.getDefault().register(ClassPath.COMPILE, new ClassPath[] {compile2}); 405 406 res = sourcePath.getResources(); 407 assertEquals(new FileObject[] {srcRoot1, srcRoot2, srcRoot3, compSrc1, compSrc2, bootSrc1, unknownSrc2}, res); 408 res = binaryPath.getResources(); 409 assertEquals(new FileObject[] {bootRoot2, unknown1}, res); 410 } 411 412 private static void gc () { 413 System.gc(); 414 long[][] spaces = new long[100][]; 415 for (int i=0; i<spaces.length; i++) { 416 spaces[i] = new long[1000]; 417 } 418 System.gc(); 419 spaces = null; 420 System.gc(); 421 } 422 423 private void assertEquals (FileObject[] expected, List <? extends PathResourceImplementation> result) throws IOException { 424 assertEquals(expected.length, result.size()); 425 Set <URL > expectedUrls = new HashSet (); 426 for (FileObject fo : expected) { 427 expectedUrls.add(fo.getURL()); 428 } 429 for (PathResourceImplementation impl : result) { 430 URL url = impl.getRoots()[0]; 431 if (!expectedUrls.remove (url)) { 432 assertTrue (String.format("Unknown URL: %s in: %s", url, expectedUrls),false); 433 } 434 } 435 assertEquals(expectedUrls.toString(),0,expectedUrls.size()); 436 } 437 438 439 private static class MutableClassPathImplementation implements ClassPathImplementation { 440 441 private final List <PathResourceImplementation> res; 442 private final PropertyChangeSupport support; 443 444 public MutableClassPathImplementation () { 445 res = new ArrayList (); 446 support = new PropertyChangeSupport (this); 447 } 448 449 public void addResource (FileObject fo) throws IOException { 450 res.add(ClassPathSupport.createResource(fo.getURL())); 451 this.support.firePropertyChange(PROP_RESOURCES,null,null); 452 } 453 454 public void removeResource (FileObject fo) throws IOException { 455 URL url = fo.getURL(); 456 for (Iterator <PathResourceImplementation> it = res.iterator(); it.hasNext(); ) { 457 PathResourceImplementation r = it.next(); 458 if (url.equals(r.getRoots()[0])) { 459 it.remove(); 460 this.support.firePropertyChange(PROP_RESOURCES,null,null); 461 } 462 } 463 } 464 465 public void removePropertyChangeListener(PropertyChangeListener listener) { 466 support.removePropertyChangeListener(listener); 467 } 468 469 public void addPropertyChangeListener(PropertyChangeListener listener) { 470 support.addPropertyChangeListener(listener); 471 } 472 473 public List getResources() { 474 return res; 475 } 476 477 } 478 479 private static class SFBQImpl implements SourceForBinaryQueryImplementation { 480 481 private static SFBQImpl instance; 482 483 final Map <URL ,FileObject> map = new HashMap (); 484 final Map <URL ,Result> results = new HashMap (); 485 486 private SFBQImpl () { 487 488 } 489 490 public void register (FileObject binRoot, FileObject sourceRoot) throws IOException { 491 URL url = binRoot.getURL(); 492 this.map.put (url,sourceRoot); 493 Result r = results.get (url); 494 if (r != null) { 495 r.update (sourceRoot); 496 } 497 } 498 499 public void unregister (FileObject binRoot) throws IOException { 500 URL url = binRoot.getURL(); 501 this.map.remove(url); 502 Result r = results.get (url); 503 if (r != null) { 504 r.update (null); 505 } 506 } 507 508 public void clean () { 509 this.map.clear(); 510 this.results.clear(); 511 } 512 513 public SourceForBinaryQuery.Result findSourceRoots(URL binaryRoot) { 514 FileObject srcRoot = this.map.get(binaryRoot); 515 if (srcRoot == null) { 516 return null; 517 } 518 Result r = results.get (binaryRoot); 519 if (r == null) { 520 r = new Result (srcRoot); 521 results.put(binaryRoot, r); 522 } 523 return r; 524 } 525 526 public static synchronized SFBQImpl getDefault () { 527 if (instance == null) { 528 instance = new SFBQImpl (); 529 } 530 return instance; 531 } 532 533 public static class Result implements SourceForBinaryQuery.Result { 534 535 private FileObject root; 536 private final List <ChangeListener > listeners; 537 538 public Result (FileObject root) { 539 this.root = root; 540 this.listeners = new LinkedList (); 541 } 542 543 public void update (FileObject root) { 544 this.root = root; 545 fireChange (); 546 } 547 548 public synchronized void addChangeListener(ChangeListener l) { 549 this.listeners.add(l); 550 } 551 552 public synchronized void removeChangeListener(ChangeListener l) { 553 this.listeners.remove(l); 554 } 555 556 public FileObject[] getRoots() { 557 if (this.root == null) { 558 return new FileObject[0]; 559 } 560 else { 561 return new FileObject[] {this.root}; 562 } 563 } 564 565 private void fireChange () { 566 ChangeListener [] _listeners; 567 synchronized (this) { 568 _listeners = this.listeners.toArray(new ChangeListener [this.listeners.size()]); 569 } 570 ChangeEvent event = new ChangeEvent (this); 571 for (ChangeListener l : _listeners) { 572 l.stateChanged (event); 573 } 574 } 575 } 576 577 } 578 579 private static class DeadLockSFBQImpl implements SourceForBinaryQueryImplementation { 580 581 private static DeadLockSFBQImpl instance; 582 583 private final Object lock; 584 585 private DeadLockSFBQImpl () { 586 this.lock = new String ("Lock"); 587 } 588 589 public Object getLock () { 590 return this.lock; 591 } 592 593 public SourceForBinaryQuery.Result findSourceRoots(URL binaryRoot) { 594 synchronized (lock) { 595 lock.toString(); 596 } 597 return null; 598 } 599 600 public static synchronized DeadLockSFBQImpl getDefault () { 601 if (instance == null) { 602 instance = new DeadLockSFBQImpl (); 603 } 604 return instance; 605 } 606 607 } 608 609 } 610 | Popular Tags |