# This patch file was generated by NetBeans IDE # This patch can be applied using context Tools: Apply Diff Patch action on respective folder. # It uses platform neutral UTF-8 encoding. # Above lines and this line are ignored by the patching process. Index: projects/projectui/src/org/netbeans/modules/project/ui/OpenProjectList.java --- projects/projectui/src/org/netbeans/modules/project/ui/OpenProjectList.java Base (1.79) +++ projects/projectui/src/org/netbeans/modules/project/ui/OpenProjectList.java Locally Modified (Based On 1.79) @@ -68,6 +68,14 @@ import java.util.Queue; import java.util.Set; import java.util.StringTokenizer; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; @@ -199,8 +207,12 @@ } } + Future openProjectsAPI() { + return LOAD; + } - private final class LoadOpenProjects implements Runnable, LookupListener { + + private final class LoadOpenProjects implements Runnable, LookupListener, Future { final RequestProcessor RP = new RequestProcessor("Load Open Projects"); // NOI18N final RequestProcessor.Task TASK = RP.create(this); private int action; @@ -209,6 +221,9 @@ private List recentTemplates; private Project mainProject; private Lookup.Result currentFiles; + private int entered; + private final Lock enteredGuard = new ReentrantLock(); + private final Condition enteredZeroed = enteredGuard.newCondition(); public LoadOpenProjects(int a) { action = a; @@ -321,8 +336,73 @@ } } + + final void enter() { + try { + enteredGuard.lock(); + entered++; + } finally { + enteredGuard.unlock(); } + } + final void exit() { + try { + enteredGuard.lock(); + if (--entered == 0) { + enteredZeroed.signalAll(); + } + } finally { + enteredGuard.unlock(); + } + + } + + + public boolean cancel(boolean mayInterruptIfRunning) { + return false; + } + + public boolean isCancelled() { + return false; + } + + public boolean isDone() { + return TASK.isFinished() && entered == 0; + } + + public Project[] get() throws InterruptedException, ExecutionException { + TASK.waitFinished(); + try { + enteredGuard.lock(); + while (entered > 0) { + enteredZeroed.await(); + } + } finally { + enteredGuard.unlock(); + } + return getDefault().getOpenProjects(); + } + + public Project[] get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + long ms = unit.convert(timeout, TimeUnit.MILLISECONDS); + if (!TASK.waitFinished(timeout)) { + throw new TimeoutException(); + } + try { + enteredGuard.lock(); + if (entered > 0) { + if (!enteredZeroed.await(ms, TimeUnit.MILLISECONDS)) { + throw new TimeoutException(); + } + } + } finally { + enteredGuard.unlock(); + } + return getDefault().getOpenProjects(); + } + } + public void open( Project p ) { open( new Project[] {p}, false ); } @@ -407,6 +487,9 @@ assert !Arrays.asList(projects).contains(null) : "Projects can't be null"; LOAD.waitFinished(); + + try { + LOAD.enter(); boolean recentProjectsChanged = false; int maxWork = 1000; int workForSubprojects = maxWork / 2; @@ -502,7 +585,6 @@ LogRecord[] addedRec = createRecord("UI_OPEN_PROJECTS", projectsToOpen.toArray(new Project[0])); // NOI18N log(addedRec); - Mutex.EVENT.readAccess(new Action() { public Void run() { pchSupport.firePropertyChange( PROPERTY_OPEN_PROJECTS, oldprjs.toArray(new Project[oldprjs.size()]), @@ -514,13 +596,20 @@ return null; } }); + } finally { + LOAD.exit(); } + } public void close( Project projects[], boolean notifyUI ) { LOAD.waitFinished(); if (!ProjectUtilities.closeAllDocuments (projects, notifyUI )) { return; } + + try { + LOAD.enter(); + logProjects("close(): closing project: ", projects); boolean mainClosed = false; boolean someClosed = false; @@ -577,7 +666,10 @@ } LogRecord[] removedRec = createRecord("UI_CLOSED_PROJECTS", projects); // NOI18N log(removedRec); + } finally { + LOAD.exit(); } + } public synchronized Project[] getOpenProjects() { Project projects[] = new Project[ openProjects.size() ]; Index: projects/projectui/src/org/netbeans/modules/project/ui/OpenProjectsTrampolineImpl.java --- projects/projectui/src/org/netbeans/modules/project/ui/OpenProjectsTrampolineImpl.java Base (1.11) +++ projects/projectui/src/org/netbeans/modules/project/ui/OpenProjectsTrampolineImpl.java Locally Modified (Based On 1.11) @@ -44,6 +44,7 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; +import java.util.concurrent.Future; import org.netbeans.api.project.Project; import org.netbeans.api.project.ui.OpenProjects; import org.netbeans.modules.project.uiapi.OpenProjectsTrampoline; @@ -118,4 +119,8 @@ OpenProjectList.getDefault().setMainProject(project); } + public Future openProjectsAPI() { + return OpenProjectList.getDefault().openProjectsAPI(); } + +} Index: projects/projectui/test/unit/src/org/netbeans/modules/project/ui/OpenProjectListTest.java --- projects/projectui/test/unit/src/org/netbeans/modules/project/ui/OpenProjectListTest.java Base (1.15) +++ projects/projectui/test/unit/src/org/netbeans/modules/project/ui/OpenProjectListTest.java Locally Modified (Based On 1.15) @@ -54,12 +54,15 @@ import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.netbeans.api.project.FileOwnerQuery; import org.netbeans.api.project.Project; import org.netbeans.api.project.ProjectManager; +import org.netbeans.api.project.ui.OpenProjects; import org.netbeans.junit.Log; import org.netbeans.junit.MockServices; import org.netbeans.junit.NbTestCase; @@ -72,6 +75,7 @@ import org.openide.filesystems.URLMapper; import org.openide.loaders.DataObject; import org.openide.loaders.DataObjectNotFoundException; +import org.openide.util.RequestProcessor; import org.openide.util.lookup.Lookups; /** Tests fix of issue 56454. @@ -436,20 +440,38 @@ } } - private static class TestProjectOpenedHookImpl extends ProjectOpenedHook { + private static class TestProjectOpenedHookImpl extends ProjectOpenedHook + implements Runnable { public static int opened = 0; public static 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; } + } + } private class ChangeListener implements PropertyChangeListener { int oldCount = -1; Index: projects/projectui/test/unit/src/org/netbeans/modules/project/ui/ProjectsRootNodeTest.java --- projects/projectui/test/unit/src/org/netbeans/modules/project/ui/ProjectsRootNodeTest.java Base (1.2) +++ projects/projectui/test/unit/src/org/netbeans/modules/project/ui/ProjectsRootNodeTest.java Locally Modified (Based On 1.2) @@ -46,9 +46,14 @@ import java.util.EventObject; import java.util.List; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import javax.swing.Action; import junit.framework.TestCase; +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.modules.project.ui.actions.TestSupport; @@ -63,6 +68,7 @@ import org.openide.nodes.NodeMemberEvent; import org.openide.nodes.NodeReorderEvent; import org.openide.util.Exceptions; +import org.openide.util.RequestProcessor; import org.openide.util.lookup.Lookups; /** @@ -111,7 +117,7 @@ super.tearDown(); } - public void testBehaviourOfProjectsLogicNode() throws InterruptedException { + public void testBehaviourOfProjectsLogicNode() throws Exception { Node logicalView = new ProjectsRootNode(ProjectsRootNode.LOGICAL_VIEW); L listener = new L(); logicalView.addNodeListener(listener); @@ -139,6 +145,10 @@ } listener.assertEvents("Goal is to receive no events at all", 0); + assertTrue("Finished", OpenProjects.getDefault().openProjects().isDone()); + assertFalse("Not cancelled, Finished", OpenProjects.getDefault().openProjects().isCancelled()); + Project[] arr = OpenProjects.getDefault().openProjects().get(); + assertEquals("30", 30, arr.length); } private static class L implements NodeListener { @@ -176,7 +186,8 @@ } - private static class TestProjectOpenedHookImpl extends ProjectOpenedHook { + private static class TestProjectOpenedHookImpl extends ProjectOpenedHook + implements Runnable { public static CountDownLatch toOpen = new CountDownLatch(30); public static int opened = 0; @@ -193,7 +204,24 @@ closed++; } + Project[] arr; + public void run() { + try { + arr = OpenProjects.getDefault().openProjects().get(50, TimeUnit.MILLISECONDS); + } catch (InterruptedException ex) { + fail("Wrong exception"); + } catch (ExecutionException ex) { + fail("Wrong exception"); + } catch (TimeoutException ex) { + // OK + } + } + protected void projectOpened() { + assertFalse("Running", OpenProjects.getDefault().openProjects().isDone()); + // now verify that other threads do not see results from the Future + RequestProcessor.getDefault().post(this).waitFinished(); + assertNull("TimeoutException thrown", arr); if (toWaitOn != null) { try { toWaitOn.await(); Index: projects/projectuiapi/apichanges.xml --- projects/projectuiapi/apichanges.xml Base (1.36) +++ projects/projectuiapi/apichanges.xml Locally Modified (Based On 1.36) @@ -105,6 +105,24 @@ + + + Added ways to track projects opening and closing + + + + + + Added a method to track progress of projects opening and closing. As the + opening of a project may take long time, and as there can be multiple + projects open at once, it may be necessary to be notified that the process + of open project list modification started or that it has + finished. + + + + + Added methods for creating customizer UI with additional listener for saving outside of AWT EQ Index: projects/projectuiapi/nbproject/project.properties --- projects/projectuiapi/nbproject/project.properties Base (1.35) +++ projects/projectuiapi/nbproject/project.properties Locally Modified (Based On 1.35) @@ -39,7 +39,7 @@ javac.compilerargs=-Xlint -Xlint:-serial javac.source=1.5 -spec.version.base=1.26.0 +spec.version.base=1.27.0 is.autoload=true javadoc.arch=${basedir}/arch.xml javadoc.apichanges=${basedir}/apichanges.xml Index: projects/projectuiapi/src/org/netbeans/api/project/ui/OpenProjects.java --- projects/projectuiapi/src/org/netbeans/api/project/ui/OpenProjects.java Base (1.12) +++ projects/projectuiapi/src/org/netbeans/api/project/ui/OpenProjects.java Locally Modified (Based On 1.12) @@ -42,6 +42,7 @@ package org.netbeans.api.project.ui; import java.beans.PropertyChangeListener; +import java.util.concurrent.Future; import org.netbeans.api.project.Project; import org.netbeans.modules.project.uiapi.OpenProjectsTrampoline; import org.netbeans.modules.project.uiapi.Utilities; @@ -114,6 +115,36 @@ } /** + * Method to track progress of projects opening and closing. As the + * opening of a project may take long time, and as there can be multiple + * projects open at once, it may be necessary to be notified that the process + * of open project list modification started or that it has + * finished. This method provides a future that can do that. + * To find out that the list of open projects is currently modified use: + *
+     * assert openProjects().isDone() == false;
+     * 
+ * To wait for the opening/closing to be finished and then obtain the result + * use: + *
+     * Project[] current = openProjects().get();
+     * 
+ * This result is different that a plain call to {@link #getOpenProjects} as + * that methods returns the current state, whatever it is. While the call through + * the future awaits for current modifications to finish. As such wait + * can take a long time one can also wait for just a limited amount of time. + * However this waiting methods should very likely only be used from dedicated threads, + * where the wait does not block other essencial operations (read: do not + * use such methods from AWT or other known threads!). + * + * @return future to track computation of open projects + * @since 1.27 + */ + public Future openProjects() { + return trampoline.openProjectsAPI(); + } + + /** * Opens given projects. * Acquires {@link org.netbeans.api.project.ProjectManager#mutex()} in the write mode. * @param projects to be opened. In the case when some of the projects are already opened Index: projects/projectuiapi/src/org/netbeans/modules/project/uiapi/OpenProjectsTrampoline.java --- projects/projectuiapi/src/org/netbeans/modules/project/uiapi/OpenProjectsTrampoline.java Base (1.7) +++ projects/projectuiapi/src/org/netbeans/modules/project/uiapi/OpenProjectsTrampoline.java Locally Modified (Based On 1.7) @@ -42,6 +42,7 @@ package org.netbeans.modules.project.uiapi; import java.beans.PropertyChangeListener; +import java.util.concurrent.Future; import org.netbeans.api.project.Project; /** @@ -58,6 +59,8 @@ public void addPropertyChangeListenerAPI( PropertyChangeListener listener, Object source ); + public Future openProjectsAPI(); + public void removePropertyChangeListenerAPI( PropertyChangeListener listener ); public Project getMainProject();