001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.api.java.classpath;
043:
044: import java.net.URL;
045: import java.util.ArrayList;
046: import java.util.Arrays;
047: import java.util.Collections;
048: import java.util.HashMap;
049: import java.util.HashSet;
050: import java.util.Iterator;
051: import java.util.Set;
052: import java.util.Map;
053: import javax.swing.event.ChangeEvent;
054: import javax.swing.event.ChangeListener;
055: import org.netbeans.api.java.queries.SourceForBinaryQuery;
056: import org.netbeans.junit.MockServices;
057: import org.netbeans.junit.NbTestCase;
058: import org.netbeans.spi.java.classpath.support.ClassPathSupport;
059: import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation;
060: import org.openide.filesystems.FileObject;
061: import org.netbeans.spi.java.classpath.ClassPathFactory;
062: import org.netbeans.spi.java.classpath.ClassPathImplementation;
063: import org.netbeans.spi.java.classpath.FilteringPathResourceImplementation;
064: import org.netbeans.spi.java.classpath.support.PathResourceBase;
065: import org.openide.filesystems.FileStateInvalidException;
066: import org.openide.filesystems.FileUtil;
067: import org.openide.util.ChangeSupport;
068: import org.openide.util.Lookup;
069:
070: /**
071: * Test functionality of GlobalPathRegistry.
072: * @author Jesse Glick
073: */
074: public class GlobalPathRegistryTest extends NbTestCase {
075:
076: public GlobalPathRegistryTest(String name) {
077: super (name);
078: MockServices
079: .setServices(SFBQImpl.class, DeadLockSFBQImpl.class);
080: }
081:
082: private GlobalPathRegistry r;
083: private FileObject root;
084: private ClassPath cp1, cp2, cp3, cp4, cp5;
085:
086: protected void setUp() throws Exception {
087: super .setUp();
088: r = GlobalPathRegistry.getDefault();
089: r.clear();
090: clearWorkDir();
091: root = FileUtil.toFileObject(getWorkDir());
092: cp1 = ClassPathSupport.createClassPath(new FileObject[] { root
093: .createFolder("1") });
094: cp2 = ClassPathSupport.createClassPath(new FileObject[] { root
095: .createFolder("2") });
096: cp3 = ClassPathSupport.createClassPath(new FileObject[] { root
097: .createFolder("3") });
098: cp4 = ClassPathSupport.createClassPath(new FileObject[] { root
099: .createFolder("4") });
100: cp5 = ClassPathSupport.createClassPath(new FileObject[] { root
101: .createFolder("5") });
102: }
103:
104: public void testBasicOperation() throws Exception {
105: assertEquals("initially no paths of type a", Collections
106: .<ClassPath> emptySet(), r.getPaths("a"));
107: r.register("a", new ClassPath[] { cp1, cp2 });
108: assertEquals("added some paths of type a",
109: new HashSet<ClassPath>(Arrays.asList(new ClassPath[] {
110: cp1, cp2 })), r.getPaths("a"));
111: r.register("a", new ClassPath[0]);
112: assertEquals("did not add any new paths to a",
113: new HashSet<ClassPath>(Arrays.asList(new ClassPath[] {
114: cp1, cp2 })), r.getPaths("a"));
115: assertEquals("initially no paths of type b", Collections
116: .<ClassPath> emptySet(), r.getPaths("b"));
117: r.register("b", new ClassPath[] { cp3, cp4, cp5 });
118: assertEquals("added some paths of type b",
119: new HashSet<ClassPath>(Arrays.asList(new ClassPath[] {
120: cp3, cp4, cp5 })), r.getPaths("b"));
121: r.unregister("a", new ClassPath[] { cp1 });
122: assertEquals("only one path left of type a", Collections
123: .<ClassPath> singleton(cp2), r.getPaths("a"));
124: r.register("a", new ClassPath[] { cp2, cp3 });
125: assertEquals("only one new path added of type a",
126: new HashSet<ClassPath>(Arrays.asList(new ClassPath[] {
127: cp2, cp3 })), r.getPaths("a"));
128: r.unregister("a", new ClassPath[] { cp2 });
129: assertEquals("still have extra cp2 in a",
130: new HashSet<ClassPath>(Arrays.asList(new ClassPath[] {
131: cp2, cp3 })), r.getPaths("a"));
132: r.unregister("a", new ClassPath[] { cp2 });
133: assertEquals("last cp2 removed from a", Collections
134: .<ClassPath> singleton(cp3), r.getPaths("a"));
135: r.unregister("a", new ClassPath[] { cp3 });
136: assertEquals("a now empty", Collections.<ClassPath> emptySet(),
137: r.getPaths("a"));
138: r.unregister("a", new ClassPath[0]);
139: assertEquals("a still empty", Collections
140: .<ClassPath> emptySet(), r.getPaths("a"));
141: try {
142: r.unregister("a", new ClassPath[] { cp3 });
143: fail("should not have been permitted to unregister a nonexistent entry");
144: } catch (IllegalArgumentException x) {
145: // Good.
146: }
147: }
148:
149: public void testListening() throws Exception {
150: assertEquals("initially no paths of type b", Collections
151: .<ClassPath> emptySet(), r.getPaths("b"));
152: L l = new L();
153: r.addGlobalPathRegistryListener(l);
154: r.register("b", new ClassPath[] { cp1, cp2 });
155: GlobalPathRegistryEvent e = l.event();
156: assertNotNull("got an event", e);
157: assertTrue("was an addition", l.added());
158: assertEquals("right registry", r, e.getRegistry());
159: assertEquals("right ID", "b", e.getId());
160: assertEquals("right changed paths", new HashSet<ClassPath>(
161: Arrays.asList(new ClassPath[] { cp1, cp2 })), e
162: .getChangedPaths());
163: r.register("b", new ClassPath[] { cp2, cp3 });
164: e = l.event();
165: assertNotNull("got an event", e);
166: assertTrue("was an addition", l.added());
167: assertEquals("right changed paths", Collections
168: .<ClassPath> singleton(cp3), e.getChangedPaths());
169: r.register("b", new ClassPath[] { cp3 });
170: e = l.event();
171: assertNull("no event for adding a dupe", e);
172: r.unregister("b", new ClassPath[] { cp1, cp3, cp3 });
173: e = l.event();
174: assertNotNull("got an event", e);
175: assertFalse("was a removal", l.added());
176: assertEquals("right changed paths", new HashSet<ClassPath>(
177: Arrays.asList(new ClassPath[] { cp1, cp3 })), e
178: .getChangedPaths());
179: r.unregister("b", new ClassPath[] { cp2 });
180: e = l.event();
181: assertNull("no event for removing an extra", e);
182: r.unregister("b", new ClassPath[] { cp2 });
183: e = l.event();
184: assertNotNull("now an event for removing the last copy", e);
185: assertFalse("was a removal", l.added());
186: assertEquals("right changed paths", Collections
187: .<ClassPath> singleton(cp2), e.getChangedPaths());
188: }
189:
190: public void testGetSourceRoots() throws Exception {
191: SFBQImpl query = Lookup.getDefault().lookup(SFBQImpl.class);
192: assertNotNull(
193: "SourceForBinaryQueryImplementation not found in lookup",
194: query);
195: query.addPair(cp3.getRoots()[0].getURL(), new FileObject[0]);
196: ClassPathTest.TestClassPathImplementation cpChangingImpl = new ClassPathTest.TestClassPathImplementation();
197: ClassPath cpChanging = ClassPathFactory
198: .createClassPath(cpChangingImpl);
199: assertEquals("cpChangingImpl is empty", 0, cpChanging
200: .getRoots().length);
201: r.register(ClassPath.SOURCE, new ClassPath[] { cp1, cp2,
202: cpChanging });
203: r.register(ClassPath.COMPILE, new ClassPath[] { cp3 });
204: Set<FileObject> result = r.getSourceRoots();
205: assertEquals("Wrong number of source roots", result.size(), cp1
206: .getRoots().length
207: + cp2.getRoots().length);
208: assertTrue("Missing roots from cp1", result.containsAll(Arrays
209: .asList(cp1.getRoots())));
210: assertTrue("Missing roots from cp2", result.containsAll(Arrays
211: .asList(cp2.getRoots())));
212: // simulate classpath change:
213: URL u = cp5.entries().get(0).getURL();
214: cpChangingImpl.addResource(u);
215: assertEquals("cpChangingImpl is not empty", 1, cpChanging
216: .getRoots().length);
217: result = r.getSourceRoots();
218: assertEquals("Wrong number of source roots", result.size(), cp1
219: .getRoots().length
220: + cp2.getRoots().length + cpChanging.getRoots().length);
221: assertTrue("Missing roots from cp1", result.containsAll(Arrays
222: .asList(cp1.getRoots())));
223: assertTrue("Missing roots from cp2", result.containsAll(Arrays
224: .asList(cp2.getRoots())));
225: cpChangingImpl.removeResource(u);
226:
227: query.addPair(cp3.getRoots()[0].getURL(), cp4.getRoots());
228: result = r.getSourceRoots();
229: assertEquals("Wrong number of source roots", result.size(), cp1
230: .getRoots().length
231: + cp2.getRoots().length + cp4.getRoots().length);
232: assertTrue("Missing roots from cp1", result.containsAll(Arrays
233: .asList(cp1.getRoots())));
234: assertTrue("Missing roots from cp2", result.containsAll(Arrays
235: .asList(cp2.getRoots())));
236: assertTrue("Missing roots from cp4", result.containsAll(Arrays
237: .asList(cp4.getRoots())));
238: }
239:
240: /**
241: * Tests issue: #60976:Deadlock between JavaFastOpen$Evaluator and AntProjectHelper$something
242: */
243: public void testGetSourceRootsDeadLock() throws Exception {
244: DeadLockSFBQImpl query = Lookup.getDefault().lookup(
245: DeadLockSFBQImpl.class);
246: assertNotNull(
247: "SourceForBinaryQueryImplementation not found in lookup",
248: query);
249: r.register(ClassPath.COMPILE, new ClassPath[] { cp1 });
250: try {
251: query.setSynchronizedJob(new Runnable() {
252: public void run() {
253: r.register(ClassPath.COMPILE,
254: new ClassPath[] { cp2 });
255: }
256: });
257: r.getSourceRoots();
258: } finally {
259: query.setSynchronizedJob(null);
260: }
261: }
262:
263: public void testFindResource() throws Exception {
264: final FileObject src1 = root.createFolder("src1");
265: FileObject src1included = FileUtil.createData(src1,
266: "included/file");
267: FileUtil.createData(src1, "excluded/file1");
268: FileUtil.createData(src1, "excluded/file2");
269: FileObject src2 = root.createFolder("src2");
270: FileObject src2included = FileUtil.createData(src2,
271: "included/file");
272: FileObject src2excluded1 = FileUtil.createData(src2,
273: "excluded/file1");
274: class PRI extends PathResourceBase implements
275: FilteringPathResourceImplementation {
276: public URL[] getRoots() {
277: try {
278: return new URL[] { src1.getURL() };
279: } catch (FileStateInvalidException x) {
280: throw new AssertionError(x);
281: }
282: }
283:
284: public boolean includes(URL root, String resource) {
285: return resource.startsWith("incl");
286: }
287:
288: public ClassPathImplementation getContent() {
289: return null;
290: }
291: }
292: r.register(ClassPath.SOURCE, new ClassPath[] {
293: ClassPathSupport.createClassPath(Collections
294: .singletonList(new PRI())),
295: ClassPathSupport
296: .createClassPath(new FileObject[] { src2 }) });
297: assertTrue(Arrays.asList(src1included, src2included).contains(
298: r.findResource("included/file")));
299: assertEquals(src2excluded1, r.findResource("excluded/file1"));
300: assertEquals(null, r.findResource("excluded/file2"));
301: assertEquals(null, r.findResource("nonexistent"));
302: }
303:
304: public void testMemoryLeak124055() throws Exception {
305: final GlobalPathRegistry reg = GlobalPathRegistry.getDefault();
306: final Set<? extends ClassPath> src = reg
307: .getPaths(ClassPath.SOURCE);
308: final Set<? extends ClassPath> boot = reg
309: .getPaths(ClassPath.BOOT);
310: final Set<? extends ClassPath> compile = reg
311: .getPaths(ClassPath.COMPILE);
312: assertTrue(src.isEmpty());
313: assertTrue(boot.isEmpty());
314: assertTrue(compile.isEmpty());
315: assertTrue(reg.getSourceRoots().isEmpty());
316: r.register(ClassPath.COMPILE, new ClassPath[] { cp3 });
317: SFBQImpl query = Lookup.getDefault().lookup(SFBQImpl.class);
318: query.addPair(cp3.getRoots()[0].getURL(), cp4.getRoots());
319: //There should be one translated source root
320: assertEquals(1, reg.getSourceRoots().size());
321: assertEquals(1, reg.getResults().size());
322: r.unregister(ClassPath.COMPILE, new ClassPath[] { cp3 });
323: //There shouldn't be registered source root
324: assertTrue(reg.getSourceRoots().isEmpty());
325: assertTrue(reg.getResults().isEmpty());
326: }
327:
328: private static final class L implements GlobalPathRegistryListener {
329:
330: private GlobalPathRegistryEvent e;
331: private boolean added;
332:
333: public L() {
334: }
335:
336: public synchronized GlobalPathRegistryEvent event() {
337: GlobalPathRegistryEvent _e = e;
338: e = null;
339: return _e;
340: }
341:
342: public boolean added() {
343: return added;
344: }
345:
346: public synchronized void pathsAdded(GlobalPathRegistryEvent e) {
347: assertNull("checked for last event", this .e);
348: this .e = e;
349: added = true;
350: }
351:
352: public synchronized void pathsRemoved(GlobalPathRegistryEvent e) {
353: assertNull("checked for last event", this .e);
354: this .e = e;
355: added = false;
356: }
357:
358: }
359:
360: public static class SFBQImpl implements
361: SourceForBinaryQueryImplementation {
362:
363: private Map<URL, SourceForBinaryQuery.Result> pairs = new HashMap<URL, SourceForBinaryQuery.Result>();
364:
365: void addPair(URL binaryRoot, FileObject[] sourceRoots) {
366: assert binaryRoot != null && sourceRoots != null;
367: Result r = (Result) this .pairs.get(binaryRoot);
368: if (r == null) {
369: r = new Result(sourceRoots);
370: this .pairs.put(binaryRoot, r);
371: } else {
372: r.setSources(sourceRoots);
373: }
374: }
375:
376: public SourceForBinaryQuery.Result findSourceRoots(
377: URL binaryRoot) {
378: Result result = (Result) this .pairs.get(binaryRoot);
379: return result;
380: }
381:
382: private static class Result implements
383: SourceForBinaryQuery.Result {
384:
385: private FileObject[] sources;
386: private final ChangeSupport changeSupport = new ChangeSupport(
387: this );
388:
389: public Result(FileObject[] sources) {
390: this .sources = sources;
391: }
392:
393: void setSources(FileObject[] sources) {
394: this .sources = sources;
395: this .changeSupport.fireChange();
396: }
397:
398: public void addChangeListener(
399: javax.swing.event.ChangeListener l) {
400: changeSupport.addChangeListener(l);
401: }
402:
403: public FileObject[] getRoots() {
404: return this .sources;
405: }
406:
407: public void removeChangeListener(
408: javax.swing.event.ChangeListener l) {
409: changeSupport.removeChangeListener(l);
410: }
411:
412: }
413:
414: }
415:
416: public static class DeadLockSFBQImpl extends Thread implements
417: SourceForBinaryQueryImplementation {
418:
419: private Runnable r;
420:
421: public SourceForBinaryQuery.Result findSourceRoots(
422: URL binaryRoot) {
423: if (this .r != null) {
424: synchronized (this ) {
425: this .start();
426: try {
427: this .wait();
428: } catch (InterruptedException ie) {
429: ie.printStackTrace();
430: }
431: }
432: }
433: return null;
434: }
435:
436: public synchronized void run() {
437: r.run();
438: this .notify();
439: }
440:
441: public void setSynchronizedJob(Runnable r) {
442: this.r = r;
443: }
444:
445: }
446:
447: }
|