# HG changeset patch # User Joshua Matsuoka # Date 1485450891 18000 # Node ID 006ac47d1506c4dd70cde7c76236ff1cb7b0d0f6 # Parent 8026471cdbea9f44cd1318b542b384ca357cdd17 Fix Version Checks in Dependency Analysis Fixes the dependency analysis code in Launcher and the Dependency analyzer command - Parses version strings from the OSGI manifest - When building the dependency graph, ensures that the correct versions of each bundle are used Reviewed-by: neugens, jkang Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2016-December/021832.html diff -r 8026471cdbea -r 006ac47d1506 dependency-tool/command/src/main/java/com/redhat/thermostat/tools/dependency/internal/Dependency.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dependency-tool/command/src/main/java/com/redhat/thermostat/tools/dependency/internal/Dependency.java Thu Jan 26 12:14:51 2017 -0500 @@ -0,0 +1,82 @@ +/* + * Copyright 2012-2017 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.tools.dependency.internal; + +public class Dependency { + + private String name; + private String version; + + public static final String NO_VERSION = "0"; + + public Dependency(String bundleName, String bundleVersion) { + this.name = String.valueOf(bundleName); + this.version = String.valueOf(bundleVersion); + } + + public String getName() { + return name; + } + + public String getVersion() { + return version; + } + + @Override + public String toString() { + return name + " version " + version; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (other == null) { + return false; + } + + if (getClass() != other.getClass()) { + return false; + } + + Dependency x = (Dependency) other; + return name.equals(x.name) && version.equals(x.version); + } + +} diff -r 8026471cdbea -r 006ac47d1506 dependency-tool/command/src/main/java/com/redhat/thermostat/tools/dependency/internal/DependencyGraphBuilder.java --- a/dependency-tool/command/src/main/java/com/redhat/thermostat/tools/dependency/internal/DependencyGraphBuilder.java Thu Jan 26 11:10:27 2017 -0500 +++ b/dependency-tool/command/src/main/java/com/redhat/thermostat/tools/dependency/internal/DependencyGraphBuilder.java Thu Jan 26 12:14:51 2017 -0500 @@ -40,6 +40,7 @@ import com.redhat.thermostat.collections.graph.HashGraph; import com.redhat.thermostat.collections.graph.Node; import com.redhat.thermostat.collections.graph.Relationship; +import com.redhat.thermostat.common.utils.LoggingUtils; import java.io.IOException; import java.nio.file.Path; @@ -51,23 +52,24 @@ import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; +import java.util.logging.Level; +import java.util.logging.Logger; /** */ public class DependencyGraphBuilder extends PathProcessor { - private Map imports; - private Map exports; + private final Logger logger = LoggingUtils.getLogger(DependencyGraphBuilder.class); + + private Map exports; private Map nodes; private Graph graph; public DependencyGraphBuilder() { - imports = new HashMap<>(); exports = new HashMap<>(); graph = new HashGraph(); - nodes = new HashMap<>(); } @@ -80,15 +82,15 @@ try { Manifest manifest = new JarFile(jar.toFile()).getManifest(); - List thisExports = new ArrayList<>(); - List thisImports = new ArrayList<>(); + List thisExports = new ArrayList<>(); + List thisImports = new ArrayList<>(); Attributes attributes = manifest.getMainAttributes(); String exports = attributes.getValue(BundleProperties.EXPORT.id()); if (exports != null) { - List dependencies = OSGIManifestScanner.parseHeader(exports); - for (String dependency : dependencies) { + List dependencies = OSGIManifestScanner.parseHeader(exports); + for (Dependency dependency : dependencies) { this.exports.put(dependency, jar); thisExports.add(dependency); } @@ -96,9 +98,8 @@ String imports = attributes.getValue(BundleProperties.IMPORT.id()); if (imports != null) { - List dependencies = OSGIManifestScanner.parseHeader(imports); - for (String dependency : dependencies) { - this.imports.put(dependency, jar); + List dependencies = OSGIManifestScanner.parseHeader(imports); + for (Dependency dependency : dependencies) { thisImports.add(dependency); } } @@ -112,7 +113,7 @@ nodes.put(jar, node); } catch (IOException e) { - e.printStackTrace(); + logger.log(Level.WARNING, e.getMessage()); } } @@ -126,22 +127,40 @@ private Graph build(boolean swap) { for (Node source : nodes.values()) { - List thisImports = source.getProperty(BundleProperties.IMPORT.id()); - - for (String dep : thisImports) { - - Path who = exports.get(dep); - if (who != null) { - Node destination = nodes.get(who); - + List thisImports = source.getProperty(BundleProperties.IMPORT.id()); + Path location; + for (Dependency dep : thisImports) { + location = exports.get(dep); + if (location == null) { + for (Dependency bundle : exports.keySet()) { + if (OSGIManifestScanner.isVersionRange(dep.getVersion())) { + if ((bundle.getName().equals(dep.getName()))) { + location = exports.get(OSGIManifestScanner.parseAndCheckBounds( + dep.getVersion(), bundle.getVersion(), bundle)); + } + } else { + // If a version range is specified as a single version, it must be interpreted + // as the range [version, infinity) according to the osgi specification. + if ((bundle.getName().equals(dep.getName()))) { + location = exports.get(OSGIManifestScanner.parseAndCheckBounds( + "[" + dep.getVersion() + "," + Integer.MAX_VALUE + ")", + bundle.getVersion(), bundle)); + } + } + if (location != null) { + break; + } + } + } + if (location != null) { + Node destination = nodes.get(location); // some package seems to have dependencies on themselves, if // we create a relationship we will cause a cycle if (source.equals(destination)) { continue; } - - Relationship relationship = null; - Set relationships = null; + Relationship relationship; + Set relationships; if (swap) { relationship = new Relationship(destination, "<-", source); relationships = graph.getRelationships(destination); diff -r 8026471cdbea -r 006ac47d1506 dependency-tool/command/src/main/java/com/redhat/thermostat/tools/dependency/internal/OSGIManifestScanner.java --- a/dependency-tool/command/src/main/java/com/redhat/thermostat/tools/dependency/internal/OSGIManifestScanner.java Thu Jan 26 11:10:27 2017 -0500 +++ b/dependency-tool/command/src/main/java/com/redhat/thermostat/tools/dependency/internal/OSGIManifestScanner.java Thu Jan 26 12:14:51 2017 -0500 @@ -42,11 +42,26 @@ import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; +import java.util.regex.Pattern; /** */ public class OSGIManifestScanner { + private static final String LBRACK = "([\\(|\\[])"; + private static final String VERSION = "(\\d+(\\.\\d+){0,2})"; + private static final String RBRACK = "([\\)|\\]])"; + private static final String COMMA = ","; + private static final String INCLUSIVE_LOWER = "([\\[])"; + private static final String INCLUSIVE_UPPER = "([\\]])"; + private static final String VERSION_RANGE = LBRACK + VERSION + COMMA + VERSION + RBRACK; + private static final String INCLUSIVE_UPPER_RANGE = LBRACK + VERSION + COMMA + VERSION + INCLUSIVE_UPPER; + private static final String INCLUSIVE_LOWER_RANGE = INCLUSIVE_LOWER + VERSION + COMMA + VERSION + RBRACK; + + private static final Pattern VERSION_RANGE_PATTERN = Pattern.compile(VERSION_RANGE); + private static final Pattern INCLUSIVE_UPPER_PATTERN = Pattern.compile(INCLUSIVE_UPPER_RANGE); + private static final Pattern INCLUSIVE_LOWER_PATTERN = Pattern.compile(INCLUSIVE_LOWER_RANGE); + public static String getAttributeFromManifest(Path jar, String attribute) { String value = null; try { @@ -62,48 +77,153 @@ return value; } - public static List parseHeader(String header) { + public static List parseHeader(String header) { header = header.concat("\0"); - List packages = new ArrayList<>(); + List packages = new ArrayList<>(); int index = 0; int start = 0; - boolean invalid = false; + boolean newSubstring = true; + boolean parsingDirective = false; + boolean parsingVersion = false; + boolean isVersionRange = false; boolean inQuotes = false; - boolean newSubstring = true; - + String version; + Dependency lastPackage = new Dependency("",""); + String directive; while (index < header.length()) { char charAtIndex = header.charAt(index); - + if (parsingVersion) { + if (charAtIndex == '[' || charAtIndex == '(') { + isVersionRange = true; + } else if (charAtIndex == ']' || charAtIndex == ')') { + isVersionRange = false; + } + } if (charAtIndex == '\"') { inQuotes = !inQuotes; } - - if (!inQuotes) { - if (charAtIndex == '=') { - invalid = true; - newSubstring = false; - } else if (charAtIndex == ';' || charAtIndex == ',' || charAtIndex == '\0') { - if (!invalid && !newSubstring) { - packages.add(header.substring(start, index)); + if (charAtIndex == '=') { + if (parsingDirective) { + directive = header.substring(start, index); + if (directive.equals("version")) { + parsingVersion = true; } - start = index + 1; - invalid = false; - newSubstring = true; - } else if (newSubstring) { - if (!Character.isJavaIdentifierStart(charAtIndex)) { - invalid = true; + parsingDirective = false; + } + invalid = true; + newSubstring = false; + start = index + 1; + } + if (charAtIndex == ';' || charAtIndex == ',' || charAtIndex == '\0') { + if (parsingVersion) { + if (!isVersionRange) { + version = header.substring(start, index); + packages.remove(lastPackage); + packages.add(new Dependency( + lastPackage.getName(), version.replace("\"", ""))); + parsingVersion = false; + } else { + index++; + continue; } - newSubstring = false; - } else if (!Character.isJavaIdentifierPart(charAtIndex) && charAtIndex != '.') { + } else { + if (!inQuotes) { + if (!invalid && !newSubstring) { + // Packages are given a default version of 0. This is changed later if a version directive + // is specified in the manifest. + lastPackage = new Dependency(header.substring(start, index), Dependency.NO_VERSION); + packages.add(lastPackage); + } + if (charAtIndex == ';') { + parsingDirective = true; + } + } + } + start = index + 1; + invalid = false; + newSubstring = true; + } else if (newSubstring) { + if (!Character.isJavaIdentifierStart(charAtIndex) && !Character.isWhitespace(charAtIndex)) { invalid = true; } + newSubstring = false; + } else if ((!Character.isJavaIdentifierPart(charAtIndex)) && charAtIndex != '.' + && !Character.isWhitespace(charAtIndex)) { + invalid = true; } - index++; } + return packages; + } - return packages; + private static boolean satisfiesBound(int[] target, int[] bound, boolean exclusive) { + int major = Integer.compare(target[0], bound[0]); + int minor = Integer.compare(target[1], bound[1]); + int micro = Integer.compare(target[2], bound[2]); + if (major > 0) { + return true; + } else if (major == 0) { + if (minor > 0) { + return true; + } else if (minor == 0) { + if (micro > 0) { + return true; + } else if (micro == 0 && !exclusive) { + return true; + } + } + } + return false; + } + + private static int[] extractVersions(String versionString) { + String[] split = versionString.split(Pattern.quote(".")); + int[] versions = {0, 0, 0}; + try { + for (int i = 0; i < Math.min(split.length, versions.length); i++) { + versions[i] = Integer.parseInt(split[i]); + } + } catch (NumberFormatException ignore) { + } + return versions; + } + + static String[] parseVersionRange(String versionString) { + String[] raw = versionString.split(","); + String[] processed = new String[2]; + for (String s : raw) { + if (!(s.equals(""))) { + if ((s.contains("[")) || (s.contains("("))) { + processed[0] = s.substring(1, s.length()); + } else { + processed[1] = s.substring(0, s.length()-1); + } + } + } + return processed; + } + + static Dependency parseAndCheckBounds(String versionString, String TargetVersion, Dependency target) { + if ((versionString == null || TargetVersion == null || target == null) || !isVersionRange(versionString)) { + return null; + } + boolean strictUpper = !INCLUSIVE_UPPER_PATTERN.matcher(versionString).matches(); + boolean strictLower = !INCLUSIVE_LOWER_PATTERN.matcher(versionString).matches(); + String [] bounds = parseVersionRange(versionString); + int[] parsedLowerBound = extractVersions(bounds[0]); + int[] parsedUpperBound = extractVersions(bounds[1]); + int[] parsedTarget = extractVersions(TargetVersion); + // Need to satisfy lowerBound <= versionString <= upperBound + if ((satisfiesBound(parsedTarget, parsedLowerBound, strictLower)) + && (satisfiesBound(parsedUpperBound, parsedTarget, strictUpper))) { + return target; + } + return null; + } + + static boolean isVersionRange(String versionString) { + return VERSION_RANGE_PATTERN.matcher(versionString).matches(); } } diff -r 8026471cdbea -r 006ac47d1506 dependency-tool/command/src/main/java/com/redhat/thermostat/tools/dependency/internal/OSGiSearchProcessor.java --- a/dependency-tool/command/src/main/java/com/redhat/thermostat/tools/dependency/internal/OSGiSearchProcessor.java Thu Jan 26 11:10:27 2017 -0500 +++ b/dependency-tool/command/src/main/java/com/redhat/thermostat/tools/dependency/internal/OSGiSearchProcessor.java Thu Jan 26 12:14:51 2017 -0500 @@ -78,8 +78,9 @@ Attributes attributes = manifest.getMainAttributes(); String bundleAttribute = attributes.getValue(what.id()); if (bundleAttribute != null) { - List dependencies = OSGIManifestScanner.parseHeader(bundleAttribute); - if (dependencies.contains(target)) { + List dependencies = OSGIManifestScanner.parseHeader(bundleAttribute); + // If no version is specified, a default version of 0 is given, See OSGIManifestScanner + if (dependencies.contains(new Dependency(target, Dependency.NO_VERSION))) { info = new BundleInfo(); info.library = jar; info.symbolicName = attributes.getValue(BundleProperties.SYMBOLIC_NAME.id()); diff -r 8026471cdbea -r 006ac47d1506 dependency-tool/command/src/main/java/com/redhat/thermostat/tools/dependency/internal/actions/PrintOSGIHeaderAction.java --- a/dependency-tool/command/src/main/java/com/redhat/thermostat/tools/dependency/internal/actions/PrintOSGIHeaderAction.java Thu Jan 26 11:10:27 2017 -0500 +++ b/dependency-tool/command/src/main/java/com/redhat/thermostat/tools/dependency/internal/actions/PrintOSGIHeaderAction.java Thu Jan 26 12:14:51 2017 -0500 @@ -38,6 +38,7 @@ import com.redhat.thermostat.common.cli.CommandContext; import com.redhat.thermostat.tools.dependency.internal.BundleProperties; +import com.redhat.thermostat.tools.dependency.internal.Dependency; import com.redhat.thermostat.tools.dependency.internal.OSGIManifestScanner; import com.redhat.thermostat.tools.dependency.internal.Utils; @@ -61,9 +62,9 @@ return; } - List parsedHeader = OSGIManifestScanner.parseHeader(header); - for (String entry : parsedHeader) { - Utils.getInstance().print(ctx, entry); + List parsedHeader = OSGIManifestScanner.parseHeader(header); + for (Dependency entry : parsedHeader) { + Utils.getInstance().print(ctx, entry.getName()); } } } diff -r 8026471cdbea -r 006ac47d1506 dependency-tool/command/src/test/java/com/redhat/thermostat/tools/dependency/DependencyAnalyzerCommandTest.java --- a/dependency-tool/command/src/test/java/com/redhat/thermostat/tools/dependency/DependencyAnalyzerCommandTest.java Thu Jan 26 11:10:27 2017 -0500 +++ b/dependency-tool/command/src/test/java/com/redhat/thermostat/tools/dependency/DependencyAnalyzerCommandTest.java Thu Jan 26 12:14:51 2017 -0500 @@ -74,7 +74,7 @@ locations.getLocations().add(underneathTheBridge.toPath()); handler = new PathProcessorHandler(locations); - testJar = TestHelper.createJarWithPackageDependency("foobar", "com.real.package", underneathTheBridge.toPath()); + testJar = TestHelper.createJarWithExports("foobar", "foobar,com.real.package", null, underneathTheBridge.toPath()); when(ctx.getConsole()).thenReturn(console); when(console.getOutput()).thenReturn(mock(PrintStream.class)); diff -r 8026471cdbea -r 006ac47d1506 dependency-tool/command/src/test/java/com/redhat/thermostat/tools/dependency/internal/DependencyGraphBuilderTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dependency-tool/command/src/test/java/com/redhat/thermostat/tools/dependency/internal/DependencyGraphBuilderTest.java Thu Jan 26 12:14:51 2017 -0500 @@ -0,0 +1,151 @@ +/* + * Copyright 2012-2017 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.tools.dependency.internal; + +import java.io.File; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Set; +import com.redhat.thermostat.collections.graph.Graph; +import com.redhat.thermostat.collections.graph.Node; +import com.redhat.thermostat.collections.graph.Relationship; +import com.redhat.thermostat.tools.dependency.internal.utils.TestHelper; +import org.junit.Before; +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class DependencyGraphBuilderTest { + + private File underneathTheBridge; + + private Path b1, b2, b3, b4, b5, b6; + private Path v1, v2, v31, v41, v30, v40; + + @Before + public void setup() throws Exception { + underneathTheBridge = TestHelper.createTestDirectory(); + b1 = TestHelper.createJar("Bundle1", "Bundle2", underneathTheBridge.toPath()); + b2 = TestHelper.createJar("Bundle2", "Bundle3,Bundle4", underneathTheBridge.toPath()); + b3 = TestHelper.createJar("Bundle3", "Bundle4,Bundle5,Bundle6", underneathTheBridge.toPath()); + b4 = TestHelper.createJar("Bundle4", "Bundle5,Bundle6", underneathTheBridge.toPath()); + b5 = TestHelper.createJar("Bundle5", "Bundle6", underneathTheBridge.toPath()); + b6 = TestHelper.createJar("Bundle6", "", underneathTheBridge.toPath()); + + v1 = TestHelper.createJarWithExports("test1", "test1;version=1.0", "test2;version=[1,2)", underneathTheBridge.toPath()); + v2 = TestHelper.createJarWithExports("test2", "test2;version=1.0", "test3;version=[3.1,4)", underneathTheBridge.toPath()); + v31 = TestHelper.createJarWithExports("test-3.1", "test3;version=3.1", "test4;version=[4.1,5)", underneathTheBridge.toPath()); + v41 = TestHelper.createJarWithExports("test-4.1", "test4;version=4.1", "", underneathTheBridge.toPath()); + v30 = TestHelper.createJarWithExports("test-3.0", "test3;version=3.0", "test4;version=[4.0,4.1)", underneathTheBridge.toPath()); + v40 = TestHelper.createJarWithExports("test-4.0", "test4;version=4.0", "", underneathTheBridge.toPath()); + } + + @Test + public void testSimpleGraphBuild() { + JarLocations locations = mock(JarLocations.class); + when(locations.getLocations()).thenReturn(Arrays.asList(b1, b2, b3, b4, b5, b6)); + Node n1 = new Node(b1.toString()); + Node n2 = new Node(b2.toString()); + Node n3 = new Node(b3.toString()); + Node n4 = new Node(b4.toString()); + Node n5 = new Node(b5.toString()); + Node n6 = new Node(b6.toString()); + PathProcessorHandler handler = new PathProcessorHandler(locations); + DependencyGraphBuilder dgb = new DependencyGraphBuilder(); + handler.process(dgb); + Graph g = dgb.build(); + assertNotNull(g); + Set edges = g.getRelationships(n1); + assertEquals(1, edges.size()); + assertTrue(edges.contains(new Relationship(n1, "->", n2))); + edges = g.getRelationships(n2); + assertEquals(2, edges.size()); + assertTrue(edges.contains(new Relationship(n2, "->", n3))); + assertTrue(edges.contains(new Relationship(n2, "->", n4))); + edges = g.getRelationships(n3); + assertEquals(3, edges.size()); + assertTrue(edges.contains(new Relationship(n3, "->", n4))); + assertTrue(edges.contains(new Relationship(n3, "->", n5))); + assertTrue(edges.contains(new Relationship(n3, "->", n6))); + edges = g.getRelationships(n4); + assertEquals(2, edges.size()); + assertTrue(edges.contains(new Relationship(n4, "->", n5))); + assertTrue(edges.contains(new Relationship(n4, "->", n6))); + edges = g.getRelationships(n5); + assertEquals(1, edges.size()); + assertTrue(edges.contains(new Relationship(n5, "->", n6))); + edges = g.getRelationships(n6); + assertEquals(0, edges.size()); + } + + @Test + public void testGraphWithSpecificVersions() { + JarLocations locations = mock(JarLocations.class); + when(locations.getLocations()).thenReturn(Arrays.asList(v1, v2, v30, v31, v40, v41)); + Node n1 = new Node(v1.toString()); + Node n2 = new Node(v2.toString()); + Node n3 = new Node(v30.toString()); + Node n4 = new Node(v31.toString()); + Node n5 = new Node(v40.toString()); + Node n6 = new Node(v41.toString()); + PathProcessorHandler handler = new PathProcessorHandler(locations); + DependencyGraphBuilder dgb = new DependencyGraphBuilder(); + handler.process(dgb); + Graph g = dgb.build(); + assertNotNull(g); + Set edges = g.getRelationships(n1); + assertEquals(1, edges.size()); + assertTrue(edges.contains(new Relationship(n1, "->", n2))); + edges = g.getRelationships(n2); + assertEquals(1, edges.size()); + assertTrue(edges.contains(new Relationship(n2, "->", n4))); + edges = g.getRelationships(n3); + assertEquals(1, edges.size()); + assertTrue(edges.contains(new Relationship(n3, "->", n5))); + edges = g.getRelationships(n4); + assertEquals(1, edges.size()); + assertTrue(edges.contains(new Relationship(n4, "->", n6))); + edges = g.getRelationships(n5); + assertEquals(0, edges.size()); + edges = g.getRelationships(n6); + assertEquals(0, edges.size()); + } +} diff -r 8026471cdbea -r 006ac47d1506 dependency-tool/command/src/test/java/com/redhat/thermostat/tools/dependency/internal/DependencyTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dependency-tool/command/src/test/java/com/redhat/thermostat/tools/dependency/internal/DependencyTest.java Thu Jan 26 12:14:51 2017 -0500 @@ -0,0 +1,74 @@ +/* + * Copyright 2012-2017 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.tools.dependency.internal; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; + +public class DependencyTest { + + private Dependency test1; + private Dependency test2; + private Dependency test3; + + @Before + public void setup() { + test1 = new Dependency("foo", "1.0.0"); + test2 = new Dependency("foo", "1.0.0"); + test3 = new Dependency("bar", "2.9.1"); + } + + @Test + public void testEquals() { + assertTrue(test1.equals(test1)); + assertTrue(test1.equals(test2)); + assertTrue(test2.equals(test1)); + } + + @Test + public void testNotEquals() { + String foo = "foo"; + assertFalse(test3.equals(test1)); + assertFalse(test3.equals(test2)); + assertFalse(test1.equals(foo)); + assertFalse(test1.equals(null)); + } + +} diff -r 8026471cdbea -r 006ac47d1506 dependency-tool/command/src/test/java/com/redhat/thermostat/tools/dependency/internal/OSGIManifestScannerTest.java --- a/dependency-tool/command/src/test/java/com/redhat/thermostat/tools/dependency/internal/OSGIManifestScannerTest.java Thu Jan 26 11:10:27 2017 -0500 +++ b/dependency-tool/command/src/test/java/com/redhat/thermostat/tools/dependency/internal/OSGIManifestScannerTest.java Thu Jan 26 12:14:51 2017 -0500 @@ -134,39 +134,75 @@ @Test public void testParseHeader() throws Exception { - List packages = OSGIManifestScanner.parseHeader(Export_Package); + List packages = OSGIManifestScanner.parseHeader(Export_Package); Assert.assertEquals(31, packages.size()); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.handler.codec.serialization", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.util", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.handler.codec.compression", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.handler.execution", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.channel.local", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.bootstrap", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.handler.codec.base64", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.handler.timeout", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.channel.socket.nio", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.handler.codec.http.websocket", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.handler.codec.replay", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.handler.codec.string", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.channel", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.handler.ssl", "logging"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.container.microcontainer", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.handler.codec.rtsp", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.handler.codec.protobuf", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.channel.socket.http", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.channel.group", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.handler.codec.embedder", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.channel.socket.oio", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.handler.codec.frame", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.handler.codec.oneone", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.container.osgi", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.logging", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.buffer", "3.2.4 .Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.handler.codec.http", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.handler.queue", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.channel.socket", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.handler.logging", "3.2.4.Final"))); + Assert.assertTrue(packages.contains(new Dependency("org.jboss.netty.handler.stream", "3.2.4.Final"))); + } - Assert.assertTrue(packages.contains("org.jboss.netty.handler.codec.serialization")); - Assert.assertTrue(packages.contains("org.jboss.netty.util")); - Assert.assertTrue(packages.contains("org.jboss.netty.handler.codec.compression")); - Assert.assertTrue(packages.contains("org.jboss.netty.handler.execution")); - Assert.assertTrue(packages.contains("org.jboss.netty.channel.local")); - Assert.assertTrue(packages.contains("org.jboss.netty.bootstrap")); - Assert.assertTrue(packages.contains("org.jboss.netty.handler.codec.base64")); - Assert.assertTrue(packages.contains("org.jboss.netty.handler.timeout")); - Assert.assertTrue(packages.contains("org.jboss.netty.channel.socket.nio")); - Assert.assertTrue(packages.contains("org.jboss.netty.handler.codec.http.websocket")); - Assert.assertTrue(packages.contains("org.jboss.netty.handler.codec.replay")); - Assert.assertTrue(packages.contains("org.jboss.netty.handler.codec.string")); - Assert.assertTrue(packages.contains("org.jboss.netty.channel")); - Assert.assertTrue(packages.contains("org.jboss.netty.handler.ssl")); - Assert.assertTrue(packages.contains("org.jboss.netty.container.microcontainer")); - Assert.assertTrue(packages.contains("org.jboss.netty.handler.codec.rtsp")); - Assert.assertTrue(packages.contains("org.jboss.netty.handler.codec.protobuf")); - Assert.assertTrue(packages.contains("org.jboss.netty.channel.socket.http")); - Assert.assertTrue(packages.contains("org.jboss.netty.channel.group")); - Assert.assertTrue(packages.contains("org.jboss.netty.handler.codec.embedder")); - Assert.assertTrue(packages.contains("org.jboss.netty.channel.socket.oio")); - Assert.assertTrue(packages.contains("org.jboss.netty.handler.codec.frame")); - Assert.assertTrue(packages.contains("org.jboss.netty.handler.codec.oneone")); - Assert.assertTrue(packages.contains("org.jboss.netty.container.osgi")); - Assert.assertTrue(packages.contains("org.jboss.netty.logging")); - Assert.assertTrue(packages.contains("org.jboss.netty.buffer")); - Assert.assertTrue(packages.contains("org.jboss.netty.handler.codec.http")); - Assert.assertTrue(packages.contains("org.jboss.netty.handler.queue")); - Assert.assertTrue(packages.contains("org.jboss.netty.channel.socket")); - Assert.assertTrue(packages.contains("org.jboss.netty.handler.logging")); - Assert.assertTrue(packages.contains("org.jboss.netty.handler.stream")); + @Test + public void testVersionExtractor() { + String versionRange = "[9.1.3,10.1.2)"; + String[] extracted = OSGIManifestScanner.parseVersionRange(versionRange); + Assert.assertEquals("9.1.3", extracted[0]); + Assert.assertEquals("10.1.2", extracted[1]); + } + + @Test + public void testVersionMatcher() { + String versionRange = "[3,4)"; + Dependency result = OSGIManifestScanner.parseAndCheckBounds(versionRange, "3.1.2", new Dependency("TestBundle", "3.1.2")); + Assert.assertEquals(result, new Dependency("TestBundle", "3.1.2")); } + + @Test + public void testVersionMatcher2() { + String versionRange = "[3,4)"; + Dependency result = OSGIManifestScanner.parseAndCheckBounds(versionRange, "4", new Dependency("TestBundle", "4")); + Assert.assertEquals(result, null); + } + + @Test + public void testVersionMatcher3() { + String versionRange = "[5,5.1.2]"; + Dependency result = OSGIManifestScanner.parseAndCheckBounds(versionRange, "5.1.2", new Dependency("TestBundle", "5.1.2")); + Assert.assertEquals(result, new Dependency("TestBundle", "5.1.2")); + } + + @Test + public void testNullDependency() { + String versionRange = "[1,2]"; + Dependency result = OSGIManifestScanner.parseAndCheckBounds(versionRange, null, null); + Assert.assertEquals(result, null); + } + } diff -r 8026471cdbea -r 006ac47d1506 dependency-tool/command/src/test/java/com/redhat/thermostat/tools/dependency/internal/actions/ListDependenciesActionTest.java --- a/dependency-tool/command/src/test/java/com/redhat/thermostat/tools/dependency/internal/actions/ListDependenciesActionTest.java Thu Jan 26 11:10:27 2017 -0500 +++ b/dependency-tool/command/src/test/java/com/redhat/thermostat/tools/dependency/internal/actions/ListDependenciesActionTest.java Thu Jan 26 12:14:51 2017 -0500 @@ -57,7 +57,7 @@ */ public class ListDependenciesActionTest { - private File underneathTheBrdige; + private File underneathTheBridge; private JarLocations paths; @@ -69,6 +69,7 @@ private Path c; private Path d; private Path e; + private Path c1; private static class TestUtils extends Utils { List results = new ArrayList<>(); @@ -92,16 +93,20 @@ utils = new TestUtils(); Utils.initSingletonForTest(utils); - underneathTheBrdige = TestHelper.createTestDirectory(); + underneathTheBridge = TestHelper.createTestDirectory(); paths = new JarLocations(); - paths.getLocations().add(underneathTheBrdige.toPath()); + paths.getLocations().add(underneathTheBridge.toPath()); - a = TestHelper.createJar("a", null, underneathTheBrdige.toPath()); - b = TestHelper.createJar("b", "a", underneathTheBrdige.toPath()); - c = TestHelper.createJar("c", "b", underneathTheBrdige.toPath()); - d = TestHelper.createJar("d", "b,c", underneathTheBrdige.toPath()); - e = TestHelper.createJar("e", "d", underneathTheBrdige.toPath()); + a = TestHelper.createJar("a", null, underneathTheBridge.toPath()); + b = TestHelper.createJar("b", "a", underneathTheBridge.toPath()); + c = TestHelper.createJar("c", "b", underneathTheBridge.toPath()); + d = TestHelper.createJar("d", "b,c", underneathTheBridge.toPath()); + e = TestHelper.createJar("e", "d", underneathTheBridge.toPath()); + + c1 = TestHelper.createJar("c1", "c2", underneathTheBridge.toPath()); + TestHelper.createJar("c2", "c3", underneathTheBridge.toPath()); + TestHelper.createJar("c3", "c1", underneathTheBridge.toPath()); } @Test @@ -137,6 +142,12 @@ Assert.assertEquals(c.toString(), utils.results.get(1).getName()); Assert.assertEquals(d.toString(), utils.results.get(2).getName()); Assert.assertEquals(e.toString(), utils.results.get(3).getName()); + } + // Test that an exception is thrown when a cycle is detected. + @Test(expected = IllegalStateException.class) + public void testCyclicDependencies() { + PathProcessorHandler handler = new PathProcessorHandler(paths); + ListDependenciesAction.execute(handler, c1.toString(), ctx, true); } } \ No newline at end of file diff -r 8026471cdbea -r 006ac47d1506 dependency-tool/command/src/test/java/com/redhat/thermostat/tools/dependency/internal/utils/TestHelper.java --- a/dependency-tool/command/src/test/java/com/redhat/thermostat/tools/dependency/internal/utils/TestHelper.java Thu Jan 26 11:10:27 2017 -0500 +++ b/dependency-tool/command/src/test/java/com/redhat/thermostat/tools/dependency/internal/utils/TestHelper.java Thu Jan 26 12:14:51 2017 -0500 @@ -53,41 +53,27 @@ public class TestHelper { public static File createTestDirectory() { - File underneathTheBrdige = null; + File underneathTheBridge = null; try { - underneathTheBrdige = Files.createTempDirectory("underneathTheBridge").toFile(); + underneathTheBridge = Files.createTempDirectory("underneathTheBridge").toFile(); } catch (IOException e) { e.printStackTrace(); } - underneathTheBrdige.deleteOnExit(); - return underneathTheBrdige; + underneathTheBridge.deleteOnExit(); + return underneathTheBridge; } public static Path createJar(String exportsDirective, String importDirective, Path base) throws Exception { - Manifest manifest = new Manifest(); - manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); - manifest.getMainAttributes().put(new Attributes.Name(BundleProperties.EXPORT.id()), exportsDirective + ";"); - if (importDirective != null) { - manifest.getMainAttributes().put(new Attributes.Name(BundleProperties.IMPORT.id()), importDirective + ";"); - } - Path path = Paths.get(base.toFile().getAbsoluteFile() + "/" + exportsDirective + ".jar"); FileOutputStream stream = new FileOutputStream(path.toFile()); - JarOutputStream target = new JarOutputStream(stream, manifest); + JarOutputStream target = new JarOutputStream(stream, createManifest(exportsDirective, importDirective)); target.close(); - return path; } - public static Path createJarWithPackageDependency(String jarName, String packagePath, Path base) throws Exception { - Manifest manifest = new Manifest(); - manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); - manifest.getMainAttributes().put(new Attributes.Name(BundleProperties.EXPORT.id()), jarName + ";"); - if (packagePath != null) { - manifest.getMainAttributes().put(new Attributes.Name(BundleProperties.EXPORT.id()), packagePath + ";"); - } - + public static Path createJarWithExports(String jarName, String exportDirective, String importDirective, Path base) throws Exception { + Manifest manifest = createManifest(exportDirective, importDirective); Path path = Paths.get(base.toFile().getAbsoluteFile() + "/" + jarName + ".jar"); FileOutputStream stream = new FileOutputStream(path.toFile()); JarOutputStream target = new JarOutputStream(stream, manifest); @@ -95,4 +81,14 @@ return path; } + private static Manifest createManifest(String exports, String imports) { + Manifest manifest = new Manifest(); + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); + manifest.getMainAttributes().put(new Attributes.Name(BundleProperties.EXPORT.id()), exports + ";"); + if (imports != null) { + manifest.getMainAttributes().put(new Attributes.Name(BundleProperties.IMPORT.id()), imports + ";"); + } + return manifest; + } + } diff -r 8026471cdbea -r 006ac47d1506 launcher/src/main/java/com/redhat/thermostat/launcher/internal/DependencyManager.java --- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/DependencyManager.java Thu Jan 26 11:10:27 2017 -0500 +++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/DependencyManager.java Thu Jan 26 12:14:51 2017 -0500 @@ -90,6 +90,9 @@ return new LinkedList<>(); } buildSubgraph(b); + if (!hasNoIncomingEdge(b)) { + throw new IllegalStateException("Node " + b + " has incoming edges."); + } LinkedList sorted = new LinkedList<>(); LinkedList queue = new LinkedList<>(); queue.add(b); @@ -105,6 +108,9 @@ } } } + for (BundleInformation node : discovered) { + assertNoEdges(node); + } return sorted; } @@ -127,6 +133,19 @@ incoming.get(to).remove(from); } + private void assertNoEdges(BundleInformation node) throws IllegalStateException { + for (BundleInformation dest : getOutgoingRelationShips(node)) { + if (discovered.contains(dest)) { + throw new IllegalStateException("Graph contains a cycle."); + } + } + for (BundleInformation src : getIncomingRelationShips(node)) { + if (discovered.contains(src)) { + throw new IllegalStateException("Graph contains a cycle."); + } + } + } + private boolean hasNoIncomingEdge(BundleInformation key) { if (getIncomingRelationShips(key).isEmpty()) { return true; diff -r 8026471cdbea -r 006ac47d1506 launcher/src/main/java/com/redhat/thermostat/launcher/internal/DependencyResolver.java --- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/DependencyResolver.java Thu Jan 26 11:10:27 2017 -0500 +++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/DependencyResolver.java Thu Jan 26 12:14:51 2017 -0500 @@ -65,11 +65,14 @@ public class DependencyResolver { private final Map nodes; - private final Map exports; - private final Map> importsMap; + private final Map exports; + private final Map> importsMap; private final Map> outgoing; private final Map> incoming; + // Provides a mapping of package names to the specific pairs that provide it. + private final Map> providedVersions; private final Logger logger = LoggingUtils.getLogger(DependencyResolver.class); + private final MetadataHandler handler; public DependencyResolver(List paths) { @@ -78,6 +81,9 @@ this.importsMap = new HashMap<>(); this.outgoing = new HashMap<>(); this.incoming = new HashMap<>(); + this.handler = new MetadataHandler(); + this.providedVersions = new HashMap<>(); + try { final PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:*.jar"); FileVisitor visitor = new PluginDirFileVisitor() { @@ -111,7 +117,7 @@ protected void process(Path jar) { try { - List bImports = new ArrayList<>(); + List bImports = new ArrayList<>(); Manifest m = new JarFile(jar.toFile()).getManifest(); Attributes a = m.getMainAttributes(); String bundleName = a.getValue(Constants.BUNDLE_SYMBOLICNAME); @@ -119,14 +125,18 @@ String bundleImports = a.getValue(Constants.IMPORT_PACKAGE); String bundleExports = a.getValue(Constants.EXPORT_PACKAGE); if (bundleExports != null) { - List exports = parseHeader(bundleExports); - for (String dep : exports) { + List exports = handler.parseHeader(bundleExports); + for (BundleInformation dep : exports) { this.exports.put(dep, jar); + if (providedVersions.get(dep.getName()) == null) { + providedVersions.put(dep.getName(), new ArrayList()); + } + providedVersions.get(dep.getName()).add(dep); } } if (bundleImports != null) { - List imports = parseHeader(bundleImports); - for (String dep : imports) { + List imports = handler.parseHeader(bundleImports); + for (BundleInformation dep : imports) { bImports.add(dep); } } @@ -140,9 +150,26 @@ private void buildGraph() { for (BundleInformation source : nodes.values()) { - List bundleImports = importsMap.get(source); - for (String dep : bundleImports) { + List bundleImports = importsMap.get(source); + for (BundleInformation dep : bundleImports) { Path who = exports.get(dep); + if (who == null && providedVersions.get(dep.getName()) != null) { + for (BundleInformation export : providedVersions.get(dep.getName())) { + if (handler.isVersionRange(dep.getVersion())) { + who = exports.get(handler.parseAndCheckBounds( + dep.getVersion(), export.getVersion(), export)); + } else { + // If a version range is specified as a single version, it must be interpreted + // as the range [version, infinity) according to the osgi specification. + who = exports.get(handler.parseAndCheckBounds( + "[" + dep.getVersion() + "," + Integer.MAX_VALUE + ")", + export.getVersion(), export)); + } + if (who != null) { + break; + } + } + } if (who != null) { BundleInformation destination = nodes.get(who); if (source.equals(destination)) { @@ -160,44 +187,4 @@ } } } - - private List parseHeader(String header) { - header = header.concat("\0"); - List packages = new ArrayList<>(); - int index = 0; - int start = 0; - - boolean invalid = false; - boolean inQuotes = false; - boolean newSubstring = true; - - while (index < header.length()) { - char charAtIndex = header.charAt(index); - if (charAtIndex == '\"') { - inQuotes = !inQuotes; - } - if (!inQuotes) { - if (charAtIndex == '=') { - invalid = true; - newSubstring = false; - } else if (charAtIndex == ';' || charAtIndex == ',' || charAtIndex == '\0') { - if (!invalid && !newSubstring) { - packages.add(header.substring(start, index)); - } - start = index + 1; - invalid = false; - newSubstring = true; - } else if (newSubstring) { - if (!Character.isJavaIdentifierStart(charAtIndex)) { - invalid = true; - } - newSubstring = false; - } else if (!Character.isJavaIdentifierPart(charAtIndex) && charAtIndex != '.') { - invalid = true; - } - } - index++; - } - return packages; - } } diff -r 8026471cdbea -r 006ac47d1506 launcher/src/main/java/com/redhat/thermostat/launcher/internal/LauncherImpl.java --- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/LauncherImpl.java Thu Jan 26 11:10:27 2017 -0500 +++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/LauncherImpl.java Thu Jan 26 12:14:51 2017 -0500 @@ -337,7 +337,7 @@ } } registry.loadBundlesByName(new ArrayList<>(bundlesToLoad)); - } catch (BundleException | IOException e) { + } catch (BundleException | IOException | IllegalStateException e) { // If this happens we definitely need to do something about it, and the // trace will be immeasurably helpful in figuring out what is wrong. out.println(t.localize(LocaleResources.COMMAND_COULD_NOT_LOAD_BUNDLES, cmdName).getContents()); diff -r 8026471cdbea -r 006ac47d1506 launcher/src/main/java/com/redhat/thermostat/launcher/internal/MetadataHandler.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/MetadataHandler.java Thu Jan 26 12:14:51 2017 -0500 @@ -0,0 +1,219 @@ +/* + * Copyright 2012-2017 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.launcher.internal; + +import com.redhat.thermostat.common.utils.LoggingUtils; +import com.redhat.thermostat.launcher.BundleInformation; + +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.ArrayList; +import java.util.List; + +public class MetadataHandler { + + private static final String LBRACK = "([\\(|\\[])"; + private static final String VERSION = "(\\d+(\\.\\d+){0,2})"; + private static final String RBRACK = "([\\)|\\]])"; + private static final String COMMA = ","; + private static final String INCLUSIVE_LOWER = "([\\[])"; + private static final String INCLUSIVE_UPPER = "([\\]])"; + private static final String VERSION_RANGE = LBRACK + VERSION + COMMA + VERSION + RBRACK; + private static final String INCLUSIVE_UPPER_RANGE = LBRACK + VERSION + COMMA + VERSION + INCLUSIVE_UPPER; + private static final String INCLUSIVE_LOWER_RANGE = INCLUSIVE_LOWER + VERSION + COMMA + VERSION + RBRACK; + private static final String NO_VERSION = "0"; + + private static final Pattern VERSION_RANGE_PATTERN = Pattern.compile(VERSION_RANGE); + private static final Pattern INCLUSIVE_UPPER_PATTERN = Pattern.compile(INCLUSIVE_UPPER_RANGE); + private static final Pattern INCLUSIVE_LOWER_PATTERN = Pattern.compile(INCLUSIVE_LOWER_RANGE); + private static final Logger logger = LoggingUtils.getLogger(MetadataHandler.class); + + public List parseHeader(String header) { + header = header.concat("\0"); + List packages = new ArrayList<>(); + int index = 0; + int start = 0; + boolean invalid = false; + boolean newSubstring = true; + boolean parsingDirective = false; + boolean parsingVersion = false; + boolean isVersionRange = false; + boolean inQuotes = false; + String version; + BundleInformation lastPackage = new BundleInformation("",""); + String directive; + while (index < header.length()) { + char charAtIndex = header.charAt(index); + if (parsingVersion) { + if (charAtIndex == '[' || charAtIndex == '(') { + isVersionRange = true; + } else if (charAtIndex == ']' || charAtIndex == ')') { + isVersionRange = false; + } + } + if (charAtIndex == '\"') { + inQuotes = !inQuotes; + } + if (charAtIndex == '=') { + if (parsingDirective) { + directive = header.substring(start, index); + if (directive.equals("version")) { + parsingVersion = true; + } + parsingDirective = false; + } + invalid = true; + newSubstring = false; + start = index + 1; + } + if (charAtIndex == ';' || charAtIndex == ',' || charAtIndex == '\0') { + if (parsingVersion) { + if (!isVersionRange) { + version = header.substring(start, index); + packages.remove(lastPackage); + packages.add(new BundleInformation( + lastPackage.getName(), version.replace("\"", ""))); + parsingVersion = false; + } else { + index++; + continue; + } + } else { + if (!inQuotes) { + if (!invalid && !newSubstring) { + // Packages are given a default version of 0. This is changed later if a version directive + // is specified in the manifest. + lastPackage = new BundleInformation(header.substring(start, index), + MetadataHandler.NO_VERSION); + packages.add(lastPackage); + } + if (charAtIndex == ';') { + parsingDirective = true; + } + } + } + start = index + 1; + invalid = false; + newSubstring = true; + } else if (newSubstring) { + if (!Character.isJavaIdentifierStart(charAtIndex) && !Character.isWhitespace(charAtIndex)) { + invalid = true; + } + newSubstring = false; + } else if ((!Character.isJavaIdentifierPart(charAtIndex)) && charAtIndex != '.' + && !Character.isWhitespace(charAtIndex)) { + invalid = true; + } + index++; + } + return packages; + } + + public BundleInformation parseAndCheckBounds(String versionString, String TargetVersion, BundleInformation target) { + boolean strictUpper = !INCLUSIVE_UPPER_PATTERN.matcher(versionString).matches(); + boolean strictLower = !INCLUSIVE_LOWER_PATTERN.matcher(versionString).matches(); + String [] bounds = parseVersionRange(versionString); + int[] parsedLowerBound = extractVersions(bounds[0]); + int[] parsedUpperBound = extractVersions(bounds[1]); + int[] parsedTarget = extractVersions(TargetVersion); + // Need to satisfy lowerBound <= versionString <= upperBound + if ((satisfiesBound(parsedTarget, parsedLowerBound, strictLower)) + && (satisfiesBound(parsedUpperBound, parsedTarget, strictUpper))) { + return target; + } + return null; + } + + public boolean isVersionRange(String versionString) { + return VERSION_RANGE_PATTERN.matcher(versionString).matches(); + } + + + // Package Private for testing + boolean satisfiesBound(int[] target, int[] bound, boolean exclusive) { + int major = Integer.compare(target[0], bound[0]); + int minor = Integer.compare(target[1], bound[1]); + int micro = Integer.compare(target[2], bound[2]); + if (major > 0) { + return true; + } else if (major == 0) { + if (target[1] != -1 && bound[1] != -1) { + if (minor > 0) { + return true; + } else if (minor == 0) { + if (target[2] != -1 && bound[2] != -1) { + if (micro > 0) { + return true; + } else if (micro == 0 && !exclusive) { + return true; + } + } else if (!exclusive && bound[2] == -1) { + return true; + } + } + } else if (!exclusive && bound[1] == -1) { + return true; + } + } + return false; + } + + public String[] parseVersionRange(String versionString) { + Matcher m = VERSION_RANGE_PATTERN.matcher(versionString); + if (m.matches()) { + return new String[]{m.group(2), m.group(4)}; + } + return null; + } + + public int[] extractVersions(String versionString) { + String[] split = versionString.split(Pattern.quote(".")); + int[] versions = {-1, -1, -1}; + try { + for (int i = 0; i < Math.min(split.length, versions.length); i++) { + versions[i] = Integer.parseInt(split[i]); + } + } catch (NumberFormatException nfe) { + // Should we get a malformed version string, make sure we log it + logger.log(Level.WARNING, "Malformed version string: " + versionString); + } + return versions; + } + +} diff -r 8026471cdbea -r 006ac47d1506 launcher/src/test/java/com/redhat/thermostat/launcher/internal/DependencyManagerTest.java --- a/launcher/src/test/java/com/redhat/thermostat/launcher/internal/DependencyManagerTest.java Thu Jan 26 11:10:27 2017 -0500 +++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/DependencyManagerTest.java Thu Jan 26 12:14:51 2017 -0500 @@ -83,11 +83,22 @@ underneathTheBridge.deleteOnExit(); userPluginRoot.deleteOnExit(); systemPluginRoot.deleteOnExit(); - Path a = createJar("a", null, underneathTheBridge.toPath()); - Path b = createJar("b", "a", underneathTheBridge.toPath()); - Path c = createJar("c", "b", underneathTheBridge.toPath()); - Path d = createJar("d", "b,c", underneathTheBridge.toPath()); - Path e = createJar("e", "d", underneathTheBridge.toPath()); + createJar("Bundle1", "com.redhat.thermostat.bundle1;version=\"1.1.1\",com.redhat.thermostat.bundle1.package1;version=\"1.1.1\",com.redhat.thermostat.bundle1.package2;version=\"1.1.2\"", "", "1.1.1", underneathTheBridge.toPath()); + createJar("Bundle2", "com.redhat.thermostat.bundle2;version=\"1.1.1\",com.redhat.thermostat.bundle2.package1;version=\"1.1.1\",com.redhat.thermostat.bundle2.package2;version=\"1.1.3\"", "com.redhat.thermostat.bundle1;version=\"[1,2)\"", "1.1.1", underneathTheBridge.toPath()); + createJar("Bundle3", "com.redhat.thermostat.bundle3;version=\"2.1.0\",com.redhat.thermostat.bundle3.package1;version=\"2.1.1\",com.redhat.thermostat.bundle3.package4;version=\"2.1.2\"", "com.redhat.thermostat.bundle1;version=\"[1.1.1,1.1.2)\",com.redhat.thermostat.bundle2.package1;version=\"[1.1.1,1.1.2)\"", "2.1.0", underneathTheBridge.toPath()); + createJar("Bundle4-9.1.0", "com.redhat.thermostat.bundle4;version=\"9.1.0\",com.redhat.thermostat.bundle4.package1;version=\"9.1.0\",com.redhat.thermostat.bundle4.package2;version=\"9.1.0\"", "com.redhat.thermostat.bundle1,com.redhat.thermostat.bundle2.package1;version=\"1.1.1\",com.redhat.thermostat.bundle3.package4;version=\"[2,3)\"", "9.1.0", underneathTheBridge.toPath()); + createJar("Bundle4-9.1.3", "com.redhat.thermostat.bundle4;version=\"9.1.3\",com.redhat.thermostat.bundle4.package1;version=\"9.1.3\",com.redhat.thermostat.bundle4.package2;version=\"9.1.3\"", "com.redhat.thermostat.bundle1,com.redhat.thermostat.bundle2.package1;version=\"1.1.1\",com.redhat.thermostat.bundle3.package4;version=\"[2,3)\"", "9.1.3", underneathTheBridge.toPath()); + createJar("Bundle4-9.3", "com.redhat.thermostat.bundle4;version=\"9.3\",com.redhat.thermostat.bundle4.package1;version=\"9.3\",com.redhat.thermostat.bundle4.package2;version=\"9.3\"", "com.redhat.thermostat.bundle1,com.redhat.thermostat.bundle2.package1;version=\"1.1.1\",com.redhat.thermostat.bundle3.package4;version=\"[2,3)\"", "9.3", underneathTheBridge.toPath()); + createJar("Bundle5", "com.redhat.thermostat.bundle5;version=\"1.1\"", "com.redhat.thermostat.bundle4.package1;version=\"[9.1.3,9.1.4)\"", "1.1", userPluginRoot.toPath()); + createJar("Bundle6", "com.redhat.thermostat.bundle6;version=\"13.1\"", "com.redhat.thermostat.bundle4;version=\"[9.2,10)\"", "13.1", userPluginRoot.toPath()); + createJar("Bundle7", "com.redhat.thermostat.bundle7;version=\"1.0\"", "com.redhat.thermostat.bundle1;version=\"3.1\",com.redhat.thermostat.bundle2;version=\"9.9.9\",com.redhat.thermostat.bundle4;version=\"[10,11]\"", "1.0", userPluginRoot.toPath()); + createJar("Bundle8", "com.redhat.thermostat.bundle8;version=\"1.0\",com.redhat.thermostat.framework;version=\"4.2.0\"", "", "1.0", userPluginRoot.toPath()); + createJar("Bundle9", "com.redhat.thermostat.bundle9;version=\"1.0\"", "com.redhat.thermostat.framework;version=\"4.2.0\"", "1.0", userPluginRoot.toPath()); + + createJar("Cycle-1", "cycle1;version=\"1.0\"", "cycle2;version=\"1.0\"", "1.0", underneathTheBridge.toPath()); + createJar("Cycle-2", "cycle2;version=\"1.0\"", "cycle3;version=\"1.0\"", "1.0", underneathTheBridge.toPath()); + createJar("Cycle-3", "cycle3;version=\"1.0\"", "cycle1;version=\"1.0\"", "1.0", underneathTheBridge.toPath()); + createJar("Cycle-Connector", "cycle4;version=\"1.0\"", "cycle1;version=\"1.0\"", "1.0", underneathTheBridge.toPath()); when(paths.getUserPluginRoot()).thenReturn(userPluginRoot); when(paths.getSystemPluginRoot()).thenReturn(systemPluginRoot); when(paths.getSystemLibRoot()).thenReturn(underneathTheBridge); @@ -106,23 +117,58 @@ } @Test - public void testGetBundle() throws Exception { - ArrayList bundles = new ArrayList<>(depManager.getDependencies(new BundleInformation("d", "1.0"))); - assertEquals(4, bundles.size()); - assertEquals("d", bundles.get(0).getName()); - assertEquals("1.0", bundles.get(0).getVersion()); - assertEquals("c", bundles.get(1).getName()); - assertEquals("1.0", bundles.get(1).getVersion()); - assertEquals("b", bundles.get(2).getName()); - assertEquals("1.0", bundles.get(2).getVersion()); - assertEquals("a", bundles.get(3).getName()); - assertEquals("1.0", bundles.get(3).getVersion()); + public void testBundleWithNoDependencies() { + ArrayList results = new ArrayList<>(depManager.getDependencies(new BundleInformation("Bundle1", "1.1.1"))); + assertEquals(0, results.size()); + } + + @Test + public void testDependencySearch() { + ArrayList results = new ArrayList<>(depManager.getDependencies(new BundleInformation("Bundle4-9.1.0", "9.1.0"))); + assertEquals("Bundle4-9.1.0", results.get(0).getName()); + assertEquals("9.1.0", results.get(0).getVersion()); + assertEquals("Bundle3", results.get(1).getName()); + assertEquals("2.1.0", results.get(1).getVersion()); + assertEquals("Bundle2", results.get(2).getName()); + assertEquals("1.1.1", results.get(2).getVersion()); + assertEquals("Bundle1", results.get(3).getName()); + assertEquals("1.1.1", results.get(3).getVersion()); } @Test - public void testMismatchBundleVersion() throws Exception { - List bundles = depManager.getDependencies(new BundleInformation("d", "1.2")); - assertEquals(0, bundles.size()); + public void testVersionDependency() { + ArrayList results = new ArrayList<>(depManager.getDependencies(new BundleInformation("Bundle5", "1.1"))); + assertEquals("Bundle5", results.get(0).getName()); + assertEquals("1.1", results.get(0).getVersion()); + assertEquals("Bundle4-9.1.3", results.get(1).getName()); + assertEquals("9.1.3", results.get(1).getVersion()); + assertEquals("Bundle3", results.get(2).getName()); + assertEquals("2.1.0", results.get(2).getVersion()); + assertEquals("Bundle2", results.get(3).getName()); + assertEquals("1.1.1", results.get(3).getVersion()); + assertEquals("Bundle1", results.get(4).getName()); + assertEquals("1.1.1", results.get(4).getVersion()); + } + + @Test + public void testVersionDependency2() { + ArrayList results = new ArrayList<>(depManager.getDependencies(new BundleInformation("Bundle6", "13.1"))); + assertEquals("Bundle6", results.get(0).getName()); + assertEquals("13.1", results.get(0).getVersion()); + assertEquals("Bundle4-9.3", results.get(1).getName()); + assertEquals("9.3", results.get(1).getVersion()); + assertEquals("Bundle3", results.get(2).getName()); + assertEquals("2.1.0", results.get(2).getVersion()); + assertEquals("Bundle2", results.get(3).getName()); + assertEquals("1.1.1", results.get(3).getVersion()); + assertEquals("Bundle1", results.get(4).getName()); + assertEquals("1.1.1", results.get(4).getVersion()); + } + + @Test + public void testMissingVersion() { + ArrayList results = new ArrayList<>(depManager.getDependencies(new BundleInformation("Bundle7", "1.0"))); + assertEquals(0, results.size()); } @Test @@ -152,16 +198,26 @@ assertEquals(underneathTheBridge.toPath(), testManager.getLocations().get(2)); } - private Path createJar(String exportsDirective, String importDirective, Path base) throws Exception { + @Test (expected = IllegalStateException.class) + public void testInvalidStart() { + ArrayList result = new ArrayList<>(depManager.getDependencies(new BundleInformation("Cycle-1", "1.0"))); + } + + @Test (expected = IllegalStateException.class) + public void testCycle() { + ArrayList result = new ArrayList<>(depManager.getDependencies(new BundleInformation("Cycle-Connector", "1.0"))); + } + + private Path createJar(String name, String exportsDirective, String importDirective, String version, Path base) throws Exception { Manifest manifest = new Manifest(); - manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, version); manifest.getMainAttributes().put(new Attributes.Name("Export-Package"), exportsDirective + ";"); if (importDirective != null) { - manifest.getMainAttributes().put(new Attributes.Name("Import-Package"), importDirective + ";"); - } - Path path = Paths.get(base.toFile().getAbsoluteFile() + "/" + exportsDirective + ".jar"); - manifest.getMainAttributes().put(new Attributes.Name("Bundle-SymbolicName"), exportsDirective); - manifest.getMainAttributes().put(new Attributes.Name("Bundle-Version"), "1.0"); + manifest.getMainAttributes().put(new Attributes.Name("Import-Package"), importDirective + ";"); + } + Path path = Paths.get(base.toFile().getAbsoluteFile() + "/" + name + ".jar"); + manifest.getMainAttributes().put(new Attributes.Name("Bundle-SymbolicName"), name); + manifest.getMainAttributes().put(new Attributes.Name("Bundle-Version"), version); FileOutputStream stream = new FileOutputStream(path.toFile()); JarOutputStream target = new JarOutputStream(stream, manifest); target.close(); diff -r 8026471cdbea -r 006ac47d1506 launcher/src/test/java/com/redhat/thermostat/launcher/internal/MetadataHandlerTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/MetadataHandlerTest.java Thu Jan 26 12:14:51 2017 -0500 @@ -0,0 +1,176 @@ +/* + * Copyright 2012-2017 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.launcher.internal; + +import com.redhat.thermostat.launcher.BundleInformation; + +import java.util.List; +import java.util.ArrayList; + +import junit.framework.Assert; +import org.junit.Test; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.assertFalse; + +public class MetadataHandlerTest { + + private static final MetadataHandler handler = new MetadataHandler(); + + @Test + public void testManifestParser() { + String exportHeader = "com.redhat.thermostat.bundle1;version=\"1.1.1\",com.redhat.thermosta"+ + "t.bundle1.package1;version=\"1.1.1\",com.redhat.thermostat.bundle1.package2;version=\"1.1.2\""; + List exports = handler.parseHeader(exportHeader); + assertEquals(exports.size(), 3); + assertTrue(exports.contains(new BundleInformation("com.redhat.thermostat.bundle1", "1.1.1"))); + assertTrue(exports.contains(new BundleInformation("com.redhat.thermostat.bundle1.package1", "1.1.1"))); + assertTrue(exports.contains(new BundleInformation("com.redhat.thermostat.bundle1.package2", "1.1.2"))); + } + + @Test + public void testExtraDirectives() { + String importHeader = "com.redhat.thermostat.bundle1;version=\"[9.1,10)" + + "\",com.redhat.thermostat.bundle1.package1;version=\"[9.1,10)\",com.redhat.thermostat.bundle1." + + "package2;resolution:=optional;version=\"9.1\""; + List imports = handler.parseHeader(importHeader); + assertEquals(imports.size(), 3); + assertTrue(imports.contains(new BundleInformation("com.redhat.thermostat.bundle1", "[9.1,10)"))); + assertTrue(imports.contains(new BundleInformation("com.redhat.thermostat.bundle1.package1", "[9.1,10)"))); + assertTrue(imports.contains(new BundleInformation("com.redhat.thermostat.bundle1.package2", "9.1"))); + } + + @Test + public void testManifestParser2() { + String exportHeader = "com.redhat.thermostat.bundle1;version=\"[6.7.1,6.8)\",com.redhat.thermostat" + + ".bundle1.package1;version=\"[6.8.8,6.8.9]\",com.redhat.thermostat.bundle1.package2;version=\"(9.1.2,9.3)\""; + List exports = handler.parseHeader(exportHeader); + assertEquals(exports.size(), 3); + assertTrue(exports.contains(new BundleInformation("com.redhat.thermostat.bundle1", "[6.7.1,6.8)"))); + assertTrue(exports.contains(new BundleInformation("com.redhat.thermostat.bundle1.package1", "[6.8.8,6.8.9]"))); + assertTrue(exports.contains(new BundleInformation("com.redhat.thermostat.bundle1.package2", "(9.1.2,9.3)"))); + } + + @Test + public void testEmptyHeader() { + String header = ""; + List exports = new ArrayList<>(handler.parseHeader(header)); + assertEquals(0, exports.size()); + } + + @Test + public void testVersionParser() { + String versionString = "[1.1.2,1.4.3)"; + String[] versions = handler.parseVersionRange(versionString); + assertEquals("1.1.2", versions[0]); + assertEquals("1.4.3", versions[1]); + int[] lowerBound = handler.extractVersions(versions[0]); + int[] upperBound = handler.extractVersions(versions[1]); + assertEquals(1, lowerBound[0]); + assertEquals(1, lowerBound[1]); + assertEquals(2, lowerBound[2]); + assertEquals(1, upperBound[0]); + assertEquals(4, upperBound[1]); + assertEquals(3, upperBound[2]); + } + + @Test + public void testParserNonFullVersion() { + String versionString = "[1,3.2]"; + String[] versions = handler.parseVersionRange(versionString); + Assert.assertEquals("1", versions[0]); + Assert.assertEquals("3.2", versions[1]); + // The version extractor returns -1 for missing versions + int[] lowerBound = handler.extractVersions(versions[0]); + int[] upperBound = handler.extractVersions(versions[1]); + assertEquals(1, lowerBound[0]); + assertEquals(-1, lowerBound[1]); + assertEquals(-1, lowerBound[2]); + assertEquals(3, upperBound[0]); + assertEquals(2, upperBound[1]); + assertEquals(-1, upperBound[2]); + } + + @Test + public void testNotAVersion() { + String versionString = "foo.bar.baz"; + int[] result = handler.extractVersions(versionString); + assertEquals(-1, result[0]); + assertEquals(-1, result[1]); + assertEquals(-1, result[2]); + } + + @Test + public void testVersionMatcher() { + String versionString = "[1,2]"; + String[] versions = handler.parseVersionRange(versionString); + int[] lowerBound = handler.extractVersions(versions[0]); + int[] upperBound = handler.extractVersions(versions[1]); + assertTrue(handler.satisfiesBound(new int[]{1, 1, 1}, lowerBound, false)); + assertTrue(handler.satisfiesBound(upperBound, new int[]{1, 1, 1}, false)); + } + + @Test + public void testVersionMatcher2() { + String versionString = "[1.1,2]"; + String[] versions = handler.parseVersionRange(versionString); + int[] lowerBound = handler.extractVersions(versions[0]); + int[] upperBound = handler.extractVersions(versions[1]); + assertFalse(handler.satisfiesBound(new int[]{1, -1, -1}, lowerBound, false)); + assertTrue(handler.satisfiesBound(upperBound, new int[]{1, -1, -1}, false)); + } + + @Test + public void testVersionMatcher3() { + String versionString = "[1.1.1,2]"; + String[] versions = handler.parseVersionRange(versionString); + int[] lowerBound = handler.extractVersions(versions[0]); + int[] upperBound = handler.extractVersions(versions[1]); + assertFalse(handler.satisfiesBound(new int[]{1, 1, -1}, lowerBound, false)); + assertTrue(handler.satisfiesBound(upperBound, new int[]{1, 1, -1}, false)); + } + + @Test + public void testInclusiveRange() { + String versionString = "[2.3.4,3)"; + String[] versions = handler.parseVersionRange(versionString); + int[] upperBound = handler.extractVersions(versions[1]); + assertTrue(handler.satisfiesBound(upperBound, new int[]{3, -1, -1}, false)); + assertFalse(handler.satisfiesBound(upperBound, new int[]{3, -1, -1}, true)); + } +}