--- a/projectapi/manifest.mf Fri Nov 21 15:32:55 2008 +0100 +++ a/projectapi/manifest.mf Fri Nov 21 18:08:30 2008 +0100 @@ -1,7 +1,7 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.modules.projectapi/1 OpenIDE-Module-Install: org/netbeans/modules/projectapi/Installer.class -OpenIDE-Module-Specification-Version: 1.21 +OpenIDE-Module-Specification-Version: 1.22 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/projectapi/Bundle.properties OpenIDE-Module-Layer: org/netbeans/modules/projectapi/layer.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ 7814471c9767 Fri Nov 21 18:08:30 2008 +0100 @@ -0,0 +1,110 @@ +/* + * 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.modules.projectapi; + +import java.util.List; +import java.util.Set; +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.util.ElementFilter; +import org.netbeans.spi.project.ProjectFactory; +import org.openide.filesystems.annotations.LayerBuilder.File; +import org.openide.filesystems.annotations.LayerGeneratingProcessor; +import org.openide.filesystems.annotations.LayerGenerationException; +import org.openide.util.lookup.ServiceProvider; + +/** Process ProjectFactory registrations. + * + * @author Jaroslav Tulach + */ +@ServiceProvider(service=Processor.class) +@SupportedSourceVersion(SourceVersion.RELEASE_6) +@SupportedAnnotationTypes("org.netbeans.spi.project.ProjectFactory.Registration") // NOI18N +public class ProjectFactoryAnnotationProcessor extends LayerGeneratingProcessor { + + @Override + protected boolean handleProcess(Set annotations, RoundEnvironment roundEnv) throws LayerGenerationException { + if (roundEnv.processingOver()) { + return false; + } + for (Element e : roundEnv.getElementsAnnotatedWith(ProjectFactory.Registration.class)) { + ProjectFactory.Registration registration = e.getAnnotation(ProjectFactory.Registration.class); + TypeElement clazz = (TypeElement) e; + int constructorCount = 0; + for (ExecutableElement constructor : ElementFilter.constructorsIn(clazz.getEnclosedElements())) { + if (!constructor.getModifiers().contains(Modifier.PUBLIC)) { + continue; + } + List params = constructor.getParameters(); + if (params.size() == 0) { + constructorCount++; + break; + } + constructorCount++; + } + if (constructorCount == 0) { + throw new LayerGenerationException("No default constructor in " + clazz); // NOI18N + } + + String binName = processingEnv.getElementUtils().getBinaryName(clazz).toString(); + File file = layer(e).file("Services/ProjectFactories/" + binName.replace('.', '-') + ".instance"). // NOI18N + methodvalue("instanceCreate", ProjectFactoryFactory.class.getName(), "create"). // NOI18N + stringvalue("instanceOf", ProjectFactory.class.getName()). // NOI18N + newvalue("delegate", binName); // NOI18N + String[] requiredFiles = registration.requiredFiles(); + if (requiredFiles.length > 0) { + for (int i = 0; i < requiredFiles.length; i++) { + file = file.stringvalue("file." + i, requiredFiles[i]); // NOI18N + } + } + file.write(); + } + return true; + } + +} --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ 7814471c9767 Fri Nov 21 18:08:30 2008 +0100 @@ -0,0 +1,106 @@ +/* + * 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.modules.projectapi; + +import java.io.IOException; +import java.util.Map; +import org.netbeans.api.project.Project; +import org.netbeans.spi.project.ProjectFactory; +import org.netbeans.spi.project.ProjectState; +import org.openide.filesystems.FileObject; + +/** Slightly optimized factory that allows to specify important files that + * need to be present for this factory to even consider loading the project. + * + * @author Jaroslav Tulach + */ +public final class ProjectFactoryFactory implements ProjectFactory { + private final Map map; + private int max = Integer.MAX_VALUE; + + private ProjectFactoryFactory(Map map) { + this.map = map; + } + + public static ProjectFactory create(Map map) { + return new ProjectFactoryFactory(map); + } + + public void saveProject(Project project) throws IOException, ClassCastException { + getDelegate().saveProject(project); + } + + public Project loadProject(FileObject projectDirectory, ProjectState state) throws IOException { + if (canBeProject(projectDirectory)) { + return getDelegate().loadProject(projectDirectory, state); + } else { + return null; + } + } + + private boolean canBeProject(FileObject projectDirectory) { + for (int i = 0; i < max; i++) { + Object path = map.get("file." + i); // NOI18N + if (path instanceof String) { + String p = (String)path; + if (projectDirectory.getFileObject(p) == null) { + return false; + } + } else { + max = i; + break; + } + } + return true; + } + + public boolean isProject(FileObject projectDirectory) { + return canBeProject(projectDirectory) && getDelegate().isProject(projectDirectory); + } + + /** + * @return the delegate + */ + public ProjectFactory getDelegate() { + return (ProjectFactory)map.get("delegate"); // NOI18N + } + + +} --- a/projectapi/src/org/netbeans/spi/project/ProjectFactory.java Fri Nov 21 15:32:55 2008 +0100 +++ a/projectapi/src/org/netbeans/spi/project/ProjectFactory.java Fri Nov 21 18:08:30 2008 +0100 @@ -42,6 +42,10 @@ package org.netbeans.spi.project; import java.io.IOException; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.netbeans.api.project.Project; import org.openide.filesystems.FileObject; @@ -89,5 +93,14 @@ * @throws ClassCastException if this factory did not create this project */ void saveProject(Project project) throws IOException, ClassCastException; - + + /** + * annotation to register {@link ProjectFactory} instances. + * @since org.netbeans.modules.projectapi 1.22 + */ + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.SOURCE) + @interface Registration { + String[] requiredFiles() default {}; + } } --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ 7814471c9767 Fri Nov 21 18:08:30 2008 +0100 @@ -0,0 +1,363 @@ +/* + * 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): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun + * Microsystems, Inc. All Rights Reserved. + * + * 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. + */ + +package org.netbeans.api.project; + +import java.io.IOException; +import org.netbeans.modules.projectapi.*; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.net.URI; +import java.util.Collection; +import org.netbeans.junit.NbTestCase; +import org.netbeans.spi.project.ProjectFactory; +import org.netbeans.spi.project.ProjectFactory.Registration; +import org.netbeans.spi.project.ProjectState; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.filesystems.test.TestFileUtils; +import org.openide.util.Lookup; +import org.openide.util.lookup.Lookups; +import org.openide.util.test.MockLookup; + +/** + * Test functionality of FileOwnerQuery. + * @author Jesse Glick + */ +public class ProjectFactoryRegistrationTest extends NbTestCase { + + public ProjectFactoryRegistrationTest(String name) { + super(name); + } + + static { + TimedWeakReference.TIMEOUT = 0; + } + + private FileObject scratch; + private FileObject projdir; + private FileObject randomfile; + private FileObject projfile; + private FileObject projfile2; + private FileObject subprojdir; + private FileObject subprojfile; + private FileObject hashedFile; + private Project p; + private FileObject zippedfile; + + protected @Override void setUp() throws Exception { + DeclarativeTestFactory.active = true; + MockLookup.init(); + Collection factories = Lookups.forPath("Services").lookupAll(ProjectFactory.class); + assertEquals("One is registered: " + factories, 1, factories.size()); + MockLookup.setInstances(factories.toArray()); + DeclarativeTestFactory.called = 0; + + ProjectManager.getDefault().reset(); + FileOwnerQuery.reset(); + scratch = TestUtil.makeScratchDir(this); + projdir = scratch.createFolder("my-project"); + projdir.createFolder("testproject"); + randomfile = scratch.createData("randomfile"); + projfile = projdir.createData("projfile"); + FileObject projsubdir = projdir.createFolder("projsubdir"); + projfile2 = projsubdir.createData("projfile2"); + subprojdir = projdir.createFolder("subproject"); + subprojdir.createFolder("testproject"); + subprojfile = subprojdir.createData("subprojfile"); + scratch.createFolder("external1").createFolder("subdir").createData("file"); + scratch.createFolder("external2").createFolder("subdir").createData("file"); + FileObject wrongDir = scratch.createFolder("external3").createFolder("subproject").createFolder("testproject"); + p = ProjectManager.getDefault().findProject(wrongDir); + assertNull("No project found", p); + assertEquals("No calls to our factory yet", 0, DeclarativeTestFactory.called); + p = ProjectManager.getDefault().findProject(projdir); + assertNotNull("found a project successfully", p); + assertEquals("One call to our factory", 1, DeclarativeTestFactory.called); + + // make jar:file:/.../projdir/foo.jar!/zipfile/zippedfile + FileObject foojar = TestFileUtils.writeZipFile(projdir, "foo.jar", "zipdir/zippedfile:"); + FileObject foojarRoot = FileUtil.getArchiveRoot(foojar); + assertNotNull("have an archive in " + foojar, foojarRoot); + zippedfile = foojarRoot.getFileObject("zipdir/zippedfile"); + assertNotNull("zippedfile found in it", zippedfile); + + hashedFile = TestFileUtils.writeZipFile(projdir, ".#webapp.jar.1.45", "zipdir/zippedfile:"); + foojarRoot = FileUtil.getArchiveRoot(hashedFile); + assertNotNull("have an archive in " + hashedFile, foojarRoot); + hashedFile = foojarRoot.getFileObject("zipdir/zippedfile"); + + } + + protected @Override void tearDown() throws Exception { + scratch = null; + projdir = null; + randomfile = null; + projfile = null; + p = null; + } + + public void testFileOwner() throws Exception { + assertEquals("correct project from projfile FileObject", p, FileOwnerQuery.getOwner(projfile)); + URI u = FileUtil.toFile(projfile).toURI(); + assertEquals("correct project from projfile URI " + u, p, FileOwnerQuery.getOwner(u)); + assertEquals("correct project from projfile2 FileObject", p, FileOwnerQuery.getOwner(projfile2)); + assertEquals("correct project from projfile2 URI", p, FileOwnerQuery.getOwner(FileUtil.toFile(projfile2).toURI())); + assertEquals("correct project from projdir FileObject", p, FileOwnerQuery.getOwner(projdir)); + assertEquals("correct project from projdir URI", p, FileOwnerQuery.getOwner(FileUtil.toFile(projdir).toURI())); + // Check that it loads the project even though we have not touched it yet: + Project p2 = FileOwnerQuery.getOwner(subprojfile); + Project subproj = ProjectManager.getDefault().findProject(subprojdir); + assertEquals("correct project from subprojdir FileObject", subproj, p2); + assertEquals("correct project from subprojdir URI", subproj, FileOwnerQuery.getOwner(FileUtil.toFile(subprojdir).toURI())); + assertEquals("correct project from subprojfile FileObject", subproj, FileOwnerQuery.getOwner(subprojfile)); + assertEquals("correct project from subprojfile URI", subproj, FileOwnerQuery.getOwner(FileUtil.toFile(subprojfile).toURI())); + assertEquals("no project from randomfile FileObject", null, FileOwnerQuery.getOwner(randomfile)); + assertEquals("no project from randomfile URI", null, FileOwnerQuery.getOwner(FileUtil.toFile(randomfile).toURI())); + assertEquals("no project in C:\\", null, FileOwnerQuery.getOwner(URI.create("file:/C:/"))); + } + + public void testJarOwners() throws Exception { + assertEquals("correct owner of a ZIPped file", p, FileOwnerQuery.getOwner(zippedfile)); + assertEquals("correct owner of a ZIPped file URL", p, FileOwnerQuery.getOwner(URI.create(zippedfile.getURL().toExternalForm()))); + assertEquals("correct owner of a ZIPped file", p, FileOwnerQuery.getOwner(hashedFile)); + assertEquals("correct owner of a ZIPped file URL", p, FileOwnerQuery.getOwner(URI.create(hashedFile.getURL().toExternalForm()))); + } + + public void testExternalOwner() throws Exception { + FileObject ext1 = scratch.getFileObject("external1"); + FileObject extfile1 = ext1.getFileObject("subdir/file"); + assertEquals("no owner yet", null, FileOwnerQuery.getOwner(extfile1)); + FileOwnerQuery.markExternalOwner(ext1, p, FileOwnerQuery.EXTERNAL_ALGORITHM_TRANSIENT); + assertEquals("now have an owner", p, FileOwnerQuery.getOwner(extfile1)); + assertEquals("even for the projdir", p, FileOwnerQuery.getOwner(ext1)); + assertEquals("but not for something else", null, FileOwnerQuery.getOwner(scratch)); + FileObject ext2 = scratch.getFileObject("external2"); + FileObject extfile2 = ext2.getFileObject("subdir/file"); + assertEquals("no owner yet", null, FileOwnerQuery.getOwner(extfile2)); + Project p2 = ProjectManager.getDefault().findProject(subprojdir); + FileOwnerQuery.markExternalOwner(ext2, p2, FileOwnerQuery.EXTERNAL_ALGORITHM_TRANSIENT); + assertEquals("now have an owner", p2, FileOwnerQuery.getOwner(extfile2)); + assertEquals("even for the projdir", p2, FileOwnerQuery.getOwner(ext2)); + assertEquals("but not for something else", null, FileOwnerQuery.getOwner(scratch)); + assertEquals("still correct for first proj", p, FileOwnerQuery.getOwner(extfile1)); + FileObject ext3 = scratch.getFileObject("external3"); + assertEquals("no owner yet", null, FileOwnerQuery.getOwner(ext3)); + FileOwnerQuery.markExternalOwner(ext3, p, FileOwnerQuery.EXTERNAL_ALGORITHM_TRANSIENT); + assertEquals("now have an owner", p, FileOwnerQuery.getOwner(ext3)); + FileObject ext3subproj = ext3.getFileObject("subproject"); + Project p3 = FileOwnerQuery.getOwner(ext3subproj); + assertNotSame("different project", p, p3); + assertEquals("but subprojects are not part of it", ProjectManager.getDefault().findProject(ext3subproj), p3); + FileOwnerQuery.markExternalOwner(ext3, null, FileOwnerQuery.EXTERNAL_ALGORITHM_TRANSIENT); + assertEquals("unmarking an owner works", null, FileOwnerQuery.getOwner(ext3)); + } + + public void testExternalOwnerFile() throws Exception { + FileObject ext1 = scratch.getFileObject("external1"); + FileObject extfile1 = ext1.getFileObject("subdir/file"); + assertEquals("no owner yet", null, FileOwnerQuery.getOwner(extfile1)); + FileOwnerQuery.markExternalOwner(extfile1, p, FileOwnerQuery.EXTERNAL_ALGORITHM_TRANSIENT); + assertEquals("now have an owner", p, FileOwnerQuery.getOwner(extfile1)); + assertEquals("not for the projdir", null, FileOwnerQuery.getOwner(ext1)); + assertEquals("and not for something else", null, FileOwnerQuery.getOwner(scratch)); + FileObject ext2 = scratch.getFileObject("external2"); + FileObject extfile2 = ext2.getFileObject("subdir/file"); + assertEquals("no owner yet", null, FileOwnerQuery.getOwner(extfile2)); + Project p2 = ProjectManager.getDefault().findProject(subprojdir); + FileOwnerQuery.markExternalOwner(extfile2, p2, FileOwnerQuery.EXTERNAL_ALGORITHM_TRANSIENT); + assertEquals("now have an owner", p2, FileOwnerQuery.getOwner(extfile2)); + assertEquals("not for the projdir", null, FileOwnerQuery.getOwner(ext2)); + assertEquals("and not for something else", null, FileOwnerQuery.getOwner(scratch)); + assertEquals("still correct for first proj", p, FileOwnerQuery.getOwner(extfile1)); + + //XXX: unmarking files. + } + + public void testExternalOwnerURI() throws Exception { + FileObject ext1 = scratch.getFileObject("external1"); + FileObject extfile1 = ext1.getFileObject("subdir/file"); + assertEquals("no owner yet through FileObjects", null, FileOwnerQuery.getOwner(extfile1)); + assertEquals("no owner yet through URI", null, FileOwnerQuery.getOwner(extfile1)); + FileOwnerQuery.markExternalOwner(fileObject2URI(ext1), p, FileOwnerQuery.EXTERNAL_ALGORITHM_TRANSIENT); + assertEquals("now have an owner through FileObjects", p, FileOwnerQuery.getOwner(extfile1)); + assertEquals("now have an owner through URI", p, FileOwnerQuery.getOwner(fileObject2URI(extfile1))); + assertEquals("even for the projdir throught FileObjects", p, FileOwnerQuery.getOwner(ext1)); + assertEquals("even for the projdir throught URI", p, FileOwnerQuery.getOwner(fileObject2URI(ext1))); + assertEquals("but not for something else throught FileObjects", null, FileOwnerQuery.getOwner(scratch)); + assertEquals("but not for something else throught URI", null, FileOwnerQuery.getOwner(fileObject2URI(scratch))); + FileObject ext2 = scratch.getFileObject("external2"); + FileObject extfile2 = ext2.getFileObject("subdir/file"); + assertEquals("no owner yet through FileObjects", null, FileOwnerQuery.getOwner(extfile2)); + assertEquals("no owner yet through URI", null, FileOwnerQuery.getOwner(fileObject2URI(extfile2))); + Project p2 = ProjectManager.getDefault().findProject(subprojdir); + FileOwnerQuery.markExternalOwner(fileObject2URI(ext2), p2, FileOwnerQuery.EXTERNAL_ALGORITHM_TRANSIENT); + assertEquals("now have an owner through FileObjects", p2, FileOwnerQuery.getOwner(extfile2)); + assertEquals("now have an owner through URI", p2, FileOwnerQuery.getOwner(fileObject2URI(extfile2))); + assertEquals("even for the projdir through FileObjects", p2, FileOwnerQuery.getOwner(ext2)); + assertEquals("even for the projdir through URI", p2, FileOwnerQuery.getOwner(ext2)); + assertEquals("but not for something else through FileObjects", null, FileOwnerQuery.getOwner(scratch)); + assertEquals("but not for something else through URI", null, FileOwnerQuery.getOwner(fileObject2URI(scratch))); + assertEquals("still correct for first proj through FileObjects", p, FileOwnerQuery.getOwner(extfile1)); + assertEquals("still correct for first proj through URI", p, FileOwnerQuery.getOwner(fileObject2URI(extfile1))); + FileObject ext3 = scratch.getFileObject("external3"); + assertEquals("no owner yet through FileObjects", null, FileOwnerQuery.getOwner(ext3)); + assertEquals("no owner yet through URI", null, FileOwnerQuery.getOwner(fileObject2URI(ext3))); + FileOwnerQuery.markExternalOwner(fileObject2URI(ext3), p, FileOwnerQuery.EXTERNAL_ALGORITHM_TRANSIENT); + assertEquals("now have an owner through FileObjects", p, FileOwnerQuery.getOwner(ext3)); + assertEquals("now have an owner through URI", p, FileOwnerQuery.getOwner(fileObject2URI(ext3))); + FileObject ext3subproj = ext3.getFileObject("subproject"); + Project p3 = FileOwnerQuery.getOwner(ext3subproj); + assertNotSame("different project", p, p3); + assertEquals("but subprojects are not part of it", ProjectManager.getDefault().findProject(ext3subproj), p3); + FileOwnerQuery.markExternalOwner(fileObject2URI(ext3), null, FileOwnerQuery.EXTERNAL_ALGORITHM_TRANSIENT); + assertEquals("unmarking an owner works through FileObjects", null, FileOwnerQuery.getOwner(ext3)); + assertEquals("unmarking an owner works through URI", null, FileOwnerQuery.getOwner(fileObject2URI(ext3))); + } + + public void testExternalOwnerFileURI() throws Exception { + FileObject ext1 = scratch.getFileObject("external1"); + FileObject extfile1 = ext1.getFileObject("subdir/file"); + assertEquals("no owner yet through FileObjects", null, FileOwnerQuery.getOwner(extfile1)); + assertEquals("no owner yet through URI", null, FileOwnerQuery.getOwner(fileObject2URI(extfile1))); + FileOwnerQuery.markExternalOwner(fileObject2URI(extfile1), p, FileOwnerQuery.EXTERNAL_ALGORITHM_TRANSIENT); + assertEquals("now have an owner through FileObjects", p, FileOwnerQuery.getOwner(extfile1)); + assertEquals("now have an owner through URI", p, FileOwnerQuery.getOwner(fileObject2URI(extfile1))); + assertEquals("not for the projdir through FileObjects", null, FileOwnerQuery.getOwner(ext1)); + assertEquals("not for the projdir through URI", null, FileOwnerQuery.getOwner(fileObject2URI(ext1))); + assertEquals("and not for something else through FileObjects", null, FileOwnerQuery.getOwner(scratch)); + assertEquals("and not for something else through URI", null, FileOwnerQuery.getOwner(fileObject2URI(scratch))); + FileObject ext2 = scratch.getFileObject("external2"); + FileObject extfile2 = ext2.getFileObject("subdir/file"); + assertEquals("no owner yet through FileObjects", null, FileOwnerQuery.getOwner(extfile2)); + assertEquals("no owner yet through URI", null, FileOwnerQuery.getOwner(fileObject2URI(extfile2))); + Project p2 = ProjectManager.getDefault().findProject(subprojdir); + FileOwnerQuery.markExternalOwner(fileObject2URI(extfile2), p2, FileOwnerQuery.EXTERNAL_ALGORITHM_TRANSIENT); + assertEquals("now have an owner through FileObjects", p2, FileOwnerQuery.getOwner(extfile2)); + assertEquals("now have an owner through URI", p2, FileOwnerQuery.getOwner(fileObject2URI(extfile2))); + assertEquals("not for the projdir through FileObjects", null, FileOwnerQuery.getOwner(ext2)); + assertEquals("not for the projdir through URI", null, FileOwnerQuery.getOwner(fileObject2URI(ext2))); + assertEquals("and not for something else through FileObjects", null, FileOwnerQuery.getOwner(scratch)); + assertEquals("and not for something else through URI", null, FileOwnerQuery.getOwner(fileObject2URI(scratch))); + assertEquals("still correct for first proj through FileObjects", p, FileOwnerQuery.getOwner(extfile1)); + assertEquals("still correct for first proj through URI", p, FileOwnerQuery.getOwner(fileObject2URI(extfile1))); + + //XXX: unmarking files. + } + + public void testIsProjectDirCollectible() throws Exception { + Project p2 = ProjectManager.getDefault().findProject(subprojdir); + FileObject root = p2.getProjectDirectory(); + FileObject ext2 = scratch.getFileObject("external2"); + FileObject extfile2 = ext2.getFileObject("subdir/file"); + + FileOwnerQuery.markExternalOwner(fileObject2URI(extfile2), p2, FileOwnerQuery.EXTERNAL_ALGORITHM_TRANSIENT); + + Reference p2WR = new WeakReference(p2); + Reference rootWR = new WeakReference(root); + + p2 = null; + root = null; + ext2 = null; + extfile2 = null; + subprojdir = null; + subprojfile = null; + + assertGC("project 2 collected", p2WR); + assertGC("project 2's project dir collected", rootWR); + } + + + /** + * Tests the issue 60297. GC causes previosly registered extenral roots + * for project to be released. Only one extenral root per project is kept. + * + */ + public void testIssue60297 () throws Exception { + FileObject ext1 = scratch.getFileObject("external1"); + assertEquals("no owner yet", null, FileOwnerQuery.getOwner(ext1)); + FileOwnerQuery.markExternalOwner(ext1, p, FileOwnerQuery.EXTERNAL_ALGORITHM_TRANSIENT); + assertEquals("now have an owner", p, FileOwnerQuery.getOwner(ext1)); + FileObject ext2 = scratch.getFileObject("external2"); + assertEquals("no owner yet", null, FileOwnerQuery.getOwner(ext2)); + FileOwnerQuery.markExternalOwner(ext2, p, FileOwnerQuery.EXTERNAL_ALGORITHM_TRANSIENT); + System.gc(); + assertEquals("now have an owner", p, FileOwnerQuery.getOwner(ext2)); + assertEquals("still correct for the first external root", p, FileOwnerQuery.getOwner(ext1)); + } + + private static URI fileObject2URI(FileObject f) throws Exception { + return URI.create(f.getURL().toString()); + } + + + @Registration(requiredFiles="testproject") + public static final class DeclarativeTestFactory extends TestUtil.TestProjectFactory { + static boolean active; + static int called; + + @Override + public boolean isProject(FileObject dir) { + if (!active) { + return false; + } + called++; + return super.isProject(dir); + } + + @Override + public Project loadProject(FileObject projectDirectory, ProjectState state) throws IOException { + if (!active) { + return null; + } + called++; + return super.loadProject(projectDirectory, state); + } + + @Override + public void saveProject(Project project) throws IOException, ClassCastException { + if (active) { + super.saveProject(project); + } + } + + } +} --- a/projectapi/test/unit/src/org/netbeans/api/project/TestUtil.java Fri Nov 21 15:32:55 2008 +0100 +++ a/projectapi/test/unit/src/org/netbeans/api/project/TestUtil.java Fri Nov 21 18:08:30 2008 +0100 @@ -190,7 +190,7 @@ */ public static Lookup LOOKUP = null; - private static final class TestProjectFactory implements ProjectFactory { + static class TestProjectFactory implements ProjectFactory { TestProjectFactory() {}