--- a/hudson/src/org/netbeans/modules/hudson/api/HudsonFolder.java
+++ a/hudson/src/org/netbeans/modules/hudson/api/HudsonFolder.java
@@ -0,0 +1,75 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle 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 2013 Sun Microsystems, Inc.
+ */
+
+package org.netbeans.modules.hudson.api;
+
+import java.util.Collection;
+import javax.swing.event.ChangeListener;
+
+/**
+ * An item which is a folder for other jobs (or subfolders).
+ * Could be any {@code ViewGroup} but probably from the CloudBees Folders plugin for Jenkins.
+ *
The current interface defines no {@code getViews} method, showing only the primary view.
+ * This is not such a loss, since heavy users of folders are not likely to need lots of views anyway.
+ * The standard connector also assumes that there is an exported {@link primaryView} field,
+ * defined in the Jenkins version of the interface.
+ *
In the Hudson/Jenkins model, this would really in an inheritance hierarchy with {@link HudsonJob} and {@link HudsonInstance},
+ * but due to the many methods in those interfaces which make no sense on folders, it seems better to separate them.
+ * @since XXX
+ */
+public interface HudsonFolder {
+
+ String getName();
+
+ String getUrl();
+
+ Collection getJobs();
+
+ Collection getFolders();
+
+ HudsonInstance getInstance();
+
+ void addChangeListener(ChangeListener listener);
+
+ void removeChangeListener(ChangeListener listener);
+
+}
--- a/hudson/src/org/netbeans/modules/hudson/api/HudsonInstance.java
+++ a/hudson/src/org/netbeans/modules/hudson/api/HudsonInstance.java
@@ -99,6 +99,9 @@
*/
public Collection getJobs();
+ /** @since XXX */
+ Collection getFolders();
+
/**
* Returns all Hudson views from registered instance
*
--- a/hudson/src/org/netbeans/modules/hudson/impl/HudsonConnector.java
+++ a/hudson/src/org/netbeans/modules/hudson/impl/HudsonConnector.java
@@ -63,6 +63,7 @@
import java.util.regex.Pattern;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.modules.hudson.api.ConnectionBuilder;
+import org.netbeans.modules.hudson.api.HudsonFolder;
import org.netbeans.modules.hudson.api.HudsonJob;
import org.netbeans.modules.hudson.api.HudsonJob.Color;
import org.netbeans.modules.hudson.api.HudsonJobBuild;
@@ -119,6 +120,7 @@
Document docInstance = getDocument(instanceUrl + XML_API_URL + (canUseTree(authentication) ?
"?tree=primaryView[name],views[name,url,jobs[name]]," +
"jobs[name,url,color,displayName,buildable,inQueue," +
+ "primaryView," + // #215135: marker for folders
"lastBuild[number],lastFailedBuild[number],lastStableBuild[number],lastSuccessfulBuild[number],lastCompletedBuild[number]," +
"modules[name,displayName,url,color]]," +
"securedJobs[name,url]" : // HUDSON-3924
@@ -131,14 +133,35 @@
if (null == docInstance) {
return new InstanceData(
Collections.emptyList(),
- Collections.emptyList());
+ Collections.emptyList(),
+ Collections.emptyList());
}
// Clear cache
cache.clear();
// Parse jobs and return them
- Collection viewsData = getViewData(docInstance);
- Collection jobsData = getJobsData(docInstance, viewsData);
- return new InstanceData(jobsData, viewsData);
+ Collection viewsData = getViewData(docInstance, instanceUrl);
+ Collection foldersData = new ArrayList();
+ Collection jobsData = getJobsData(docInstance, instanceUrl, viewsData, foldersData);
+ return new InstanceData(jobsData, viewsData, foldersData);
+ }
+
+ @Override public InstanceData getInstanceData(HudsonFolder parentFolder, boolean authentication) {
+ Document docInstance = getDocument(parentFolder.getUrl() + XML_API_URL + "?tree=jobs[name,url,color,displayName,buildable,inQueue,primaryView," +
+ "lastBuild[number],lastFailedBuild[number],lastStableBuild[number],lastSuccessfulBuild[number],lastCompletedBuild[number]," +
+ "modules[name,displayName,url,color]]," +
+ "securedJobs[name,url]", authentication); // NOI18N
+
+ if (null == docInstance) {
+ return new InstanceData(
+ Collections.emptyList(),
+ Collections.emptyList(),
+ Collections.emptyList());
+ }
+ // Clear cache
+ cache.clear();
+ Collection foldersData = new ArrayList();
+ Collection jobsData = getJobsData(docInstance, parentFolder.getUrl(), Collections.emptySet(), foldersData);
+ return new InstanceData(jobsData, Collections.emptySet(), foldersData);
}
@Override
@@ -235,7 +258,7 @@
}
- private Collection getViewData(Document doc) {
+ private Collection getViewData(Document doc, String baseUrl) {
String primaryViewName = null;
Element primaryViewEl = XMLUtil.findElement(doc.getDocumentElement(), "primaryView", null); // NOI18N
if (primaryViewEl != null) {
@@ -267,7 +290,7 @@
name = o.getFirstChild().getTextContent();
isPrimary = name.equals(primaryViewName);
} else if (o.getNodeName().equals(XML_API_URL_ELEMENT)) {
- url = normalizeUrl(o.getFirstChild().getTextContent(), isPrimary ? "" : "view/[^/]+/"); // NOI18N
+ url = normalizeUrl(baseUrl, o.getFirstChild().getTextContent(), isPrimary ? "" : "view/[^/]+/"); // NOI18N
}
}
@@ -300,8 +323,8 @@
return views;
}
- private Collection getJobsData(Document doc,
- Collection viewsData) {
+ private Collection getJobsData(Document doc, String baseUrl,
+ Collection viewsData, Collection foldersData) {
Collection jobs = new ArrayList();
NodeList nodes = doc.getDocumentElement().getChildNodes();
@@ -314,6 +337,8 @@
JobData jd = new JobData();
jd.setSecured(secured);
+ FolderData fd = new FolderData();
+ boolean isFolder = false;
NodeList jobDetails = n.getChildNodes();
for (int k = 0; k < jobDetails.getLength(); k++) {
@@ -324,8 +349,13 @@
String nodeName = d.getNodeName();
if (nodeName.equals(XML_API_NAME_ELEMENT)) {
jd.setJobName(d.getFirstChild().getTextContent());
+ fd.setName(d.getFirstChild().getTextContent());
} else if (nodeName.equals(XML_API_URL_ELEMENT)) {
- jd.setJobUrl(normalizeUrl(d.getFirstChild().getTextContent(), "job/[^/]+/")); // NOI18N
+ String u = normalizeUrl(baseUrl, d.getFirstChild().getTextContent(), "job/[^/]+/"); // NOI18N
+ jd.setJobUrl(u);
+ fd.setUrl(u);
+ } else if (nodeName.equals("primaryView")) { // NOI18N
+ isFolder = true;
} else if (nodeName.equals(XML_API_COLOR_ELEMENT)) {
jd.setColor(Color.find(d.getFirstChild().getTextContent().trim()));
} else if (nodeName.equals(XML_API_DISPLAY_NAME_ELEMENT)) {
@@ -362,7 +392,7 @@
} else if (nodeName2.equals("displayName")) { // NOI18N
displayName = text;
} else if (nodeName2.equals("url")) { // NOI18N
- url = normalizeUrl(text, "job/[^/]+/[^/]+/"); // NOI18N
+ url = normalizeUrl(baseUrl, text, "job/[^/]+/[^/]+/"); // NOI18N
} else if (nodeName2.equals("color")) { // NOI18N
color = Color.find(text);
} else {
@@ -391,7 +421,11 @@
jd.addView(v.getName());
}
}
- jobs.add(jd);
+ if (isFolder) {
+ foldersData.add(fd);
+ } else {
+ jobs.add(jd);
+ }
}
return jobs;
}
@@ -403,7 +437,7 @@
* @return analogous URL constructed from instance root, e.g. {@code https://my.facade/hudson/job/My%20Job/}
* @see "#165735"
*/
- private String normalizeUrl(String suggested, String relativePattern) {
+ private static String normalizeUrl(String instanceUrl, String suggested, String relativePattern) {
Pattern tailPattern;
synchronized (tailPatterns) {
tailPattern = tailPatterns.get(relativePattern);
@@ -620,4 +654,5 @@
}
return items;
}
+
}
--- a/hudson/src/org/netbeans/modules/hudson/impl/HudsonFolderImpl.java
+++ a/hudson/src/org/netbeans/modules/hudson/impl/HudsonFolderImpl.java
@@ -0,0 +1,131 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle 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 2013 Sun Microsystems, Inc.
+ */
+
+package org.netbeans.modules.hudson.impl;
+
+import java.util.Collection;
+import javax.swing.event.ChangeListener;
+import org.netbeans.modules.hudson.api.HudsonChangeListener;
+import org.netbeans.modules.hudson.api.HudsonFolder;
+import org.netbeans.modules.hudson.api.HudsonInstance;
+import org.netbeans.modules.hudson.api.HudsonJob;
+import org.netbeans.modules.hudson.spi.BuilderConnector;
+import org.netbeans.modules.hudson.ui.interfaces.OpenableInBrowser;
+import org.openide.util.ChangeSupport;
+import org.openide.util.WeakListeners;
+
+public class HudsonFolderImpl implements HudsonFolder, OpenableInBrowser, HudsonChangeListener {
+
+ private final ChangeSupport cs = new ChangeSupport(this);
+ private final HudsonInstanceImpl instance;
+ private final String name, url;
+ private BuilderConnector.InstanceData children;
+ private Collection jobs;
+ private Collection folders;
+
+ HudsonFolderImpl(HudsonInstanceImpl instance, String name, String url) {
+ this.instance = instance;
+ this.name = name;
+ this.url = url;
+ instance.addHudsonChangeListener(WeakListeners.create(HudsonChangeListener.class, this, instance));
+ }
+
+ @Override public String getName() {
+ return name;
+ }
+
+ @Override public String getUrl() {
+ return url;
+ }
+
+ private synchronized void load() {
+ if (children == null) {
+ children = instance.getBuilderConnector().getInstanceData(this, true);
+ jobs = instance.createJobs(children.getJobsData());
+ folders = instance.createFolders(children.getFoldersData());
+ }
+ }
+
+ @Override public Collection getJobs() {
+ load();
+ return jobs;
+ }
+
+ @Override public Collection getFolders() {
+ load();
+ return folders;
+ }
+
+ @Override public HudsonInstance getInstance() {
+ return instance;
+ }
+
+ @Override public String toString() {
+ return url;
+ }
+
+ @Override public boolean equals(Object obj) {
+ return obj instanceof HudsonFolderImpl && ((HudsonFolderImpl) obj).url.equals(url);
+ }
+
+ @Override public int hashCode() {
+ return url.hashCode();
+ }
+
+ @Override public void addChangeListener(ChangeListener listener) {
+ cs.addChangeListener(listener);
+ }
+
+ @Override public void removeChangeListener(ChangeListener listener) {
+ cs.removeChangeListener(listener);
+ }
+
+ @Override public void stateChanged() {
+ synchronized (this) {
+ children = null;
+ }
+ cs.fireChange();
+ }
+
+ @Override public void contentChanged() {}
+
+}
--- a/hudson/src/org/netbeans/modules/hudson/impl/HudsonInstanceImpl.java
+++ a/hudson/src/org/netbeans/modules/hudson/impl/HudsonInstanceImpl.java
@@ -64,6 +64,7 @@
import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.modules.hudson.api.HudsonChangeAdapter;
import org.netbeans.modules.hudson.api.HudsonChangeListener;
+import org.netbeans.modules.hudson.api.HudsonFolder;
import org.netbeans.modules.hudson.api.HudsonInstance;
import org.netbeans.modules.hudson.api.HudsonJob;
import org.netbeans.modules.hudson.api.HudsonJobBuild;
@@ -107,6 +108,7 @@
private final Task synchronization;
private Collection jobs = new ArrayList();
+ private Collection folders = new ArrayList();
private Collection views = new ArrayList();
private HudsonView primaryView;
private final Collection listeners = new ArrayList();
@@ -220,6 +222,7 @@
forbidden = false;
version = null;
jobs.clear();
+ folders.clear();
views.clear();
primaryView = null;
@@ -239,6 +242,7 @@
assert !(connector instanceof HudsonConnector);
this.builderConnector = connector;
this.jobs.clear();
+ folders.clear();
this.views.clear();
synchronize(false);
}
@@ -273,6 +277,10 @@
return jobs;
}
+ @Override public synchronized Collection getFolders() {
+ return folders;
+ }
+
boolean isSalient(HudsonJobImpl job) {
HudsonInstanceProperties props = getProperties();
if (HudsonInstanceProperties.split(props.get(INSTANCE_SUPPRESSED_JOBS)).contains(job.getName())) {
@@ -377,6 +385,7 @@
configureViews(instanceData.getViewsData());
Collection retrieved = createJobs(
instanceData.getJobsData());
+ Collection retrievedFolders = createFolders(instanceData.getFoldersData());
// Exit when instance is terminated
if (terminated) {
@@ -405,12 +414,13 @@
}
// When there are no changes return and do not fire changes
- if (getJobs().equals(retrieved) && oldViews.equals(getViews())) {
+ if (jobs.equals(retrieved) && folders.equals(retrievedFolders) && oldViews.equals(getViews())) {
return;
}
// Update jobs
jobs = retrieved;
+ folders = retrievedFolders;
// Fire all changes
fireContentChanges();
@@ -551,6 +561,14 @@
return jobList;
}
+ public Collection createFolders(Collection foldersData) {
+ Collection result = new ArrayList();
+ for (BuilderConnector.FolderData datum : foldersData) {
+ result.add(new HudsonFolderImpl(this, datum.getName(), datum.getUrl()));
+ }
+ return result;
+ }
+
private void configureViews(Collection viewsData) {
Collection viewList = new ArrayList();
--- a/hudson/src/org/netbeans/modules/hudson/spi/BuilderConnector.java
+++ a/hudson/src/org/netbeans/modules/hudson/spi/BuilderConnector.java
@@ -42,12 +42,14 @@
package org.netbeans.modules.hudson.spi;
import java.util.Collection;
+import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.NonNull;
+import org.netbeans.modules.hudson.api.HudsonFolder;
import org.netbeans.modules.hudson.api.HudsonJob;
import org.netbeans.modules.hudson.api.HudsonJob.Color;
import org.netbeans.modules.hudson.api.HudsonJobBuild;
@@ -76,6 +78,15 @@
boolean authentication);
/**
+ * Like {@link #getInstanceData(boolean)} but gets the contents of a folder rather than top level.
+ * Consider abstract; the default implementation produces an empty result.
+ * @since XXX
+ */
+ public /*abstract*/ @NonNull InstanceData getInstanceData(@NonNull HudsonFolder parentFolder, boolean authentication) {
+ return new InstanceData(Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
+ }
+
+ /**
* Get builds for the specified job.
*/
public abstract @NonNull Collection getJobBuildsData(
@@ -336,6 +347,30 @@
}
}
+ /** @since XXX */
+ public static final class FolderData {
+
+ private String name;
+ private String url;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ }
+
/**
* Type for storing build module data
*/
@@ -428,10 +463,18 @@
private Collection jobsData;
private Collection viewsData;
+ private final Collection foldersData;
+ @Deprecated
public InstanceData(Collection jobsData, Collection viewsData) {
+ this(jobsData, viewsData, Collections.emptySet());
+ }
+
+ /** @since XXX */
+ public InstanceData(Collection jobsData, Collection viewsData, Collection foldersData) {
this.jobsData = jobsData;
this.viewsData = viewsData;
+ this.foldersData = foldersData;
}
public Collection getJobsData() {
@@ -441,5 +484,11 @@
public Collection getViewsData() {
return viewsData;
}
+
+ /** @since XXX */
+ public Collection getFoldersData() {
+ return foldersData;
+ }
+
}
}
--- a/hudson/src/org/netbeans/modules/hudson/ui/nodes/HudsonFolderNode.java
+++ a/hudson/src/org/netbeans/modules/hudson/ui/nodes/HudsonFolderNode.java
@@ -0,0 +1,125 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle 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 2013 Sun Microsystems, Inc.
+ */
+
+package org.netbeans.modules.hudson.ui.nodes;
+
+import java.awt.Image;
+import java.util.ArrayList;
+import java.util.List;
+import javax.swing.Action;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import org.netbeans.modules.hudson.api.HudsonFolder;
+import org.netbeans.modules.hudson.api.HudsonJob;
+import org.netbeans.modules.hudson.ui.actions.OpenUrlAction;
+import org.netbeans.modules.hudson.ui.interfaces.OpenableInBrowser;
+import org.openide.filesystems.FileUtil;
+import org.openide.loaders.DataFolder;
+import org.openide.nodes.AbstractNode;
+import org.openide.nodes.ChildFactory;
+import org.openide.nodes.Children;
+import org.openide.nodes.Node;
+import org.openide.util.Union2;
+import org.openide.util.WeakListeners;
+import org.openide.util.lookup.Lookups;
+
+class HudsonFolderNode extends AbstractNode {
+
+ private static final Node iconDelegate = DataFolder.findFolder(FileUtil.getConfigRoot()).getNodeDelegate();
+
+ private final HudsonFolder folder;
+
+ HudsonFolderNode(HudsonFolder folder) {
+ super(Children.create(new HudsonFolderChildren(folder), true), Lookups.singleton(folder));
+ this.folder = folder;
+ setName(folder.getName());
+ }
+
+ public @Override Image getIcon(int type) {
+ return iconDelegate.getIcon(type);
+ }
+
+ public @Override Image getOpenedIcon(int type) {
+ return iconDelegate.getOpenedIcon(type);
+ }
+
+ @Override public Action[] getActions(boolean context) {
+ List actions = new ArrayList();
+ if (folder instanceof OpenableInBrowser) {
+ actions.add(OpenUrlAction.forOpenable((OpenableInBrowser) folder));
+ }
+ return actions.toArray(new Action[actions.size()]);
+ }
+
+ private static final class HudsonFolderChildren extends ChildFactory.Detachable> implements ChangeListener {
+
+ private final HudsonFolder folder;
+
+ HudsonFolderChildren(HudsonFolder folder) {
+ this.folder = folder;
+ }
+
+ @Override protected boolean createKeys(List> toPopulate) {
+ for (HudsonFolder subfolder : folder.getFolders()) {
+ toPopulate.add(Union2.createSecond(subfolder));
+ }
+ for (HudsonJob job : folder.getJobs()) {
+ toPopulate.add(Union2.createFirst(job));
+ }
+ return true;
+ }
+
+ @Override protected Node createNodeForKey(Union2 key) {
+ return key.hasFirst() ? new HudsonJobNode(key.first()) : new HudsonFolderNode(key.second());
+ }
+
+ @Override protected void addNotify() {
+ folder.addChangeListener(WeakListeners.change(this, folder));
+ }
+
+ @Override public void stateChanged(ChangeEvent e) {
+ refresh(false);
+ }
+
+ }
+
+}
--- a/hudson/src/org/netbeans/modules/hudson/ui/nodes/HudsonInstanceNode.java
+++ a/hudson/src/org/netbeans/modules/hudson/ui/nodes/HudsonInstanceNode.java
@@ -47,11 +47,13 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.LinkedList;
import java.util.List;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import javax.swing.Action;
import org.netbeans.modules.hudson.api.HudsonChangeListener;
+import org.netbeans.modules.hudson.api.HudsonFolder;
import org.netbeans.modules.hudson.api.HudsonInstance;
import org.netbeans.modules.hudson.api.HudsonJob;
import org.netbeans.modules.hudson.api.HudsonJob.Color;
@@ -65,6 +67,7 @@
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.util.NbBundle.Messages;
+import org.openide.util.Union2;
import org.openide.util.lookup.Lookups;
/**
@@ -195,7 +198,7 @@
*/
public static final String SELECTED_VIEW = "view"; // NOI18N
- private static class InstanceNodeChildren extends Children.Keys implements HudsonChangeListener {
+ private static class InstanceNodeChildren extends Children.Keys> implements HudsonChangeListener {
private final HudsonInstanceImpl instance;
@@ -209,15 +212,15 @@
});
}
- @Override protected Node[] createNodes(HudsonJob job) {
- return new Node[] {new HudsonJobNode(job)};
+ @Override protected Node[] createNodes(Union2 item) {
+ return new Node[] {item.hasFirst() ? new HudsonJobNode(item.first()) : new HudsonFolderNode(item.second())};
}
@Override
protected void addNotify() {
super.addNotify();
if (!instance.isConnected()/* && seems undesirable: !instance.isForbidden()*/) {
- setKeys(Collections.emptySet());
+ setKeys(Collections.>emptySet());
instance.synchronize(true);
} else {
refreshKeys();
@@ -226,7 +229,7 @@
@Override
protected void removeNotify() {
- setKeys(Collections.emptySet());
+ setKeys(Collections.>emptySet());
super.removeNotify();
}
@@ -249,7 +252,15 @@
jobs.add(job);
}
Collections.sort(jobs);
- setKeys(jobs);
+ List> items = new LinkedList>();
+ for (HudsonFolder folder : instance.getFolders()) {
+ // XXX ideally should restrict by selected view, like jobs
+ items.add(Union2.createSecond(folder));
+ }
+ for (HudsonJob job : jobs) {
+ items.add(Union2.createFirst(job));
+ }
+ setKeys(items);
}
@Override public void stateChanged() {