diff -r f4a875afac55 projectui/src/org/netbeans/modules/project/ui/OpenProjectList.java --- a/projectui/src/org/netbeans/modules/project/ui/OpenProjectList.java Wed Dec 03 10:10:50 2008 +0100 +++ b/projectui/src/org/netbeans/modules/project/ui/OpenProjectList.java Wed Dec 03 10:05:49 2008 +0100 @@ -91,6 +91,7 @@ import org.netbeans.modules.project.uiapi.ProjectOpenedTrampoline; import org.netbeans.spi.project.SubprojectProvider; import org.netbeans.spi.project.ui.PrivilegedTemplates; +import org.netbeans.spi.project.ui.ProjectInitializationHook; import org.netbeans.spi.project.ui.ProjectOpenedHook; import org.netbeans.spi.project.ui.RecommendedTemplates; import org.openide.ErrorManager; @@ -220,6 +221,30 @@ } } return wrap; + } + + private static Project doInitializeProject(Project p) { + ClassLoader l = ProjectOpenedHook.class.getClassLoader(); + try { + Class.forName(ProjectOpenedHook.class.getName(), true, l); + } catch (ClassNotFoundException ex) { + Exceptions.printStackTrace(ex); + } + INIT: for (;;) { + Collection initHooks = p.getLookup().lookupAll(ProjectInitializationHook.class); + assert initHooks.size() <= 1 : "There can be at most one hook " + initHooks; + for (ProjectInitializationHook h : initHooks) { + Project np = ProjectOpenedTrampoline.DEFAULT.projectWillBeOpened(h); + if (np == null) { + return np; + } + if (np != p) { + p = np; + continue INIT; + } + } + return p; + } } /** Modifications to the recentTemplates variables shall be done only @@ -352,6 +377,12 @@ } p = toOpenProjects.remove(); LOGGER.log(Level.FINER, "after remove {0}", toOpenProjects); // NOI18N + } + LOGGER.log(Level.FINE, "about to init a project {0}", p); // NOI18N + p = doInitializeProject(p); + if (p == null) { + LOGGER.log(Level.FINE, "project shall not be opened"); // NOI18N + continue; } LOGGER.log(Level.FINE, "about to open a project {0}", p); // NOI18N if (notifyOpened(p)) { @@ -1031,9 +1062,14 @@ } } - private boolean doOpenProject(final Project p) { + private boolean doOpenProject(Project p) { boolean recentProjectsChanged; LOGGER.finer("doOpenProject(): opening project " + p.toString()); + p = doInitializeProject(p); + if (p == null) { + return false; + } + synchronized (this) { if (openProjects.contains(p)) { return false; @@ -1049,12 +1085,12 @@ logProjects("doOpenProject(): openProjects == ", openProjects.toArray(new Project[0])); // NOI18N // Notify projects opened notifyOpened(p); - + + final Project openFilesFor = p; Mutex.EVENT.readAccess(new Action() { public Void run() { // Open project files - ProjectUtilities.openProjectFiles(p); - + ProjectUtilities.openProjectFiles(openFilesFor); return null; } }); diff -r f4a875afac55 projectui/test/unit/src/org/netbeans/modules/project/ui/OpenProjectInitializeTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/projectui/test/unit/src/org/netbeans/modules/project/ui/OpenProjectInitializeTest.java Wed Dec 03 10:05:50 2008 +0100 @@ -0,0 +1,274 @@ +/* + * 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.modules.project.ui; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Level; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.ProjectManager; +import org.netbeans.api.project.ui.OpenProjects; +import org.netbeans.junit.MockServices; +import org.netbeans.junit.NbTestCase; +import org.netbeans.spi.project.ProjectFactory; +import org.netbeans.spi.project.ProjectState; +import org.netbeans.spi.project.ui.ProjectInitializationHook; +import org.netbeans.spi.project.ui.ProjectOpenedHook; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.Exceptions; +import org.openide.util.Lookup; +import org.openide.util.RequestProcessor; +import org.openide.util.lookup.AbstractLookup; +import org.openide.util.lookup.InstanceContent; + +/** Verify ProjectInitializationHook is called. + * + * @author Jaroslav Tulach + */ +public class OpenProjectInitializeTest extends NbTestCase { + FileObject f1_1_open, f1_2_open, f1_3_close; + FileObject f2_1_open; + + Project project1; + + public OpenProjectInitializeTest (String testName) { + super (testName); + } + + @Override + protected Level logLevel() { + return Level.FINE; + } + + @Override + protected void setUp () throws Exception { + super.setUp (); + MockServices.setServices(MyFactory.class); + clearWorkDir (); + + FileObject workDir = FileUtil.toFileObject (getWorkDir ()); + + FileObject p1 = workDir.createFolder("prjDir"); + f1_1_open = p1.createData("f1_1.java"); + f1_2_open = p1.createData("f1_2.java"); + f1_3_close = p1.createData("f1_3.java"); + + MyFactory.toRet = new MyProject(p1); + project1 = ProjectManager.getDefault ().findProject (p1); + assertNotNull("Project found", project1); + + assertEquals("No open prjs", 0, OpenProjects.getDefault().openProjects().get().length); + } + + @Override + protected void tearDown () throws Exception { + OpenProjectList.getDefault().close(OpenProjectList.getDefault().getOpenProjects(), false); + assertEquals("No open prjs", 0, OpenProjects.getDefault().openProjects().get().length); + } + + public void testOpenWithOneRedirect() throws Exception { + assertTrue ("No project is open.", OpenProjectList.getDefault ().getOpenProjects ().length == 0); + + MyProject project2 = new MyProject(project1.getProjectDirectory()); + + InitHook h = new InitHook(project2); + MyProject tp = (MyProject)project1; + h.stateToClose = tp.state; + tp.ic.add(h); + + OpenProjectList.getDefault ().open(tp, true); + + Project[] open = OpenProjectList.getDefault().getOpenProjects(); + assertEquals("One is open", 1, open.length); + assertEquals("Redirect gets opened", project2, open[0]); + assertEquals("Its open hook is called", 1, project2.openHook.opened); + assertEquals("Open hook of project1 ignored", 0, tp.openHook.opened); + } + + public void testOpenWithTwoRedirect() throws Exception { + assertTrue ("No project is open.", OpenProjectList.getDefault ().getOpenProjects ().length == 0); + + MyProject project2 = new MyProject(project1.getProjectDirectory()); + MyProject project3 = new MyProject(project1.getProjectDirectory()); + + InitHook h = new InitHook(project2); + InitHook h2 = new InitHook(project3); + MyProject tp = (MyProject)project1; + h2.stateToClose = tp.state; + tp.ic.add(h2); + project2.ic.add(h2); + + OpenProjectList.getDefault ().open (tp, true); + + Project[] open = OpenProjectList.getDefault().getOpenProjects(); + assertEquals("One is open", 1, open.length); + assertEquals("Project3 gets opened", project3, open[0]); + assertEquals("Project3 open hook is called", 1, project3.openHook.opened); + assertEquals("Project2 open hook is ignored", 0, project2.openHook.opened); + assertEquals("Open hook of project1 ignored", 0, tp.openHook.opened); + } + + public void testNullOpensNoProject() throws Exception { + assertTrue ("No project is open.", OpenProjectList.getDefault ().getOpenProjects ().length == 0); + + InitHook h = new InitHook(null); + MyProject tp = (MyProject)project1; + tp.ic.add(h); + + OpenProjectList.getDefault ().open (tp, true); + + Project[] open = OpenProjectList.getDefault().getOpenProjects(); + assertEquals("Nothing is open", 0, open.length); + assertEquals("Open hook of project1 ignored", 0, tp.openHook.opened); + } + + public static class MyFactory implements ProjectFactory { + static MyProject toRet; + + public boolean isProject(FileObject d) { + return toRet != null && toRet.getProjectDirectory().equals(d); + } + + public Project loadProject(FileObject projectDirectory, ProjectState state) throws IOException { + if (toRet != null && projectDirectory.equals(toRet.getProjectDirectory())) { + MyProject h = toRet; + h.state = state; + toRet = null; + return h; + } + return null; + } + + public void saveProject(Project project) throws IOException, ClassCastException { + } + } // end of MyFactory + + + + private static class MyProject implements Project { + final FileObject fo; + final Lookup lookup; + final InstanceContent ic; + final OpenHook openHook; + ProjectState state; + + public MyProject(FileObject fo) { + this.fo = fo; + this.ic = new InstanceContent(); + this.lookup = new AbstractLookup(ic); + this.openHook = new OpenHook(); + ic.add(openHook); + } + + public FileObject getProjectDirectory() { + return fo; + } + + public Lookup getLookup() { + return lookup; + } + } // end of MyProject + private static class InitHook extends ProjectInitializationHook { + MyProject willBeOpened; + ProjectState stateToClose; + public InitHook(MyProject next) { + willBeOpened = next; + } + + @Override + protected Project projectWillBeOpened() { + if (stateToClose != null) { + stateToClose.notifyDeleted(); + stateToClose = null; + } + if (willBeOpened == null) { + return null; + } + try { + MyFactory.toRet = willBeOpened; + Project found = ProjectManager.getDefault().findProject(willBeOpened.getProjectDirectory()); + assertEquals("The right one found", willBeOpened, found); + return found; + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } catch (IllegalArgumentException ex) { + Exceptions.printStackTrace(ex); + } + return null; + } + } // end of InitHook + private static class OpenHook extends ProjectOpenedHook + implements Runnable { + int opened = 0; + int closed = 0; + + private Object result; + + protected void projectClosed() { + closed++; + assertFalse("Working on", OpenProjects.getDefault().openProjects().isDone()); + RequestProcessor.getDefault().post(this).waitFinished(); + assertNotNull("some result computed", result); + assertEquals("It is time out exception", TimeoutException.class, result.getClass()); + } + + protected void projectOpened() { + opened++; + assertFalse("Working on", OpenProjects.getDefault().openProjects().isDone()); + RequestProcessor.getDefault().post(this).waitFinished(); + assertNotNull("some result computed", result); + assertEquals("It is time out exception", TimeoutException.class, result.getClass()); + } + + public void run() { + try { + result = OpenProjects.getDefault().openProjects().get(100, TimeUnit.MILLISECONDS); + } catch (Exception ex) { + result = ex; + } + } + } + + +} diff -r f4a875afac55 projectuiapi/apichanges.xml --- a/projectuiapi/apichanges.xml Wed Dec 03 10:10:50 2008 +0100 +++ b/projectuiapi/apichanges.xml Wed Dec 03 10:05:50 2008 +0100 @@ -104,6 +104,23 @@ + + + Add ProjectInitializationHook + + + + + +

+ ProjectInitializationHook can be provided by Project's + to be notified that the project is about to become opened and + prevent that by finding proper substitute. +

+
+ + +
Add annotation @NodeFactory.Registration diff -r f4a875afac55 projectuiapi/nbproject/project.properties --- a/projectuiapi/nbproject/project.properties Wed Dec 03 10:10:50 2008 +0100 +++ b/projectuiapi/nbproject/project.properties Wed Dec 03 10:05:50 2008 +0100 @@ -39,7 +39,7 @@ javac.compilerargs=-Xlint -Xlint:-serial javac.source=1.5 -spec.version.base=1.33.0 +spec.version.base=1.34.0 is.autoload=true javadoc.arch=${basedir}/arch.xml javadoc.apichanges=${basedir}/apichanges.xml diff -r f4a875afac55 projectuiapi/src/org/netbeans/modules/project/uiapi/ProjectOpenedTrampoline.java --- a/projectuiapi/src/org/netbeans/modules/project/uiapi/ProjectOpenedTrampoline.java Wed Dec 03 10:10:50 2008 +0100 +++ b/projectuiapi/src/org/netbeans/modules/project/uiapi/ProjectOpenedTrampoline.java Wed Dec 03 10:05:50 2008 +0100 @@ -41,6 +41,8 @@ package org.netbeans.modules.project.uiapi; +import org.netbeans.api.project.Project; +import org.netbeans.spi.project.ui.ProjectInitializationHook; import org.netbeans.spi.project.ui.ProjectOpenedHook; /** @@ -62,6 +64,9 @@ /** Used by {@link ProjectOpenedHook}. */ protected ProjectOpenedTrampoline() {} + + /** Delegates to {@link ProjectInitializationHook#projectWillBeOpened()}. */ + public abstract Project projectWillBeOpened(ProjectInitializationHook hook); /** Delegates to {@link ProjectOpenedHook#projectOpened}. */ public abstract void projectOpened(ProjectOpenedHook hook); diff -r f4a875afac55 projectuiapi/src/org/netbeans/spi/project/ui/ProjectInitializationHook.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/projectuiapi/src/org/netbeans/spi/project/ui/ProjectInitializationHook.java Wed Dec 03 10:05:50 2008 +0100 @@ -0,0 +1,76 @@ +/* + * 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.spi.project.ui; + +import org.netbeans.api.project.Project; + +/** + * A hook which can be run when a project is about to be "opened" the GUI. + * Called before {@link ProjectOpenedHook}'s projectOpened method + * is called. Register an instance of this class into {@link Project#getLookup()} + * method to perform pre-initialization actions, including finding another + * project to represent this directory. + *

+ * @see ProjectOpenedHook#projectOpened + * @see org.netbeans.api.project.Project#getLookup + * + * @author Jaroslav Tulach + * @since 1.34 + */ +public abstract class ProjectInitializationHook { + /** + * Default constructor for use by subclasses. + */ + protected ProjectInitializationHook() {} + + /** + * Called when a project is about to be open in the GUI. The project has + * a chance to initialize necessary system resources and potentially + * also provide a delegate that shall be open instead of the project + * itself. + * + * @return either project that shall be installed instead of the one + * providing this hook, the project itself, or + * null which means that no project will be opened at all + */ + protected abstract Project projectWillBeOpened(); +} diff -r f4a875afac55 projectuiapi/src/org/netbeans/spi/project/ui/ProjectOpenedHook.java --- a/projectuiapi/src/org/netbeans/spi/project/ui/ProjectOpenedHook.java Wed Dec 03 10:10:50 2008 +0100 +++ b/projectuiapi/src/org/netbeans/spi/project/ui/ProjectOpenedHook.java Wed Dec 03 10:05:50 2008 +0100 @@ -41,6 +41,7 @@ package org.netbeans.spi.project.ui; +import org.netbeans.api.project.Project; import org.netbeans.modules.project.uiapi.ProjectOpenedTrampoline; /** @@ -82,6 +83,11 @@ } public void projectClosed(ProjectOpenedHook hook) { hook.projectClosed(); + } + + @Override + public Project projectWillBeOpened(ProjectInitializationHook hook) { + return hook.projectWillBeOpened(); } }; }