--- a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/ClassPathProviderMerger.java Tue May 06 09:24:02 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,82 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common - * Development and Distribution License("CDDL") (collectively, the - * "License"). You may not use this file except in compliance with the - * License. You can obtain a copy of the License at - * http://www.netbeans.org/cddl-gplv2.html - * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the - * specific language governing permissions and limitations under the - * License. When distributing the software, include this License Header - * Notice in each file and include the License file at - * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this - * particular file as subject to the "Classpath" exception as provided - * by Sun in the GPL Version 2 section of the License file that - * accompanied this code. If applicable, add the following below the - * License Header, with the fields enclosed by brackets [] replaced by - * your own identifying information: - * "Portions Copyrighted [year] [name of copyright owner]" - * - * Contributor(s): - * - * Portions Copyrighted 2007 Sun Microsystems, Inc. - */ -package org.netbeans.modules.java.j2seproject; - -import org.netbeans.api.java.classpath.ClassPath; -import org.netbeans.spi.java.classpath.ClassPathProvider; -import org.netbeans.spi.project.LookupMerger; -import org.openide.filesystems.FileObject; -import org.openide.util.Lookup; - -/** - * - * @author Tomas Zezula - */ -public final class ClassPathProviderMerger implements LookupMerger { - - private final ClassPathProvider defaultProvider; - - public ClassPathProviderMerger (final ClassPathProvider defaultProvider) { - assert defaultProvider != null; - this.defaultProvider = defaultProvider; - } - - public Class getMergeableClass() { - return ClassPathProvider.class; - } - - public ClassPathProvider merge(Lookup lookup) { - return new CPProvider (lookup); - } - - - private class CPProvider implements ClassPathProvider { - - private final Lookup lookup; - - public CPProvider(final Lookup lookup) { - assert lookup != null; - this.lookup = lookup; - } - - public ClassPath findClassPath(FileObject file, String type) { - ClassPath result = defaultProvider.findClassPath(file, type); - if (result != null) { - return result; - } - for (ClassPathProvider cpProvider : lookup.lookupAll(ClassPathProvider.class)) { - result = cpProvider.findClassPath(file, type); - if (result != null) { - return result; - } - } - return null; - } - } - -} --- a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEProject.java Tue May 06 09:24:02 2008 +0200 +++ a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEProject.java Mon May 12 13:57:52 2008 +0200 @@ -265,7 +265,7 @@ new J2SELogicalViewProvider(this, this.updateHelper, evaluator(), spp, refHelper), // new J2SECustomizerProvider(this, this.updateHelper, evaluator(), refHelper), new CustomizerProviderImpl(this, this.updateHelper, evaluator(), refHelper, this.genFilesHelper), - new ClassPathProviderMerger(cpProvider), + LookupMergerSupport.createClassPathProviderMerger(cpProvider), QuerySupport.createCompiledSourceForBinaryQuery(helper, evaluator(), getSourceRoots(), getTestSourceRoots()), QuerySupport.createJavadocForBinaryQuery(helper, evaluator()), new AntArtifactProviderImpl(), --- a/java.project/apichanges.xml Tue May 06 09:24:02 2008 +0200 +++ a/java.project/apichanges.xml Mon May 12 13:57:52 2008 +0200 @@ -106,6 +106,24 @@ + + + + Create LookupMerger implementation for ClassPathProvider + + + + + +

+ LookupMergerSupport.createClassPathProviderMerger(ClassPathProvider) + can be used to allow composing the project's classpath from multiple sources (modules). +

+
+ + +
+ Support for adding/removing relative classpath entries --- a/java.project/manifest.mf Tue May 06 09:24:02 2008 +0200 +++ a/java.project/manifest.mf Mon May 12 13:57:52 2008 +0200 @@ -3,6 +3,6 @@ OpenIDE-Module-Layer: org/netbeans/modules/java/project/layer.xml OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/java/project/Bundle.properties OpenIDE-Module-Needs: javax.script.ScriptEngine.freemarker -OpenIDE-Module-Specification-Version: 1.17 +OpenIDE-Module-Specification-Version: 1.18 AutoUpdate-Show-In-Client: false --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ 9974def0a8cf Mon May 12 13:57:52 2008 +0200 @@ -0,0 +1,282 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2008 Sun Microsystems, Inc. + */ +package org.netbeans.spi.java.project.support; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.spi.java.classpath.ClassPathFactory; +import org.netbeans.spi.java.classpath.ClassPathImplementation; +import org.netbeans.spi.java.classpath.ClassPathProvider; +import org.netbeans.spi.java.classpath.PathResourceImplementation; +import org.netbeans.spi.project.LookupMerger; +import org.openide.filesystems.FileObject; +import org.openide.util.Exceptions; +import org.openide.util.Lookup; +import org.openide.util.LookupEvent; +import org.openide.util.LookupListener; +import org.openide.util.WeakListeners; + +/** + * Lookup Merger implementation for ClassPathProvider + * + * @author Tomas Zezula, Milos Kleint + */ +final class ClassPathProviderMerger implements LookupMerger { + + private final ClassPathProvider defaultProvider; + + ClassPathProviderMerger(final ClassPathProvider defaultProvider) { + assert defaultProvider != null; + this.defaultProvider = defaultProvider; + } + + public Class getMergeableClass() { + return ClassPathProvider.class; + } + + public ClassPathProvider merge(Lookup lookup) { + return new CPProvider(lookup); + } + + private class CPProvider implements ClassPathProvider { + + private final Lookup lookup; + private final Map> cpCache = new HashMap>(); + + public CPProvider(final Lookup lookup) { + assert lookup != null; + this.lookup = lookup; + } + + public ClassPath findClassPath(FileObject file, String type) { + synchronized (cpCache) { + Map cptype = cpCache.get(file); + if (cptype != null) { + ClassPath path = cptype.get(type); + if (path != null) { + return path; + } + } + } + ProxyClassPathImplementation result = new ProxyClassPathImplementation(defaultProvider, lookup, file, type); + ClassPath cp = ClassPathFactory.createClassPath(result); + synchronized (cpCache) { + Map cptype = cpCache.get(file); + if (cptype == null) { + cptype = new HashMap(); + cpCache.put(file, cptype); + } + cptype.put(type, cp); + } + return cp; + } + } + + /** ProxyClassPathImplementation provides read only proxy for ClassPathImplementations. + * The order of the resources is given by the order of its delegates. + * The proxy is designed to be used as a union of class paths. + * E.g. to be able to easily iterate or listen on all design resources = sources + compile resources + */ + public class ProxyClassPathImplementation implements ClassPathImplementation { + + private ClassPathImplementation[] classPaths; + private List resourcesCache; + private ArrayList listeners; + private LookupListener lookupList; + private Lookup.Result providers; + private ClassPathProvider mainProvider; + private PropertyChangeListener classPathsListener; + private FileObject file; + private String type; + + public ProxyClassPathImplementation(ClassPathProvider dominant, Lookup context, FileObject fo, String type) { + assert dominant != null; + this.type = type; + this.file = fo; + mainProvider = dominant; + providers = context.lookupResult(ClassPathProvider.class); + classPathsListener = new DelegatesListener(); + + checkProviders(); + lookupList = new LookupListener() { + public void resultChanged(LookupEvent ev) { + checkProviders(); + } + }; + providers.addLookupListener(lookupList); + + } + + private void checkProviders() { + List impls = new ArrayList(); + ClassPath mainResult = mainProvider.findClassPath(file, type); + if (mainResult != null) { + impls.add(getClassPathImplementation(mainResult)); + } + + for (ClassPathProvider prvd : providers.allInstances()) { + ClassPath path = prvd.findClassPath(file, type); + if (path != null) { + impls.add(getClassPathImplementation(path)); + } + } + for (ClassPathImplementation cpImpl : impls) { + if (cpImpl == null) { + continue; + } + cpImpl.addPropertyChangeListener(WeakListeners.propertyChange(classPathsListener, cpImpl)); + } + synchronized (this) { + if (classPaths != null) { + //TODO how to remove weak listeners + } + this.classPaths = impls.toArray(new ClassPathImplementation[impls.size()]); + } + PropertyChangeEvent ev = new PropertyChangeEvent(this, ClassPathImplementation.PROP_RESOURCES, null, null); + firePropertyChange(ev); + } + + public List getResources() { + synchronized (this) { + if (this.resourcesCache != null) { + return this.resourcesCache; + } + } + + ArrayList result = new ArrayList(classPaths.length * 10); + for (ClassPathImplementation cpImpl : classPaths) { + List subPath = cpImpl.getResources(); + assert subPath != null : "ClassPathImplementation.getResources() returned null. ClassPathImplementation.class: " + cpImpl.getClass().toString() + " ClassPathImplementation: " + cpImpl.toString(); + result.addAll(subPath); + } + + synchronized (this) { + if (this.resourcesCache == null) { + resourcesCache = Collections.unmodifiableList(result); + } + return this.resourcesCache; + } + } + + public synchronized void addPropertyChangeListener(PropertyChangeListener listener) { + if (this.listeners == null) { + this.listeners = new ArrayList(); + } + this.listeners.add(listener); + } + + public synchronized void removePropertyChangeListener(PropertyChangeListener listener) { + if (this.listeners == null) { + return; + } + this.listeners.remove(listener); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("["); //NOI18N + + for (ClassPathImplementation cpImpl : this.classPaths) { + builder.append(cpImpl.toString()); + builder.append(", "); //NOI18N + + } + builder.append("]"); //NOI18N + + return builder.toString(); + } + + private void firePropertyChange(PropertyChangeEvent event) { + PropertyChangeListener[] _listeners; + synchronized (this) { + resourcesCache = null; //Clean the cache + if (listeners == null) { + return; + } + _listeners = listeners.toArray(new PropertyChangeListener[ProxyClassPathImplementation.this.listeners.size()]); + } + for (PropertyChangeListener l : _listeners) { + l.propertyChange(event); + } + } + + private class DelegatesListener implements PropertyChangeListener { + + public void propertyChange(PropertyChangeEvent evt) { + PropertyChangeEvent event = new PropertyChangeEvent(ProxyClassPathImplementation.this, evt.getPropertyName(), null, null); + firePropertyChange(event); + } + } + } + + static Field implField; + static { + try { + implField = ClassPath.class.getDeclaredField("impl"); + implField.setAccessible(true); + } catch (Exception x) { + Exceptions.printStackTrace(x); + } + } + /** + * sort of hack. JavaSupport APIs are meant to stay project free (thus the LookupMerger cannot be placed there). + * But the Java project Support module has no access to the ClassPathAccessor.DEFAULT instance + * to be able to get the ClassPathImplementation out of the ClassPath instance. + * (as the ProxyClassPathImplemntation in Java Support API module does). + */ + static ClassPathImplementation getClassPathImplementation(ClassPath path) { + assert implField != null : "ClassPath.impl field is gone."; + Object toRet = null; + try { + toRet = implField.get(path); + } catch (IllegalArgumentException ex) { + Exceptions.printStackTrace(ex); + } catch (IllegalAccessException ex) { + Exceptions.printStackTrace(ex); + } + return (ClassPathImplementation)toRet; + } +} --- a/java.project/src/org/netbeans/spi/java/project/support/LookupMergerSupport.java Tue May 06 09:24:02 2008 +0200 +++ a/java.project/src/org/netbeans/spi/java/project/support/LookupMergerSupport.java Mon May 12 13:57:52 2008 +0200 @@ -43,6 +43,7 @@ import java.util.Collection; import org.netbeans.api.java.queries.JavadocForBinaryQuery; import org.netbeans.api.java.queries.SourceForBinaryQuery; +import org.netbeans.spi.java.classpath.ClassPathProvider; import org.netbeans.spi.java.queries.JavadocForBinaryQueryImplementation; import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation; import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation2; @@ -77,6 +78,19 @@ public static LookupMerger createJFBLookupMerger() { return new JFBLookupMerger(); } + + /** + * Creates a LookupMerger for ClassPathProviders, allowing multiple instances of ClassPathProviders to reside + * in project's lookup. The merger makes sure the classpaths are merged together. + * When ClassPathProviders appear or disappear in project's lookup, the classpath is updated accordingly. + * @param defaultProvider the default project ClassPathProvider that will always be asked first for classpath. + * @return LookupMerger instance to be put in project's lookup. + * @since org.netbeans.modules.java.project 1.18 + * @see LookupMerger + */ + public static LookupMerger createClassPathProviderMerger(ClassPathProvider defaultProvider) { + return new ClassPathProviderMerger(defaultProvider); + } private static class SFBLookupMerger implements LookupMerger { --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ 9974def0a8cf Mon May 12 13:57:52 2008 +0200 @@ -0,0 +1,179 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2008 Sun Microsystems, Inc. + */ + +package org.netbeans.spi.java.project.support; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.junit.NbTestCase; +import org.netbeans.spi.java.classpath.ClassPathProvider; +import org.netbeans.spi.java.classpath.support.ClassPathSupport; +import org.openide.filesystems.FileObject; +import org.openide.util.Lookup; +import org.openide.util.lookup.AbstractLookup; +import org.openide.util.lookup.InstanceContent; + +/** + * + * @author mkleint + */ +public class ClassPathProviderMergerTest extends NbTestCase { + + public ClassPathProviderMergerTest(String testName) { + super(testName); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + /** + * Test of merge method, of class ClassPathProviderMerger. + */ + public void testMerge() { + System.out.println("merge"); + InstanceContent ic = new InstanceContent(); + Lookup lookup = new AbstractLookup(ic); + ProviderImpl defaultCP = new ProviderImpl(); + //for some weird reason the specific path doesn't work in this module. + // it worked fine in Java Support APIs module before moving here +// URL url = createURLReference("org/netbeans/modules/java/project/"); + URL url = createURLReference(""); + defaultCP.paths.put(ClassPath.COMPILE, ClassPathSupport.createClassPath(url)); + ClassPathProviderMerger instance = new ClassPathProviderMerger(defaultCP); + ClassPathProvider result = instance.merge(lookup); + ClassPath cp = result.findClassPath(null, ClassPath.BOOT); + assertNotNull(cp); + + ClassPath compile = result.findClassPath(null, ClassPath.COMPILE); + assertNotNull(compile); + FileObject[] fos = compile.getRoots(); + assertNotNull(fos); + assertEquals(1, fos.length); + + final List semaphor = new ArrayList(); + compile.addPropertyChangeListener(new PropertyChangeListener() { + + public void propertyChange(PropertyChangeEvent evt) { + semaphor.add(new Object()); + } + }); + + ProviderImpl additional = new ProviderImpl(); + //for some weird reason the specific path doesn't work in this module. + // it worked fine in Java Support APIs module before moving here +// additional.paths.put(ClassPath.COMPILE, ClassPathSupport.createClassPath(createURLReference("org/netbeans/spi/java/project/classpath/"))); +// additional.paths.put(ClassPath.BOOT, ClassPathSupport.createClassPath(createURLReference("org/netbeans/spi/java/project/support/"))); + additional.paths.put(ClassPath.COMPILE, ClassPathSupport.createClassPath(createURLReference(""))); + additional.paths.put(ClassPath.BOOT, ClassPathSupport.createClassPath(createURLReference(""))); + + ic.add(additional); + + fos = compile.getRoots(); + assertNotNull(fos); + assertEquals(2, fos.length); + assertEquals(2, semaphor.size()); // why 2 changes are fired? + + cp = result.findClassPath(null, ClassPath.COMPILE); + assertEquals(cp, compile); + + cp = result.findClassPath(null, ClassPath.BOOT); + assertNotNull(cp); + fos = cp.getRoots(); + assertNotNull(fos); + assertEquals(fos.length, 1); + + + ic.remove(additional); + + fos = compile.getRoots(); + assertNotNull(fos); + assertEquals(1, fos.length); + assertEquals(4, semaphor.size()); // why 2 changes are fired? + + + } + + public void testReflection() throws Exception { + ProviderImpl defaultCP = new ProviderImpl(); + URL url = createURLReference("org/netbeans/modules/java/project/"); + defaultCP.paths.put(ClassPath.COMPILE, ClassPathSupport.createClassPath(url)); + ClassPathProviderMerger instance = new ClassPathProviderMerger(defaultCP); + assertNotNull(instance.implField); + + InstanceContent ic = new InstanceContent(); + Lookup lookup = new AbstractLookup(ic); + ClassPathProvider prov = instance.merge(lookup); + ClassPath cp = prov.findClassPath(null, ClassPath.COMPILE); + assertNotNull(ClassPathProviderMerger.getClassPathImplementation(cp)); + + } + + + private static URL createURLReference(String path) { + URL url = ClassPathProviderMergerTest.class.getClassLoader().getResource(path); + + return url; + } + + private class ProviderImpl implements ClassPathProvider { + + public Map paths = new HashMap(); + + public ClassPath findClassPath(FileObject file, String type) { + return paths.get(type); + } + + } + + +} +