# 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/projectapi/src/org/netbeans/api/project/Bundle.properties --- projects/projectapi/src/org/netbeans/api/project/Bundle.properties Base (1.1) +++ projects/projectapi/src/org/netbeans/api/project/Bundle.properties Locally Modified (Based On 1.1) Index: projects/projectui/src/org/netbeans/modules/project/ui/Bundle.properties --- projects/projectui/src/org/netbeans/modules/project/ui/Bundle.properties Base (1.95) +++ projects/projectui/src/org/netbeans/modules/project/ui/Bundle.properties Locally Modified (Based On 1.95) @@ -311,3 +311,5 @@ UI_INIT_PROJECTS_ICON_BASE=org/netbeans/modules/project/ui/resources/open.gif OpeningProjectPanel.openingProjectLabel.text=Opening Project\: + +MSG_ProjChInit=Initializing project... Index: projects/projectui/src/org/netbeans/modules/project/ui/LazyProject.java --- projects/projectui/src/org/netbeans/modules/project/ui/LazyProject.java No Base Revision +++ projects/projectui/src/org/netbeans/modules/project/ui/LazyProject.java Locally New @@ -0,0 +1,165 @@ +/* + * 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-2007 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.awt.Image; +import java.beans.PropertyChangeListener; +import java.net.URL; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import javax.swing.Icon; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.ProjectInformation; +import org.netbeans.spi.project.ui.LogicalViewProvider; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.URLMapper; +import org.openide.loaders.DataObject; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; +import org.openide.util.Utilities; +import org.openide.util.lookup.Lookups; +import org.openidex.search.SearchInfo; + +/** + * Dummy project that shows a wait node while the real project list is + * loaded + * + * @author Tim Boudreau, Jaroslav Tulach + */ +final class LazyProject implements Project, ProjectInformation, SearchInfo, LogicalViewProvider { + URL url; + String displayName; + ExtIcon icon; + + public LazyProject(URL url, String displayName, ExtIcon icon) { + super(); + this.url = url; + this.displayName = displayName; + this.icon = icon; + } + + public FileObject getProjectDirectory() { + return URLMapper.findFileObject(url); + } + + public Lookup getLookup() { + return Lookups.fixed(this); + } + + public String getName() { + return displayName; + } + + public String getDisplayName() { + return displayName; + } + + public Icon getIcon() { + return icon.getIcon(); + } + + public Project getProject() { + return this; + } + + public void addPropertyChangeListener(PropertyChangeListener listener) { + } + + public void removePropertyChangeListener(PropertyChangeListener listener) { + } + + public boolean canSearch() { + return false; + } + + public Iterator objectsToSearch() { + return Collections.emptyList().iterator(); + } + + public Node createLogicalView() { + return new ProjNode(Lookups.singleton(this)); + } + + public Node findPath(Node root, Object target) { + return null; + } + + private final class ProjNode extends AbstractNode { + public ProjNode(Lookup lookup) { + super(new ProjCh(), lookup); + + setName(url.toExternalForm()); + setDisplayName(displayName); + } + + @Override + public Image getIcon(int type) { + return Utilities.icon2Image(icon.getIcon()); + } + + @Override + public Image getOpenedIcon(int type) { + return getIcon(type); + } + } // end of ProjNode + + private final class ProjCh extends Children.Array { + @Override + protected Collection initCollection() { + AbstractNode n = new AbstractNode(Children.LEAF); + n.setName("init"); // NOI18N + n.setDisplayName(NbBundle.getMessage(ProjCh.class, "MSG_ProjChInit")); + n.setIconBaseWithExtension("org/netbeans/modules/project/ui/resources/wait.gif"); + return Collections.singletonList((Node)n); + } + + @Override + protected void addNotify() { + super.addNotify(); + OpenProjectList.preferredProject(LazyProject.this); + } + + } +} Index: projects/projectui/src/org/netbeans/modules/project/ui/OpenProjectList.java --- projects/projectui/src/org/netbeans/modules/project/ui/OpenProjectList.java Base (1.75) +++ projects/projectui/src/org/netbeans/modules/project/ui/OpenProjectList.java Locally Modified (Based On 1.75) @@ -65,6 +65,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Queue; import java.util.Set; import java.util.StringTokenizer; import java.util.logging.Level; @@ -75,7 +76,9 @@ import javax.swing.SwingUtilities; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; +import org.netbeans.api.project.FileOwnerQuery; import org.netbeans.api.project.Project; +import org.netbeans.api.project.ProjectInformation; import org.netbeans.api.project.ProjectManager; import org.netbeans.api.project.ProjectUtils; import org.netbeans.modules.project.ui.api.UnloadedProjectInformation; @@ -97,10 +100,15 @@ import org.openide.loaders.DataObjectNotFoundException; import org.openide.modules.ModuleInfo; import org.openide.util.Lookup; +import org.openide.util.LookupEvent; +import org.openide.util.LookupListener; import org.openide.util.Mutex; import org.openide.util.Mutex.Action; import org.openide.util.NbBundle; import org.openide.util.RequestProcessor; +import org.openide.util.Utilities; +import org.openide.util.WeakListeners; +import org.openide.util.lookup.Lookups; import org.openide.windows.WindowManager; /** @@ -115,6 +123,7 @@ public static final String PROPERTY_OPEN_PROJECTS = "OpenProjects"; public static final String PROPERTY_MAIN_PROJECT = "MainProject"; public static final String PROPERTY_RECENT_PROJECTS = "RecentProjects"; + public static final String PROPERTY_REPLACE = "ReplaceProject"; private static OpenProjectList INSTANCE; @@ -146,8 +155,10 @@ private NbProjectDeletionListener nbprojectDeleteListener = new NbProjectDeletionListener(); private PropertyChangeListener infoListener; + private final LoadOpenProjects LOAD; OpenProjectList() { + LOAD = new LoadOpenProjects(0); openProjects = new ArrayList(); openProjectsModuleInfos = new HashMap>(); infoListener = new PropertyChangeListener() { @@ -170,22 +181,112 @@ Project[] inital = null; synchronized ( OpenProjectList.class ) { if ( INSTANCE == null ) { - needNotify = true; INSTANCE = new OpenProjectList(); INSTANCE.openProjects = loadProjectList(); - inital = INSTANCE.openProjects.toArray(new Project[0]); - INSTANCE.recentTemplates = new ArrayList( OpenProjectListSettings.getInstance().getRecentTemplates() ); + WindowManager.getDefault().invokeWhenUIReady(INSTANCE.LOAD); + } + } + return INSTANCE; + } + + static void waitProjectsFullyOpen() { + getDefault().LOAD.waitFinished(); + } + + static void preferredProject(Project lazyP) { + if (lazyP != null) { + getDefault().LOAD.preferredProject(lazyP); + } + } + + + private final class LoadOpenProjects implements Runnable, LookupListener { + final RequestProcessor RP = new RequestProcessor("Load Open Projects"); // NOI18N + final RequestProcessor.Task TASK = RP.create(this); + private int action; + private LinkedList toOpenProjects = new LinkedList(); + private List openedProjects; + private List recentTemplates; + private Project mainProject; + private Lookup.Result currentFiles; + + public LoadOpenProjects(int a) { + action = a; + currentFiles = Utilities.actionsGlobalContext().lookupResult(FileObject.class); + currentFiles.addLookupListener(WeakListeners.create(LookupListener.class, this, currentFiles)); + resultChanged(null); + } + + final void waitFinished() { + if (EventQueue.isDispatchThread()) { + if (action == 0) { + run(); + } + } + TASK.waitFinished(); + } + + public void run() { + switch (action) { + case 0: + action = 1; + TASK.schedule(0); + return; + case 1: + action = 2; + loadOnBackground(); + updateGlobalState(); + return; + case 2: + // finished, oK + return; + default: + throw new IllegalStateException("unknown action: " + action); + } + } + + final void preferredProject(Project lazyP) { + synchronized (toOpenProjects) { + for (Project p : toOpenProjects) { + if (p.getProjectDirectory().equals(lazyP.getProjectDirectory())) { + toOpenProjects.remove(p); + toOpenProjects.addFirst(p); + return; + } + } + } + } + + private void updateGlobalState() { + INSTANCE.openProjects = openedProjects; + INSTANCE.mainProject = mainProject; + INSTANCE.recentTemplates = recentTemplates; + + INSTANCE.pchSupport.firePropertyChange(PROPERTY_OPEN_PROJECTS, new Project[0], openedProjects.toArray(new Project[0])); + INSTANCE.pchSupport.firePropertyChange(PROPERTY_MAIN_PROJECT, null, INSTANCE.mainProject); + } + + private void loadOnBackground() { + openedProjects = new ArrayList(); + List URLs = OpenProjectListSettings.getInstance().getOpenProjectsURLs(); + toOpenProjects.addAll(URLs2Projects(URLs)); + Project[] inital; + synchronized (toOpenProjects) { + inital = toOpenProjects.toArray(new Project[0]); + } + recentTemplates = new ArrayList( OpenProjectListSettings.getInstance().getRecentTemplates() ); URL mainProjectURL = OpenProjectListSettings.getInstance().getMainProjectURL(); // Load recent project list INSTANCE.recentProjects.load(); - for( Iterator it = INSTANCE.openProjects.iterator(); it.hasNext(); ) { + synchronized (toOpenProjects) { + for( Iterator it = toOpenProjects.iterator(); it.hasNext(); ) { Project p = (Project)it.next(); INSTANCE.addModuleInfo(p); // Set main project try { if ( mainProjectURL != null && mainProjectURL.equals( p.getProjectDirectory().getURL() ) ) { - INSTANCE.mainProject = p; + mainProject = p; } } catch( FileStateInvalidException e ) { @@ -193,21 +294,35 @@ } } } + for (;;) { + Project p; + synchronized (toOpenProjects) { + if (toOpenProjects.isEmpty()) { + break; } - if ( needNotify ) { - //#68738: a project may open other projects in its ProjectOpenedHook: - for(Project p: new ArrayList(INSTANCE.openProjects)) { + p = toOpenProjects.remove(); + } + openedProjects.add(p); notifyOpened(p); + PropertyChangeEvent ev = new PropertyChangeEvent(this, PROPERTY_REPLACE, null, p); + pchSupport.firePropertyChange(ev); } - } if (inital != null) { log(createRecord("UI_INIT_PROJECTS", inital)); } - return INSTANCE; } + public void resultChanged(LookupEvent ev) { + for (FileObject fileObject : currentFiles.allInstances()) { + Project p = FileOwnerQuery.getOwner(fileObject); + OpenProjectList.preferredProject(p); + } + + } + } + public void open( Project p ) { open( new Project[] {p}, false ); } @@ -290,6 +405,7 @@ private void doOpen(Project[] projects, boolean openSubprojects, ProgressHandle handle, OpeningProjectPanel panel) { assert !Arrays.asList(projects).contains(null) : "Projects can't be null"; + LOAD.waitFinished(); boolean recentProjectsChanged = false; int maxWork = 1000; @@ -401,6 +517,7 @@ } public void close( Project projects[], boolean notifyUI ) { + LOAD.waitFinished(); if (!ProjectUtilities.closeAllDocuments (projects, notifyUI )) { return; } @@ -618,8 +735,8 @@ // Private methods --------------------------------------------------------- - private static List URLs2Projects( Collection URLs ) { - ArrayList result = new ArrayList( URLs.size() ); + private static LinkedList URLs2Projects( Collection URLs ) { + LinkedList result = new LinkedList(); for(URL url: URLs) { FileObject dir = URLMapper.findFileObject( url ); @@ -730,8 +847,20 @@ private static List loadProjectList() { List URLs = OpenProjectListSettings.getInstance().getOpenProjectsURLs(); - List projects = URLs2Projects( URLs ); + List names = OpenProjectListSettings.getInstance().getOpenProjectsDisplayNames(); + List icons = OpenProjectListSettings.getInstance().getOpenProjectsIcons(); + List projects = new ArrayList(); + Iterator urlIt = URLs.iterator(); + Iterator namesIt = names.iterator(); + Iterator iconIt = icons.iterator(); + + while(urlIt.hasNext() && namesIt.hasNext() && iconIt.hasNext()) { + projects.add(new LazyProject(urlIt.next(), namesIt.next(), iconIt.next())); + } + + //List projects = URLs2Projects( URLs ); + return projects; } @@ -739,7 +868,18 @@ private static void saveProjectList( List projects ) { List URLs = projects2URLs( projects ); OpenProjectListSettings.getInstance().setOpenProjectsURLs( URLs ); + List names = new ArrayList(); + List icons = new ArrayList(); + for (Iterator it = projects.iterator(); it.hasNext(); ) { + ProjectInformation prjInfo = ProjectUtils.getInformation(it.next()); + names.add(prjInfo.getDisplayName()); + ExtIcon extIcon = new ExtIcon(); + extIcon.setIcon(prjInfo.getIcon()); + icons.add(extIcon); } + OpenProjectListSettings.getInstance().setOpenProjectsDisplayNames(names); + OpenProjectListSettings.getInstance().setOpenProjectsIcons(icons); + } private static void saveMainProject( Project mainProject ) { try { Index: projects/projectui/src/org/netbeans/modules/project/ui/OpenProjectListSettings.java --- projects/projectui/src/org/netbeans/modules/project/ui/OpenProjectListSettings.java Base (1.31) +++ projects/projectui/src/org/netbeans/modules/project/ui/OpenProjectListSettings.java Locally Modified (Based On 1.31) @@ -51,6 +51,7 @@ import java.util.prefs.Preferences; import javax.swing.filechooser.FileSystemView; import org.openide.filesystems.FileUtil; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.openide.util.NbPreferences; @@ -68,6 +69,8 @@ private static final String MAIN_PROJECT_URL = "mainProjectURL"; //NOI18N -URL private static final String OPEN_AS_MAIN = "openAsMain"; //NOI18N - boolean private static final String OPEN_PROJECTS_URLS = "openProjectsURLs"; //NOI18N - List of URLs + private static final String OPEN_PROJECTS_DISPLAY_NAMES = "openProjectsDisplayNames"; //NOI18N - List of names + private static final String OPEN_PROJECTS_ICONS = "openProjectsIcons"; //NOI18N - List of icons private static final String OPEN_SUBPROJECTS = "openSubprojects"; //NOI18N - boolean private static final String PROP_PROJECTS_FOLDER = "projectsFolder"; //NOI18N - String private static final String RECENT_PROJECTS_URLS = "recentProjectsURLs"; //NOI18N List of URLs @@ -198,7 +201,25 @@ public void setOpenProjectsURLs( List list ) { setURLList( OPEN_PROJECTS_URLS, list); } + public List getOpenProjectsDisplayNames() { + return getStringList(OPEN_PROJECTS_DISPLAY_NAMES); + } + public void setOpenProjectsDisplayNames( List list ) { + setStringList( OPEN_PROJECTS_DISPLAY_NAMES, list); + } + public List getOpenProjectsIcons() { + return getIconList(OPEN_PROJECTS_ICONS); + } + + public void setOpenProjectsIcons( List list ) { + try { + setIconList(OPEN_PROJECTS_ICONS, list); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + } + public boolean isOpenSubprojects() { return getPreferences().getBoolean( OPEN_SUBPROJECTS, false); } Index: projects/projectui/src/org/netbeans/modules/project/ui/ProjectsRootNode.java --- projects/projectui/src/org/netbeans/modules/project/ui/ProjectsRootNode.java Base (1.51) +++ projects/projectui/src/org/netbeans/modules/project/ui/ProjectsRootNode.java Locally Modified (Based On 1.51) @@ -63,6 +63,7 @@ import javax.swing.event.ChangeListener; import org.netbeans.api.project.FileOwnerQuery; import org.netbeans.api.project.Project; +import org.netbeans.api.project.ProjectManager; import org.netbeans.api.project.ProjectUtils; import org.netbeans.api.project.SourceGroup; import org.netbeans.api.project.Sources; @@ -79,6 +80,7 @@ import org.openide.nodes.Children; import org.openide.nodes.FilterNode; import org.openide.nodes.Node; +import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.RequestProcessor; import org.openide.util.WeakListeners; @@ -213,7 +215,7 @@ // XXX Needs to listen to project rename // However project rename is currently disabled so it is not a big deal - static class ProjectChildren extends Children.Keys implements ChangeListener, PropertyChangeListener { + static class ProjectChildren extends Children.Keys implements ChangeListener, PropertyChangeListener { private java.util.Map > sources2projects = new WeakHashMap>(); @@ -221,72 +223,87 @@ public ProjectChildren( int type ) { this.type = type; - OpenProjectList.getDefault().addPropertyChangeListener( this ); } // Children.Keys impl -------------------------------------------------- + @Override public void addNotify() { + OpenProjectList.getDefault().addPropertyChangeListener(this); setKeys( getKeys() ); } + @Override public void removeNotify() { + OpenProjectList.getDefault().removePropertyChangeListener(this); for (Sources sources : sources2projects.keySet()) { sources.removeChangeListener( this ); } sources2projects.clear(); - setKeys(Collections.emptySet()); + setKeys(Collections.emptySet()); } - protected Node[] createNodes(Project project) { - LogicalViewProvider lvp = project.getLookup().lookup(LogicalViewProvider.class); + protected Node[] createNodes(Pair p) { + Project project = p.project; - Node nodes[] = null; - boolean projectInLookup = true; + Node origNodes[] = null; + boolean[] projectInLookup = new boolean[1]; + projectInLookup[0] = true; if ( type == PHYSICAL_VIEW ) { Sources sources = ProjectUtils.getSources( project ); sources.removeChangeListener( this ); sources.addChangeListener( this ); sources2projects.put( sources, new WeakReference( project ) ); - nodes = PhysicalView.createNodesForProject( project ); + origNodes = PhysicalView.createNodesForProject( project ); + } else { + origNodes = new Node[] { logicalViewForProject(project, projectInLookup) }; } - else if ( lvp == null ) { - ErrorManager.getDefault().log(ErrorManager.WARNING, "Warning - project " + ProjectUtils.getInformation(project).getName() + " failed to supply a LogicalViewProvider in its lookup"); // NOI18N - Sources sources = ProjectUtils.getSources( project ); - sources.removeChangeListener( this ); - sources.addChangeListener( this ); - nodes = PhysicalView.createNodesForProject( project ); - if ( nodes.length > 0 ) { - nodes = new Node[] { nodes[0] }; + + Node[] badgedNodes = new Node[ origNodes.length ]; + for( int i = 0; i < origNodes.length; i++ ) { + if ( type == PHYSICAL_VIEW && !PhysicalView.isProjectDirNode( origNodes[i] ) ) { + // Don't badge external sources + badgedNodes[i] = origNodes[i]; } else { - nodes = new Node[] { Node.EMPTY }; + badgedNodes[i] = new BadgingNode( origNodes[i], + type == LOGICAL_VIEW && projectInLookup[0]); } } - else { - nodes = new Node[] { lvp.createLogicalView() }; - if (nodes[0].getLookup().lookup(Project.class) != project) { + + return badgedNodes; + } + + private Node logicalViewForProject(Project project, boolean[] projectInLookup) { + Node node; + + LogicalViewProvider lvp = project.getLookup().lookup(LogicalViewProvider.class); + + if ( lvp == null ) { + ErrorManager.getDefault().log(ErrorManager.WARNING, "Warning - project " + ProjectUtils.getInformation(project).getName() + " failed to supply a LogicalViewProvider in its lookup"); // NOI18N + Sources sources = ProjectUtils.getSources(project); + sources.removeChangeListener(this); + sources.addChangeListener(this); + Node[] physical = PhysicalView.createNodesForProject(project); + if (physical.length > 0) { + node = physical[0]; + } else { + node = Node.EMPTY; + } + } else { + node = lvp.createLogicalView(); + if (node.getLookup().lookup(Project.class) != project) { // Various actions, badging, etc. are not going to work. ErrorManager.getDefault().log(ErrorManager.WARNING, "Warning - project " + ProjectUtils.getInformation(project).getName() + " failed to supply itself in the lookup of the root node of its own logical view"); // NOI18N //#114664 - projectInLookup = false; + if (projectInLookup != null) { + projectInLookup[0] = false; } } - - Node[] badgedNodes = new Node[ nodes.length ]; - for( int i = 0; i < nodes.length; i++ ) { - if ( type == PHYSICAL_VIEW && !PhysicalView.isProjectDirNode( nodes[i] ) ) { - // Don't badge external sources - badgedNodes[i] = nodes[i]; } - else { - badgedNodes[i] = new BadgingNode( nodes[i], - type == LOGICAL_VIEW && projectInLookup); - } - } - return badgedNodes; + return node; } // PropertyChangeListener impl ----------------------------------------- @@ -315,22 +332,66 @@ // Fix for 50259, callers sometimes hold locks SwingUtilities.invokeLater( new Runnable() { public void run() { - refreshKey( project ); + refreshKey( new Pair(project, project.getProjectDirectory()) ); } } ); } // Own methods --------------------------------------------------------- - public Collection getKeys() { + public Collection getKeys() { List projects = Arrays.asList( OpenProjectList.getDefault().getOpenProjects() ); Collections.sort( projects, OpenProjectList.PROJECT_BY_DISPLAYNAME ); - return projects; + List dirs = Arrays.asList( new Pair[projects.size()] ); + + for (int i = 0; i < projects.size(); i++) { + Project project = projects.get(i); + dirs.set(i, new Pair(project, project.getProjectDirectory())); } + + return dirs; } + /** Object that comparers two projects just by their directory. + * This allows to replace a LazyProject with real one without discarding + * the nodes. + */ + private static final class Pair extends Object { + public final Project project; + public final FileObject fo; + + public Pair(Project project, FileObject fo) { + this.project = project; + this.fo = fo; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Pair other = (Pair) obj; + if (this.fo != other.fo && (this.fo == null || !this.fo.equals(other.fo))) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 53 * hash + (this.fo != null ? this.fo.hashCode() : 0); + return hash; + } + } + + } + private static final class BadgingNode extends FilterNode implements PropertyChangeListener, Runnable, FileStatusListener { private static String badgedNamePattern = NbBundle.getMessage(ProjectsRootNode.class, "LBL_MainProject_BadgedNamePattern"); @@ -341,7 +402,7 @@ private volatile boolean nameChange; public BadgingNode(Node n, boolean addSearchInfo) { - super(n, null, addSearchInfo ? new ProxyLookup(n.getLookup(), Lookups.singleton(alwaysSearchableSearchInfo(n.getLookup().lookup(Project.class)))) : n.getLookup()); + super(n, null, addSearchInfo ? badgingLookup(n) : n.getLookup()); OpenProjectList.getDefault().addPropertyChangeListener(WeakListeners.propertyChange(this, OpenProjectList.getDefault())); Project proj = getOriginal().getLookup().lookup(Project.class); if (proj != null) { @@ -362,6 +423,17 @@ } } + private static Lookup badgingLookup(Node n) { + return new BadgingLookup(n.getLookup(), Lookups.singleton(alwaysSearchableSearchInfo(n.getLookup().lookup(Project.class)))); + } + + private void updateLookup(Node n) { + if (getLookup() instanceof BadgingLookup) { + BadgingLookup bl = (BadgingLookup)getLookup(); + bl.setMyLookups(n.getLookup(), Lookups.singleton(alwaysSearchableSearchInfo(n.getLookup().lookup(Project.class)))); + } + } + public void run() { if (nameChange) { fireDisplayNameChange(null, null); @@ -433,14 +505,38 @@ if ( OpenProjectList.PROPERTY_MAIN_PROJECT.equals( e.getPropertyName() ) ) { fireDisplayNameChange( null, null ); } + if ( OpenProjectList.PROPERTY_REPLACE.equals(e.getPropertyName())) { + Project p = getLookup().lookup(Project.class); + if (p == null) { + return; } + FileObject fo = p.getProjectDirectory(); + Project newProj = (Project)e.getNewValue(); + assert newProj != null; + if (newProj.getProjectDirectory().equals(fo)) { + ProjectChildren ch = (ProjectChildren)getParentNode().getChildren(); + Node n = ch.logicalViewForProject(newProj, null); + changeOriginal(n, true); + updateLookup(n); + } + } + } private boolean isMain() { Project p = getLookup().lookup(Project.class); return p != null && OpenProjectList.getDefault().isMainProject( p ); } + } // end of BadgingNode + + private static final class BadgingLookup extends ProxyLookup { + public BadgingLookup(Lookup... lkps) { + super(lkps); } + public void setMyLookups(Lookup... lkps) { + setLookups(lkps); + } + } // end of BadgingLookup /** * Produce a {@link SearchInfo} variant that is always searchable, for speed. Index: projects/projectui/src/org/netbeans/modules/project/ui/actions/OpenProjectFolderAction.java --- projects/projectui/src/org/netbeans/modules/project/ui/actions/OpenProjectFolderAction.java Base (1.7) +++ projects/projectui/src/org/netbeans/modules/project/ui/actions/OpenProjectFolderAction.java Locally Modified (Based On 1.7) Index: projects/projectui/src/org/netbeans/modules/project/ui/api/RecentProjects.java --- projects/projectui/src/org/netbeans/modules/project/ui/api/RecentProjects.java Base (1.6) +++ projects/projectui/src/org/netbeans/modules/project/ui/api/RecentProjects.java Locally Modified (Based On 1.6) 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.14) +++ projects/projectui/test/unit/src/org/netbeans/modules/project/ui/OpenProjectListTest.java Locally Modified (Based On 1.14) @@ -198,6 +198,8 @@ public void testSerialize() throws Exception { testOpen(); + + OpenProjectList.waitProjectsFullyOpen(); Field f = OpenProjectList.class.getDeclaredField("INSTANCE"); f.setAccessible(true); f.set(null, null); @@ -205,6 +207,9 @@ CharSequence whatIsLoggedWhenDeserializing = Log.enable("org.netbeans.ui", Level.FINE); Project[] arr = OpenProjectList.getDefault().getOpenProjects(); + OpenProjectList.waitProjectsFullyOpen(); + arr = OpenProjectList.getDefault().getOpenProjects(); + assertEquals("One", 1, arr.length); Pattern p = Pattern.compile("Initializing.*1.*TestProject", Pattern.MULTILINE | Pattern.DOTALL); Matcher m = p.matcher(whatIsLoggedWhenDeserializing); Index: projects/projectui/test/unit/src/org/netbeans/modules/project/ui/OpenProjectsTrampolineImplTest.java --- projects/projectui/test/unit/src/org/netbeans/modules/project/ui/OpenProjectsTrampolineImplTest.java Base (1.7) +++ projects/projectui/test/unit/src/org/netbeans/modules/project/ui/OpenProjectsTrampolineImplTest.java Locally Modified (Based On 1.7) @@ -80,6 +80,7 @@ // mysteryproject = scratch.createFolder("mystery"); TestUtil.setLookup(Lookups.singleton(TestUtil.testProjectFactory())); pm = ProjectManager.getDefault(); + OpenProjectList.waitProjectsFullyOpen(); } protected void tearDown() throws Exception { Index: projects/projectui/test/unit/src/org/netbeans/modules/project/ui/ProjectUtilitiesTest.java --- projects/projectui/test/unit/src/org/netbeans/modules/project/ui/ProjectUtilitiesTest.java Base (1.18) +++ projects/projectui/test/unit/src/org/netbeans/modules/project/ui/ProjectUtilitiesTest.java Locally Modified (Based On 1.18) @@ -80,7 +80,7 @@ static { System.setProperty("org.openide.windows.DummyWindowManager.VISIBLE", "false"); } - */ + /**/ private static final String NAVIGATOR_MODE = "navigator"; Index: projects/projectui/test/unit/src/org/netbeans/modules/project/ui/ProjectsRootNodePreferredFromContextOpenTest.java --- projects/projectui/test/unit/src/org/netbeans/modules/project/ui/ProjectsRootNodePreferredFromContextOpenTest.java No Base Revision +++ projects/projectui/test/unit/src/org/netbeans/modules/project/ui/ProjectsRootNodePreferredFromContextOpenTest.java Locally New @@ -0,0 +1,277 @@ +/* + * 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]" + * + * 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 2007 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.project.ui; + +import java.awt.EventQueue; +import java.beans.PropertyChangeEvent; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.EventObject; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import javax.swing.Action; +import junit.framework.TestCase; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.ProjectManager; +import org.netbeans.junit.MockServices; +import org.netbeans.junit.NbTestCase; +import org.netbeans.modules.project.ui.actions.TestSupport; +import org.netbeans.spi.project.ui.ProjectOpenedHook; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.filesystems.URLMapper; +import org.openide.nodes.Node; +import org.openide.nodes.Node.Handle; +import org.openide.nodes.NodeEvent; +import org.openide.nodes.NodeListener; +import org.openide.nodes.NodeMemberEvent; +import org.openide.nodes.NodeReorderEvent; +import org.openide.util.ContextGlobalProvider; +import org.openide.util.Exceptions; +import org.openide.util.Lookup; +import org.openide.util.lookup.Lookups; + +/** + * + * @author Jaroslav Tulach + */ +public class ProjectsRootNodePreferredFromContextOpenTest extends NbTestCase { + CountDownLatch first; + CountDownLatch middle; + CountDownLatch rest; + + public ProjectsRootNodePreferredFromContextOpenTest(String testName) { + super(testName); + } + + @Override + protected void setUp() throws Exception { + clearWorkDir(); + + MockServices.setServices(TestSupport.TestProjectFactory.class, ContextProv.class); + + FileObject workDir = FileUtil.toFileObject(getWorkDir()); + assertNotNull(workDir); + + first = new CountDownLatch(1); + middle = new CountDownLatch(1); + rest = new CountDownLatch(2); + + List list = new ArrayList(); + List icons = new ArrayList(); + List names = new ArrayList(); + for (int i = 0; i < 10; i++) { + FileObject prj = TestSupport.createTestProject(workDir, "prj" + i); + URL url = URLMapper.findURL(prj, URLMapper.EXTERNAL); + list.add(url); + names.add(url.toExternalForm()); + icons.add(new ExtIcon()); + TestSupport.TestProject tmp = (TestSupport.TestProject)ProjectManager.getDefault ().findProject (prj); + assertNotNull("Project found", tmp); + CountDownLatch down = i == 0 ? first : (i == 5 ? middle : rest); + tmp.setLookup(Lookups.singleton(new TestProjectOpenedHookImpl(down))); + } + + OpenProjectListSettings.getInstance().setOpenProjectsURLs(list); + OpenProjectListSettings.getInstance().setOpenProjectsDisplayNames(names); + OpenProjectListSettings.getInstance().setOpenProjectsIcons(icons); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + public void testPreferencesInOpenCanBeChanged() throws InterruptedException, IOException { + Node logicalView = new ProjectsRootNode(ProjectsRootNode.LOGICAL_VIEW); + L listener = new L(); + logicalView.addNodeListener(listener); + + assertEquals("10 children", 10, logicalView.getChildren().getNodesCount()); + listener.assertEvents("None", 0); + assertEquals("No project opened yet", 0, TestProjectOpenedHookImpl.opened); + + for (Node n : logicalView.getChildren().getNodes()) { + TestSupport.TestProject p = n.getLookup().lookup(TestSupport.TestProject.class); + assertNull("No project of this type, yet", p); + } + + Node midNode = logicalView.getChildren().getNodes()[5]; + { + TestSupport.TestProject p = midNode.getLookup().lookup(TestSupport.TestProject.class); + assertNull("No project of this type, yet", p); + } + Project lazyP = midNode.getLookup().lookup(Project.class); + assertNotNull("Some project is found", lazyP); + assertEquals("It is lazy project", LazyProject.class, lazyP.getClass()); + + middle.countDown(); + // not necessary, but to ensure middle really does not run + Thread.sleep(300); + assertEquals("Still no processing", 0, TestProjectOpenedHookImpl.opened); + + // make a file of some project selected, that + // shall trigger OpenProjectList.preferredProject(lazyP); + FileObject create = FileUtil.createData(lazyP.getProjectDirectory(), "test.txt"); + ContextProv.assign(Lookups.singleton(create)); + first.countDown(); + + TestProjectOpenedHookImpl.toOpen.await(); + + { + TestSupport.TestProject p = null; + for (int i = 0; i < 10; i++) { + Node midNode2 = logicalView.getChildren().getNodes()[5]; + p = midNode.getLookup().lookup(TestSupport.TestProject.class); + if (p != null) { + break; + } + Thread.sleep(100); + } + assertNotNull("The right project opened", p); + } + + rest.countDown(); + rest.countDown(); + OpenProjectList.waitProjectsFullyOpen(); + + assertEquals("All projects opened", 10, TestProjectOpenedHookImpl.opened); + + + for (Node n : logicalView.getChildren().getNodes()) { + TestSupport.TestProject p = n.getLookup().lookup(TestSupport.TestProject.class); + assertNotNull("Nodes have correct project of this type", p); + } + } + + private static class L implements NodeListener { + public List events = new ArrayList(); + + public void childrenAdded(NodeMemberEvent ev) { + assertFalse("No event in AWT thread", EventQueue.isDispatchThread()); + events.add(ev); + } + + public void childrenRemoved(NodeMemberEvent ev) { + assertFalse("No event in AWT thread", EventQueue.isDispatchThread()); + events.add(ev); + } + + public void childrenReordered(NodeReorderEvent ev) { + assertFalse("No event in AWT thread", EventQueue.isDispatchThread()); + events.add(ev); + } + + public void nodeDestroyed(NodeEvent ev) { + assertFalse("No event in AWT thread", EventQueue.isDispatchThread()); + events.add(ev); + } + + public void propertyChange(PropertyChangeEvent evt) { + assertFalse("No event in AWT thread", EventQueue.isDispatchThread()); + events.add(evt); + } + + final void assertEvents(String string, int i) { + assertEquals(string + events, i, events.size()); + events.clear(); + } + + } + + private static class TestProjectOpenedHookImpl extends ProjectOpenedHook { + + public static CountDownLatch toOpen = new CountDownLatch(2); + public static int opened = 0; + public static int closed = 0; + + + private CountDownLatch toWaitOn; + + public TestProjectOpenedHookImpl(CountDownLatch toWaitOn) { + this.toWaitOn = toWaitOn; + } + + protected void projectClosed() { + closed++; + } + + protected void projectOpened() { + if (toWaitOn != null) { + try { + toWaitOn.await(); + } catch (InterruptedException ex) { + throw new IllegalStateException(ex); + } + } + opened++; + toOpen.countDown(); + } + + } + + public static final class ContextProv implements ContextGlobalProvider, + Lookup.Provider { + private static Lookup current = Lookup.EMPTY; + private static Lookup proxy; + public ContextProv() { + assert proxy == null; + proxy = Lookups.proxy(this); + } + + public static void assign(Lookup current) { + ContextProv.current = current; + // refresh + if (proxy != null) { + proxy.lookup((Class)null); + } + } + + public Lookup createGlobalContext() { + return proxy; + } + + public Lookup getLookup() { + return current; + } + + } +} Index: projects/projectui/test/unit/src/org/netbeans/modules/project/ui/ProjectsRootNodePreferredOpenTest.java --- projects/projectui/test/unit/src/org/netbeans/modules/project/ui/ProjectsRootNodePreferredOpenTest.java No Base Revision +++ projects/projectui/test/unit/src/org/netbeans/modules/project/ui/ProjectsRootNodePreferredOpenTest.java Locally New @@ -0,0 +1,245 @@ +/* + * 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]" + * + * 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 2007 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.project.ui; + +import java.awt.EventQueue; +import java.beans.PropertyChangeEvent; +import java.net.URL; +import java.util.ArrayList; +import java.util.EventObject; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import javax.swing.Action; +import junit.framework.TestCase; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.ProjectManager; +import org.netbeans.junit.MockServices; +import org.netbeans.junit.NbTestCase; +import org.netbeans.modules.project.ui.actions.TestSupport; +import org.netbeans.spi.project.ui.ProjectOpenedHook; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.filesystems.URLMapper; +import org.openide.nodes.Node; +import org.openide.nodes.Node.Handle; +import org.openide.nodes.NodeEvent; +import org.openide.nodes.NodeListener; +import org.openide.nodes.NodeMemberEvent; +import org.openide.nodes.NodeReorderEvent; +import org.openide.util.Exceptions; +import org.openide.util.lookup.Lookups; + +/** + * + * @author Jaroslav Tulach + */ +public class ProjectsRootNodePreferredOpenTest extends NbTestCase { + CountDownLatch first; + CountDownLatch middle; + CountDownLatch rest; + + public ProjectsRootNodePreferredOpenTest(String testName) { + super(testName); + } + + @Override + protected void setUp() throws Exception { + clearWorkDir(); + + MockServices.setServices(TestSupport.TestProjectFactory.class); + + FileObject workDir = FileUtil.toFileObject(getWorkDir()); + assertNotNull(workDir); + + first = new CountDownLatch(1); + middle = new CountDownLatch(1); + rest = new CountDownLatch(2); + + List list = new ArrayList(); + List icons = new ArrayList(); + List names = new ArrayList(); + for (int i = 0; i < 10; i++) { + FileObject prj = TestSupport.createTestProject(workDir, "prj" + i); + URL url = URLMapper.findURL(prj, URLMapper.EXTERNAL); + list.add(url); + names.add(url.toExternalForm()); + icons.add(new ExtIcon()); + TestSupport.TestProject tmp = (TestSupport.TestProject)ProjectManager.getDefault ().findProject (prj); + assertNotNull("Project found", tmp); + CountDownLatch down = i == 0 ? first : (i == 5 ? middle : rest); + tmp.setLookup(Lookups.singleton(new TestProjectOpenedHookImpl(down))); + } + + OpenProjectListSettings.getInstance().setOpenProjectsURLs(list); + OpenProjectListSettings.getInstance().setOpenProjectsDisplayNames(names); + OpenProjectListSettings.getInstance().setOpenProjectsIcons(icons); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + public void testPreferencesInOpenCanBeChanged() throws InterruptedException { + Node logicalView = new ProjectsRootNode(ProjectsRootNode.LOGICAL_VIEW); + L listener = new L(); + logicalView.addNodeListener(listener); + + assertEquals("10 children", 10, logicalView.getChildren().getNodesCount()); + listener.assertEvents("None", 0); + assertEquals("No project opened yet", 0, TestProjectOpenedHookImpl.opened); + + for (Node n : logicalView.getChildren().getNodes()) { + TestSupport.TestProject p = n.getLookup().lookup(TestSupport.TestProject.class); + assertNull("No project of this type, yet", p); + } + + Node midNode = logicalView.getChildren().getNodes()[5]; + { + TestSupport.TestProject p = midNode.getLookup().lookup(TestSupport.TestProject.class); + assertNull("No project of this type, yet", p); + } + Project lazyP = midNode.getLookup().lookup(Project.class); + assertNotNull("Some project is found", lazyP); + assertEquals("It is lazy project", LazyProject.class, lazyP.getClass()); + + middle.countDown(); + // not necessary, but to ensure middle really does not run + Thread.sleep(300); + assertEquals("Still no processing", 0, TestProjectOpenedHookImpl.opened); + // trigger initialization of the node, shall trigger OpenProjectList.preferredProject(lazyP); + midNode.getChildren().getNodes(); + first.countDown(); + + TestProjectOpenedHookImpl.toOpen.await(); + + { + TestSupport.TestProject p = null; + for (int i = 0; i < 10; i++) { + Node midNode2 = logicalView.getChildren().getNodes()[5]; + p = midNode.getLookup().lookup(TestSupport.TestProject.class); + if (p != null) { + break; + } + Thread.sleep(100); + } + assertNotNull("The right project opened", p); + } + + rest.countDown(); + rest.countDown(); + + OpenProjectList.waitProjectsFullyOpen(); + + assertEquals("All projects opened", 10, TestProjectOpenedHookImpl.opened); + + + for (Node n : logicalView.getChildren().getNodes()) { + TestSupport.TestProject p = n.getLookup().lookup(TestSupport.TestProject.class); + assertNotNull("Nodes have correct project of this type", p); + } + } + + private static class L implements NodeListener { + public List events = new ArrayList(); + + public void childrenAdded(NodeMemberEvent ev) { + assertFalse("No event in AWT thread", EventQueue.isDispatchThread()); + events.add(ev); + } + + public void childrenRemoved(NodeMemberEvent ev) { + assertFalse("No event in AWT thread", EventQueue.isDispatchThread()); + events.add(ev); + } + + public void childrenReordered(NodeReorderEvent ev) { + assertFalse("No event in AWT thread", EventQueue.isDispatchThread()); + events.add(ev); + } + + public void nodeDestroyed(NodeEvent ev) { + assertFalse("No event in AWT thread", EventQueue.isDispatchThread()); + events.add(ev); + } + + public void propertyChange(PropertyChangeEvent evt) { + assertFalse("No event in AWT thread", EventQueue.isDispatchThread()); + events.add(evt); + } + + final void assertEvents(String string, int i) { + assertEquals(string + events, i, events.size()); + events.clear(); + } + + } + + private static class TestProjectOpenedHookImpl extends ProjectOpenedHook { + + public static CountDownLatch toOpen = new CountDownLatch(2); + public static int opened = 0; + public static int closed = 0; + + + private CountDownLatch toWaitOn; + + public TestProjectOpenedHookImpl(CountDownLatch toWaitOn) { + this.toWaitOn = toWaitOn; + } + + protected void projectClosed() { + closed++; + } + + protected void projectOpened() { + if (toWaitOn != null) { + try { + toWaitOn.await(); + } catch (InterruptedException ex) { + throw new IllegalStateException(ex); + } + } + opened++; + toOpen.countDown(); + } + + } +} 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 No Base Revision +++ projects/projectui/test/unit/src/org/netbeans/modules/project/ui/ProjectsRootNodeTest.java Locally New @@ -0,0 +1,209 @@ +/* + * 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]" + * + * 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 2007 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.project.ui; + +import java.awt.EventQueue; +import java.beans.PropertyChangeEvent; +import java.net.URL; +import java.util.ArrayList; +import java.util.EventObject; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import javax.swing.Action; +import junit.framework.TestCase; +import org.netbeans.api.project.ProjectManager; +import org.netbeans.junit.MockServices; +import org.netbeans.junit.NbTestCase; +import org.netbeans.modules.project.ui.actions.TestSupport; +import org.netbeans.spi.project.ui.ProjectOpenedHook; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.filesystems.URLMapper; +import org.openide.nodes.Node; +import org.openide.nodes.Node.Handle; +import org.openide.nodes.NodeEvent; +import org.openide.nodes.NodeListener; +import org.openide.nodes.NodeMemberEvent; +import org.openide.nodes.NodeReorderEvent; +import org.openide.util.Exceptions; +import org.openide.util.lookup.Lookups; + +/** + * + * @author Jaroslav Tulach + */ +public class ProjectsRootNodeTest extends NbTestCase { + CountDownLatch down; + + public ProjectsRootNodeTest(String testName) { + super(testName); + } + + @Override + protected void setUp() throws Exception { + clearWorkDir(); + + MockServices.setServices(TestSupport.TestProjectFactory.class); + + FileObject workDir = FileUtil.toFileObject(getWorkDir()); + assertNotNull(workDir); + + down = new CountDownLatch(1); + + List list = new ArrayList(); + List icons = new ArrayList(); + List names = new ArrayList(); + for (int i = 0; i < 30; i++) { + FileObject prj = TestSupport.createTestProject(workDir, "prj" + i); + URL url = URLMapper.findURL(prj, URLMapper.EXTERNAL); + list.add(url); + names.add(url.toExternalForm()); + icons.add(new ExtIcon()); + TestSupport.TestProject tmp = (TestSupport.TestProject)ProjectManager.getDefault ().findProject (prj); + assertNotNull("Project found", tmp); + tmp.setLookup(Lookups.singleton(new TestProjectOpenedHookImpl(down))); + } + + OpenProjectListSettings.getInstance().setOpenProjectsURLs(list); + OpenProjectListSettings.getInstance().setOpenProjectsDisplayNames(names); + OpenProjectListSettings.getInstance().setOpenProjectsIcons(icons); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + public void testBehaviourOfProjectsLogicNode() throws InterruptedException { + Node logicalView = new ProjectsRootNode(ProjectsRootNode.LOGICAL_VIEW); + L listener = new L(); + logicalView.addNodeListener(listener); + + assertEquals("30 children", 30, logicalView.getChildren().getNodesCount()); + listener.assertEvents("None", 0); + assertEquals("No project opened yet", 0, TestProjectOpenedHookImpl.opened); + + for (Node n : logicalView.getChildren().getNodes()) { + TestSupport.TestProject p = n.getLookup().lookup(TestSupport.TestProject.class); + assertNull("No project of this type, yet", p); + } + + // let project open code run + down.countDown(); + TestProjectOpenedHookImpl.toOpen.await(); + + assertEquals("All projects opened", 30, TestProjectOpenedHookImpl.opened); + + OpenProjectList.waitProjectsFullyOpen(); + + for (Node n : logicalView.getChildren().getNodes()) { + TestSupport.TestProject p = n.getLookup().lookup(TestSupport.TestProject.class); + assertNotNull("Nodes have correct project of this type", p); + } + + listener.assertEvents("Goal is to receive no events at all", 0); + } + + private static class L implements NodeListener { + public List events = new ArrayList(); + + public void childrenAdded(NodeMemberEvent ev) { + assertFalse("No event in AWT thread", EventQueue.isDispatchThread()); + events.add(ev); + } + + public void childrenRemoved(NodeMemberEvent ev) { + assertFalse("No event in AWT thread", EventQueue.isDispatchThread()); + events.add(ev); + } + + public void childrenReordered(NodeReorderEvent ev) { + assertFalse("No event in AWT thread", EventQueue.isDispatchThread()); + events.add(ev); + } + + public void nodeDestroyed(NodeEvent ev) { + assertFalse("No event in AWT thread", EventQueue.isDispatchThread()); + events.add(ev); + } + + public void propertyChange(PropertyChangeEvent evt) { + assertFalse("No event in AWT thread", EventQueue.isDispatchThread()); + events.add(evt); + } + + final void assertEvents(String string, int i) { + assertEquals(string + events, i, events.size()); + events.clear(); + } + + } + + private static class TestProjectOpenedHookImpl extends ProjectOpenedHook { + + public static CountDownLatch toOpen = new CountDownLatch(30); + public static int opened = 0; + public static int closed = 0; + + + private CountDownLatch toWaitOn; + + public TestProjectOpenedHookImpl(CountDownLatch toWaitOn) { + this.toWaitOn = toWaitOn; + } + + protected void projectClosed() { + closed++; + } + + protected void projectOpened() { + if (toWaitOn != null) { + try { + toWaitOn.await(); + } catch (InterruptedException ex) { + throw new IllegalStateException(ex); + } + } + opened++; + toOpen.countDown(); + } + + } +} Index: projects/projectui/test/unit/src/org/netbeans/modules/project/ui/api/RecentProjectsTest.java --- projects/projectui/test/unit/src/org/netbeans/modules/project/ui/api/RecentProjectsTest.java Base (1.6) +++ projects/projectui/test/unit/src/org/netbeans/modules/project/ui/api/RecentProjectsTest.java Locally Modified (Based On 1.6)