Skip to content

Commit ab22637

Browse files
committed
Merge branch 'poms'
This branch adds a getAllPOMs() utility method to org.scijava.util.POM which searches JARs on the classpath for Maven POM metadata. It also improves the POM class in various other ways, including the addition of a constructor that takes a URL, more accessor methods, and making POMs naturally comparable by GAV.
2 parents 30e577d + d03cfac commit ab22637

File tree

3 files changed

+280
-10
lines changed

3 files changed

+280
-10
lines changed

src/main/java/org/scijava/util/POM.java

Lines changed: 169 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
import java.io.IOException;
3636
import java.io.InputStream;
3737
import java.net.URL;
38+
import java.util.ArrayList;
39+
import java.util.Comparator;
40+
import java.util.Enumeration;
41+
import java.util.List;
3842

3943
import javax.xml.parsers.ParserConfigurationException;
4044

@@ -45,7 +49,7 @@
4549
*
4650
* @author Curtis Rueden
4751
*/
48-
public class POM extends XML {
52+
public class POM extends XML implements Comparable<POM> {
4953

5054
/** Parses a POM from the given file. */
5155
public POM(final File file) throws ParserConfigurationException,
@@ -54,6 +58,13 @@ public POM(final File file) throws ParserConfigurationException,
5458
super(file);
5559
}
5660

61+
/** Parses a POM from the given URL. */
62+
public POM(final URL url) throws ParserConfigurationException, SAXException,
63+
IOException
64+
{
65+
super(url);
66+
}
67+
5768
/** Parses a POM from the given input stream. */
5869
public POM(final InputStream in) throws ParserConfigurationException,
5970
SAXException, IOException
@@ -62,8 +73,8 @@ public POM(final InputStream in) throws ParserConfigurationException,
6273
}
6374

6475
/** Parses a POM from the given string. */
65-
public POM(final String s) throws ParserConfigurationException,
66-
SAXException, IOException
76+
public POM(final String s) throws ParserConfigurationException, SAXException,
77+
IOException
6778
{
6879
super(s);
6980
}
@@ -89,6 +100,52 @@ public String getVersion() {
89100
return cdata("//project/parent/version");
90101
}
91102

103+
/** Gets the project name. */
104+
public String getProjectName() {
105+
return cdata("//project/name");
106+
}
107+
108+
/** Gets the project description. */
109+
public String getProjectDescription() {
110+
return cdata("//project/description");
111+
}
112+
113+
/** Gets the project URL. */
114+
public String getProjectURL() {
115+
return cdata("//project/url");
116+
}
117+
118+
/** Gets the project inception year. */
119+
public String getProjectInceptionYear() {
120+
return cdata("//project/inceptionYear");
121+
}
122+
123+
/** Gets the organization name. */
124+
public String getOrganizationName() {
125+
return cdata("//project/organization/name");
126+
}
127+
128+
/** Gets the organization URL. */
129+
public String getOrganizationURL() {
130+
return cdata("//project/organization/url");
131+
}
132+
133+
// -- Comparable methods --
134+
135+
@Override
136+
public int compareTo(final POM pom) {
137+
// sort by groupId first
138+
final int gid = getGroupId().compareTo(pom.getGroupId());
139+
if (gid != 0) return gid;
140+
141+
// sort by artifactId second
142+
final int aid = getArtifactId().compareTo(pom.getArtifactId());
143+
if (aid != 0) return aid;
144+
145+
// finally, sort by version
146+
return compareVersions(getVersion(), pom.getVersion());
147+
}
148+
92149
// -- Utility methods --
93150

94151
/**
@@ -103,13 +160,15 @@ public static POM getPOM(final Class<?> c, final String groupId,
103160
{
104161
try {
105162
final URL location = ClassUtils.getLocation(c);
106-
if (!location.getProtocol().equals("file") || location.toString().endsWith(".jar")) {
163+
if (!location.getProtocol().equals("file") ||
164+
location.toString().endsWith(".jar"))
165+
{
107166
// look for pom.xml in JAR's META-INF/maven subdirectory
108167
final String pomPath =
109168
"META-INF/maven/" + groupId + "/" + artifactId + "/pom.xml";
110-
final URL pomURL = new URL("jar:" + location.toString() + "!/" + pomPath);
111-
final InputStream pomStream = pomURL.openStream();
112-
return new POM(pomStream);
169+
final URL pomURL =
170+
new URL("jar:" + location.toString() + "!/" + pomPath);
171+
return new POM(pomURL);
113172
}
114173
// look for the POM in the class's base directory
115174
final File file = FileUtils.urlToFile(location);
@@ -128,4 +187,107 @@ public static POM getPOM(final Class<?> c, final String groupId,
128187
}
129188
}
130189

190+
/** Gets all available Maven POMs on the class path. */
191+
public static List<POM> getAllPOMs() {
192+
// find all META-INF/maven/ folders on the classpath
193+
final String pomPrefix = "META-INF/maven/";
194+
final ClassLoader classLoader =
195+
Thread.currentThread().getContextClassLoader();
196+
final Enumeration<URL> resources;
197+
try {
198+
resources = classLoader.getResources(pomPrefix);
199+
}
200+
catch (final IOException exc) {
201+
return null;
202+
}
203+
204+
final ArrayList<POM> poms = new ArrayList<POM>();
205+
206+
// recursively list contents of META-INF/maven/ directories
207+
for (final URL resource : new IteratorPlus<URL>(resources)) {
208+
for (final URL url : FileUtils.listContents(resource)) {
209+
// look for pom.xml files amongst the contents
210+
if (url.getPath().endsWith("/pom.xml")) {
211+
try {
212+
poms.add(new POM(url));
213+
}
214+
catch (final IOException exc) {
215+
// ignore and continue
216+
}
217+
catch (final ParserConfigurationException exc) {
218+
// ignore and continue
219+
}
220+
catch (final SAXException exc) {
221+
// ignore and continue
222+
}
223+
}
224+
}
225+
}
226+
227+
return poms;
228+
}
229+
230+
/**
231+
* Compares two version strings.
232+
* <p>
233+
* Given the variation between versioning styles, there is no single
234+
* comparison method that can possibly be correct 100% of the time. So this
235+
* method works on a "best effort" basis; YMMV.
236+
* </p>
237+
* <p>
238+
* The algorithm is as follows:
239+
* </p>
240+
* <ul>
241+
* <li>Split on non-alphameric characters.</li>
242+
* <li>Compare each token one by one.</li>
243+
* <li>Comparison is numerical when possible (i.e., when an integer can be
244+
* parsed from the token), and lexicographic otherwise.</li>
245+
* <li>If one version string runs out of tokens, the version with additional
246+
* tokens remaining is considered <em>greater than</em> the version without
247+
* additional tokens.</li>
248+
* <li>There is one exception: if two version strings are identical except
249+
* that one has a suffix beginning with a dash ({@code -}), the version with
250+
* suffix will be considered <em>less than</em> the one without a suffix. The
251+
* reason for this is to accommodate the <a
252+
* href="http://semver.org/">SemVer</a> versioning scheme's usage of
253+
* "prerelease" version suffixes. For example, {@code 2.0.0} will compare
254+
* greater than {@code 2.0.0-beta-1}, whereas {@code 2.0.0} will compare less
255+
* than {@code 2.0.0.1}.</li>
256+
* </ul>
257+
*
258+
* @return a negative integer, zero, or a positive integer as the first
259+
* argument is less than, equal to, or greater than the second.
260+
* @see Comparator#compare(Object, Object)
261+
*/
262+
public static int compareVersions(final String v1, final String v2) {
263+
final String[] t1 = v1.split("[^\\w]");
264+
final String[] t2 = v2.split("[^\\w]");
265+
final int size = Math.min(t1.length, t2.length);
266+
for (int i = 0; i < size; i++) {
267+
try {
268+
final long n1 = Long.parseLong(t1[i]);
269+
final long n2 = Long.parseLong(t2[i]);
270+
if (n1 < n2) return -1;
271+
if (n1 > n2) return 1;
272+
}
273+
catch (final NumberFormatException exc) {
274+
final int result = t1[i].compareTo(t2[i]);
275+
if (result != 0) return result;
276+
}
277+
}
278+
if (t1.length == t2.length) return 0; // versions match
279+
280+
// check for SemVer prerelease versions
281+
if (v1.startsWith(v2) && v1.charAt(v2.length()) == '-') {
282+
// v1 is a prerelease version of v2
283+
return -1;
284+
}
285+
if (v2.startsWith(v1) && v2.charAt(v1.length()) == '-') {
286+
// v2 is a prerelease version of v1
287+
return 1;
288+
}
289+
290+
return t1.length < t2.length ? -1 : 1;
291+
}
292+
131293
}

src/main/java/org/scijava/util/XML.java

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import java.io.InputStream;
3939
import java.io.PrintStream;
4040
import java.io.StringWriter;
41+
import java.net.URL;
4142

4243
import javax.xml.parsers.DocumentBuilder;
4344
import javax.xml.parsers.DocumentBuilderFactory;
@@ -66,6 +67,9 @@
6667
*/
6768
public class XML {
6869

70+
/** Path to the XML document (e.g., a file or URL). */
71+
private final String path;
72+
6973
/** The parsed XML DOM. */
7074
private final Document doc;
7175

@@ -76,31 +80,49 @@ public class XML {
7680
public XML(final File file) throws ParserConfigurationException,
7781
SAXException, IOException
7882
{
79-
this(loadXML(file));
83+
this(file.getAbsolutePath(), loadXML(file));
84+
}
85+
86+
/** Parses XML from the given URL. */
87+
public XML(final URL url) throws ParserConfigurationException,
88+
SAXException, IOException
89+
{
90+
this(url.getPath(), loadXML(url));
8091
}
8192

8293
/** Parses XML from the given input stream. */
8394
public XML(final InputStream in) throws ParserConfigurationException,
8495
SAXException, IOException
8596
{
86-
this(loadXML(in));
97+
this(null, loadXML(in));
8798
}
8899

89100
/** Parses XML from the given string. */
90101
public XML(final String s) throws ParserConfigurationException,
91102
SAXException, IOException
92103
{
93-
this(loadXML(s));
104+
this(null, loadXML(s));
94105
}
95106

96107
/** Creates an XML object for an existing document. */
97108
public XML(final Document doc) {
109+
this(null, doc);
110+
}
111+
112+
/** Creates an XML object for an existing document. */
113+
public XML(final String path, final Document doc) {
114+
this.path = path;
98115
this.doc = doc;
99116
xpath = XPathFactory.newInstance().newXPath();
100117
}
101118

102119
// -- XML methods --
103120

121+
/** Gets the path to the XML document, or null if none. */
122+
public String getPath() {
123+
return path;
124+
}
125+
104126
/** Gets the XML's DOM representation. */
105127
public Document getDocument() {
106128
return doc;
@@ -150,6 +172,16 @@ private static Document loadXML(final File file)
150172
return createBuilder().parse(file.getAbsolutePath());
151173
}
152174

175+
/** Loads an XML document from the given URL. */
176+
private static Document loadXML(final URL url)
177+
throws ParserConfigurationException, SAXException, IOException
178+
{
179+
final InputStream in = url.openStream();
180+
final Document document = loadXML(in);
181+
in.close();
182+
return document;
183+
}
184+
153185
/** Loads an XML document from the given input stream. */
154186
protected static Document loadXML(final InputStream in)
155187
throws ParserConfigurationException, SAXException, IOException
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* #%L
3+
* SciJava Common shared library for SciJava software.
4+
* %%
5+
* Copyright (C) 2009 - 2014 Board of Regents of the University of
6+
* Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
7+
* Institute of Molecular Cell Biology and Genetics.
8+
* %%
9+
* Redistribution and use in source and binary forms, with or without
10+
* modification, are permitted provided that the following conditions are met:
11+
*
12+
* 1. Redistributions of source code must retain the above copyright notice,
13+
* this list of conditions and the following disclaimer.
14+
* 2. Redistributions in binary form must reproduce the above copyright notice,
15+
* this list of conditions and the following disclaimer in the documentation
16+
* and/or other materials provided with the distribution.
17+
*
18+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
22+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28+
* POSSIBILITY OF SUCH DAMAGE.
29+
* #L%
30+
*/
31+
32+
package org.scijava.util;
33+
34+
import static org.junit.Assert.assertTrue;
35+
36+
import org.junit.Test;
37+
38+
/**
39+
* Tests methods of {@link POM}.
40+
*
41+
* @author Curtis Rueden
42+
*/
43+
public class POMTest {
44+
45+
@Test
46+
public void testCompareVersions() {
47+
// basic checks
48+
assertTrue(POM.compareVersions("1", "2") < 0);
49+
assertTrue(POM.compareVersions("1.0", "2.0") < 0);
50+
assertTrue(POM.compareVersions("1.0", "1.1") < 0);
51+
assertTrue(POM.compareVersions("1.0.0", "2.0.0") < 0);
52+
53+
// sanity checks for argument order
54+
assertTrue(POM.compareVersions("2", "1") > 0);
55+
assertTrue(POM.compareVersions("2.0", "1.0") > 0);
56+
assertTrue(POM.compareVersions("1.1", "1.0") > 0);
57+
assertTrue(POM.compareVersions("2.0.0", "1.0.0") > 0);
58+
59+
// more complex/unusual checks
60+
assertTrue(POM.compareVersions("1.0-RC1", "1.0-RC2") < 0);
61+
assertTrue(POM.compareVersions("1.0-RC-1", "1.0-RC-2") < 0);
62+
assertTrue(POM.compareVersions("1.0-RC-2", "1.0-RC-10") < 0);
63+
assertTrue(POM.compareVersions("0.4-alpha", "0.4-beta") < 0);
64+
assertTrue(POM.compareVersions("foo", "bar") > 0);
65+
66+
// checks which expose bugs/limitations
67+
assertTrue(POM.compareVersions("1.0-RC2", "1.0-RC10") > 0);
68+
assertTrue(POM.compareVersions("1.0-rc1", "1.0-RC2") > 0);
69+
70+
// check that varying numbers of digits are handled properly
71+
assertTrue(POM.compareVersions("2.0.0", "2.0.0.1") < 0);
72+
// check that SemVer prerelease versions are handled properly
73+
assertTrue(POM.compareVersions("2.0.0", "2.0.0-beta-1") > 0);
74+
}
75+
76+
}

0 commit comments

Comments
 (0)