# HG changeset patch # User Jaroslav Havlin # Parent 572cab5760bd1f347c0d59d7d0d259887c185622 #215135: ProjectHudsonProvider.Association supports jobs nested in folders diff --git a/hudson/manifest.mf b/hudson/manifest.mf --- a/hudson/manifest.mf +++ b/hudson/manifest.mf @@ -2,5 +2,5 @@ OpenIDE-Module: org.netbeans.modules.hudson OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/hudson/Bundle.properties OpenIDE-Module-Layer: org/netbeans/modules/hudson/layer.xml -OpenIDE-Module-Specification-Version: 1.31 +OpenIDE-Module-Specification-Version: 1.32 diff --git a/hudson/src/org/netbeans/modules/hudson/spi/ProjectHudsonProvider.java b/hudson/src/org/netbeans/modules/hudson/spi/ProjectHudsonProvider.java --- a/hudson/src/org/netbeans/modules/hudson/spi/ProjectHudsonProvider.java +++ b/hudson/src/org/netbeans/modules/hudson/spi/ProjectHudsonProvider.java @@ -45,6 +45,8 @@ package org.netbeans.modules.hudson.spi; import java.net.URI; +import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; @@ -54,6 +56,7 @@ import org.netbeans.api.annotations.common.CheckForNull; import org.netbeans.api.project.Project; import org.netbeans.api.project.ui.OpenProjects; +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.impl.HudsonManagerImpl; @@ -145,7 +148,9 @@ public static final class Association { private final String serverURL; + private final String jobURL; private final String jobName; + private final String[] jobPath; /** * Creates an association. @@ -165,7 +170,74 @@ throw new IllegalArgumentException("No slashes permitted in job name: " + jobName); } this.serverURL = serverURL; + this.jobURL = getStandardJobUrl(serverURL, jobName); this.jobName = jobName; + this.jobPath = new String[]{jobName}; + } + + private static String getStandardJobUrl(String serverURL, String jobName) { + return jobName != null + ? serverURL + "job/" + Utilities.uriEncode(jobName) + "/" // NOI18N + : serverURL; + } + + /** + * Creates an association. + * + * @param job URL + * @throws IllegalArgumentException if parameter has invalid syntax + * @since hudson/1.32 + */ + public Association(String jobURL) throws IllegalArgumentException { + URI.create(jobURL); // check syntax + if (!jobURL.endsWith("/")) { // NOI18N + throw new IllegalArgumentException(jobURL + " must end in a slash"); // NOI18N + } + this.jobURL = jobURL; + this.serverURL = extractServerURL(jobURL); + this.jobPath = extractJobPath(jobURL); + this.jobName = jobPath != null + ? jobPath[jobPath.length - 1] + : null; + } + + private static String extractServerURL(String jobURL) throws IllegalArgumentException { + Matcher m = Pattern.compile("(https?://.+?/)(job/([^/]+)/)*").matcher(jobURL); // NOI18N + if (!m.matches()) { + throw new IllegalArgumentException("Cannot extract server URL: " + jobURL); //NOI18N + } else { + m.group(1); + return m.group(1); + } + } + + private static String[] extractJobPath(String jobURL) throws IllegalArgumentException { + Matcher m = Pattern.compile("(?:https?://.+?/)(?:[^/]+/)*?((?:job/(?:[^/]+)/)*)").matcher(jobURL); // NOI18N + if (!m.matches()) { + throw new IllegalArgumentException("Cannot extract job path: " + jobURL); //NOI18N + } else { + String rawPath = m.group(1); + if (rawPath == null || rawPath.isEmpty()) { + return null; + } else { + String[] elements = rawPath.split("/"); + assert elements.length > 0 && (elements.length % 2) == 0; + String[] result = new String[elements.length / 2]; + for (int i = 0; i < elements.length; i++) { + String element = elements[i]; + if (i % 2 == 0) { + assert "job".equals(element); + } else { + String decoded = Utilities.uriDecode(element); + if (decoded.trim().isEmpty()) { + throw new IllegalArgumentException("Empty job name: " + jobURL); //NOI18N + } + result[i / 2] = decoded; + } + } + return result; + } + } } /** @@ -174,7 +246,7 @@ * @return an association with the same server URL and job name */ public static Association forJob(HudsonJob job) { - return new Association(job.getInstance().getUrl(), job.getName()); + return new Association(job.getUrl()); } /** @@ -192,19 +264,71 @@ } /** + * Get copy of job path. Not private - called from tests. + */ + String[] getJobPath() { + return Arrays.copyOf(jobPath, jobPath.length); + } + + /** * Finds the corresponding job on a registered server, if any. * @return a job with the name {@link #getJobName} on the server with the same {@link #getServerUrl}, or null */ public @CheckForNull HudsonJob getJob() { - if (jobName == null) { + if (jobPath == null) { return null; } HudsonInstance instance = HudsonManagerImpl.getDefault().getInstance(serverURL); if (instance == null) { return null; } - for (HudsonJob job : instance.getJobs()) { - if (job.getName().equals(jobName)) { + if (jobPath.length == 1) { + for (HudsonJob job : instance.getJobs()) { + if (job.getName().equals(jobName)) { + return job; + } + } + return null; + } else { + HudsonFolder lastFolder = null; + for (int i = 0; i < jobPath.length; i++) { + String name = jobPath[i]; + if (i == 0) { + lastFolder = findFolderByName(instance.getFolders(), name); + } else if (i < jobPath.length - 1 && lastFolder != null) { + lastFolder = findFolderByName(lastFolder.getFolders(), name); + } else if (lastFolder != null) { + return findJobByName(lastFolder.getJobs(), name); + } + } + } + return null; + } + + /** + * Find a folder of specified name in a collection of folders. + * + * @return The folder with name {@code name}, or null if not found. + */ + private HudsonFolder findFolderByName(Collection folders, + String name) { + for (HudsonFolder folder : folders) { + if (name.equals(folder.getName())) { + return folder; + } + } + return null; + } + + /** + * Find a job of specified name in a collection of jobs. + * + * @return The job with name {@code name}, or null if not found. + */ + private HudsonJob findJobByName(Collection jobs, + String name) { + for (HudsonJob job : jobs) { + if (name.equals(job.getName())) { return job; } } @@ -226,7 +350,7 @@ * URL of either job or server root. */ public @Override String toString() { - return jobName != null ? serverURL + "job/" + Utilities.uriEncode(jobName) + "/" : serverURL; // NOI18N + return jobURL; } /** @@ -234,13 +358,8 @@ * @return an association based on parsing a Hudson job or root URL, or null */ public static Association fromString(String s) { - Matcher m = Pattern.compile("(https?://.+?/)(?:job/([^/]+)/?)?").matcher(s); // NOI18N - if (!m.matches()) { - return null; - } - String jobNameRaw = m.group(2); try { - return new Association(m.group(1), jobNameRaw != null ? Utilities.uriDecode(jobNameRaw) : null); + return new Association(s); } catch (IllegalArgumentException x) { Logger.getLogger(ProjectHudsonProvider.class.getName()).log(Level.WARNING, "Bad URL: {0}", s); return null; diff --git a/hudson/test/unit/src/org/netbeans/modules/hudson/spi/ProjectHudsonProviderTest.java b/hudson/test/unit/src/org/netbeans/modules/hudson/spi/ProjectHudsonProviderTest.java --- a/hudson/test/unit/src/org/netbeans/modules/hudson/spi/ProjectHudsonProviderTest.java +++ b/hudson/test/unit/src/org/netbeans/modules/hudson/spi/ProjectHudsonProviderTest.java @@ -76,4 +76,16 @@ assertEquals("Some Job", Association.fromString("http://nowhere.net/hudson/view/someview/job/Some%20Job/").getJobName()); } + public void testAssociationOfHudsonJobsInFolderHierarchy() { + Association a = Association.fromString( + "http://localhost:8080/app/hudson/job/folder%201/job/folder%201a/job/TestJob/"); + assertEquals("http://localhost:8080/app/hudson/", a.getServerUrl()); + assertEquals("TestJob", a.getJobName()); + String[] expectedPath = {"folder 1", "folder 1a", "TestJob"}; + assertEquals("Job path lenght is wrong", + expectedPath.length, a.getJobPath().length); + for (int i = 0; i < expectedPath.length; i++) { + assertEquals(expectedPath[i], a.getJobPath()[i]); + } + } }