protected Map<String, PluginWrapper> plugins;
/*
- * A map of plugin class loaders (he key is the 'pluginId').
+ * A map of plugin class loaders (the key is the 'pluginId').
*/
private Map<String, ClassLoader> pluginClassLoaders;
private List<PluginWrapper> unresolvedPlugins;
/**
- * A list with resolved plugins (resolved dependency).
+ * A list with all resolved plugins (resolved dependency).
*/
private List<PluginWrapper> resolvedPlugins;
/*
* The system version used for comparisons to the plugin requires attribute.
*/
- private Version systemVersion = Version.forIntegers(0, 0, 0);
+ private Version systemVersion = Version.forIntegers(0);
private PluginRepository pluginRepository;
private PluginFactory pluginFactory;
}
/**
- * Stop the specified plugin and it's dependencies.
+ * Stop the specified plugin and it's dependents.
*/
@Override
public PluginState stopPlugin(String pluginId) {
}
}
- return (version != null) ? Version.valueOf(version) : Version.forIntegers(0, 0, 0);
+ return (version != null) ? Version.valueOf(version) : Version.forIntegers(0);
}
protected abstract PluginRepository createPluginRepository();
// If exact versions are not allowed in requires, rewrite to >= expression
requires = ">=" + requires;
}
- if (systemVersion.equals(Version.forIntegers(0,0,0)) || systemVersion.satisfies(requires)) {
+ if (systemVersion.equals(Version.forIntegers(0)) || systemVersion.satisfies(requires)) {
return true;
}
}
protected void resolvePlugins() throws PluginException {
- resolveDependencies();
- }
+ // extract plugins descriptors from "unresolvedPlugins" list
+ List<PluginDescriptor> descriptors = new ArrayList<>();
+ for (PluginWrapper plugin : unresolvedPlugins) {
+ descriptors.add(plugin.getDescriptor());
+ }
- protected void resolveDependencies() throws PluginException {
- dependencyResolver.resolve(unresolvedPlugins);
- resolvedPlugins = dependencyResolver.getSortedPlugins();
- for (PluginWrapper pluginWrapper : resolvedPlugins) {
+ DependencyResolver.Result result = dependencyResolver.resolve(descriptors);
+
+ if (result.hasCyclicDependency()) {
+ throw new DependencyResolver.CyclicDependencyException();
+ }
+
+ List<String> notFoundDependencies = result.getNotFoundDependencies();
+ if (!notFoundDependencies.isEmpty()) {
+ throw new DependencyResolver.DependenciesNotFoundException(notFoundDependencies);
+ }
+
+ List<DependencyResolver.WrongDependencyVersion> wrongVersionDependencies = result.getWrongVersionDependencies();
+ if (!wrongVersionDependencies.isEmpty()) {
+ throw new DependencyResolver.DependenciesWrongVersionException(wrongVersionDependencies);
+ }
+
+ List<String> sortedPlugins = result.getSortedPlugins();
+
+ // move plugins from "unresolved" to "resolved"
+ for (String pluginId : sortedPlugins) {
+ PluginWrapper pluginWrapper = plugins.get(pluginId);
unresolvedPlugins.remove(pluginWrapper);
- log.info("Plugin '{}' resolved", pluginWrapper.getDescriptor().getPluginId());
+ resolvedPlugins.add(pluginWrapper);
+ log.info("Plugin '{}' resolved", pluginId);
}
}
return plugin.getPluginId();
}
}
+
return null;
}
public void setExactVersionAllowed(boolean exactVersionAllowed) {
this.exactVersionAllowed = exactVersionAllowed;
}
+
}
*/
package ro.fortsoft.pf4j;
+import com.github.zafarkhaja.semver.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ro.fortsoft.pf4j.util.DirectedGraph;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* @author Decebal Suiu
private static final Logger log = LoggerFactory.getLogger(DependencyResolver.class);
- private List<PluginWrapper> plugins;
- private DirectedGraph<String> dependenciesGraph;
- private DirectedGraph<String> dependentsGraph;
+ private DirectedGraph<String> dependenciesGraph; // the value is 'pluginId'
+ private DirectedGraph<String> dependentsGraph; // the value is 'pluginId'
private boolean resolved;
- public void resolve(List<PluginWrapper> plugins) {
- this.plugins = plugins;
+ public Result resolve(List<PluginDescriptor> plugins) {
+ // create graphs
+ dependenciesGraph = new DirectedGraph<>();
+ dependentsGraph = new DirectedGraph<>();
+
+ // populate graphs
+ Map<String, PluginDescriptor> pluginByIds = new HashMap<>();
+ for (PluginDescriptor plugin : plugins) {
+ addPlugin(plugin);
+ pluginByIds.put(plugin.getPluginId(), plugin);
+ }
+
+ log.debug("Graph: {}", dependenciesGraph);
- initGraph();
+ // get a sorted list of dependencies
+ List<String> sortedPlugins = dependenciesGraph.reverseTopologicalSort();
+ log.debug("Plugins order: {}", sortedPlugins);
+
+ // create the result object
+ Result result = new Result(sortedPlugins);
resolved = true;
- }
- public List<String> getDependecies(String pluginsId) {
- if (!resolved) {
- return Collections.emptyList();
+ if (sortedPlugins != null) { // no cyclic dependency
+ // detect not found dependencies
+ for (String pluginId : sortedPlugins) {
+ if (!pluginByIds.containsKey(pluginId)) {
+ result.addNotFoundDependency(pluginId);
+ }
+ }
}
- return dependenciesGraph.getNeighbors(pluginsId);
- }
+ // check dependencies versions
+ for (PluginDescriptor plugin : plugins) {
+ String pluginId = plugin.getPluginId();
+ Version existingVersion = plugin.getVersion();
- public List<String> getDependents(String pluginsId) {
- if (!resolved) {
- return Collections.emptyList();
+ List<String> dependents = getDependents(pluginId);
+ while (!dependents.isEmpty()) {
+ String dependentId = dependents.remove(0);
+ PluginDescriptor dependent = pluginByIds.get(dependentId);
+ String requiredVersion = getDependencyVersionSupport(dependent, pluginId);
+ boolean ok = checkDependencyVersion(requiredVersion, existingVersion);
+ if (!ok) {
+ result.addWrongDependencyVersion(new WrongDependencyVersion(pluginId, dependentId, existingVersion, requiredVersion));
+ }
+ }
}
- return dependentsGraph.getNeighbors(pluginsId);
+ return result;
}
- /**
- * Get the list of plugins in dependency sorted order.
- */
- public List<PluginWrapper> getSortedPlugins() throws PluginException {
- if (!resolved) {
- return Collections.emptyList();
+ /**
+ * Retrieves the plugins ids that the given plugin id directly depends on.
+ *
+ * @param pluginId
+ * @return
+ */
+ public List<String> getDependencies(String pluginId) {
+ checkResolved();
+ return dependenciesGraph.getNeighbors(pluginId);
+ }
+
+ /**
+ * Retrieves the plugins ids that the given content is a direct dependency of.
+ *
+ * @param pluginId
+ * @return
+ */
+ public List<String> getDependents(String pluginId) {
+ checkResolved();
+ return dependentsGraph.getNeighbors(pluginId);
+ }
+
+ /**
+ * Check if an existing version of dependency is compatible with the required version (from plugin descriptor).
+ *
+ * @param requiredVersion
+ * @param existingVersion
+ * @return
+ */
+ protected boolean checkDependencyVersion(String requiredVersion, Version existingVersion) {
+ return existingVersion.satisfies(requiredVersion);
+ }
+
+ private void addPlugin(PluginDescriptor descriptor) {
+ String pluginId = descriptor.getPluginId();
+ List<PluginDependency> dependencies = descriptor.getDependencies();
+ if (dependencies.isEmpty()) {
+ dependenciesGraph.addVertex(pluginId);
+ dependentsGraph.addVertex(pluginId);
+ } else {
+ for (PluginDependency dependency : dependencies) {
+ dependenciesGraph.addEdge(pluginId, dependency.getPluginId());
+ dependentsGraph.addEdge(dependency.getPluginId(), pluginId);
+ }
}
+ }
- log.debug("Graph: {}", dependenciesGraph);
- List<String> pluginsId = dependenciesGraph.reverseTopologicalSort();
+ private void checkResolved() {
+ if (!resolved) {
+ throw new IllegalStateException("Call 'resolve' method first");
+ }
+ }
- if (pluginsId == null) {
- throw new PluginException("Cyclic dependencies !!! {}", dependenciesGraph.toString());
- }
+ private String getDependencyVersionSupport(PluginDescriptor dependent, String dependencyId) {
+ List<PluginDependency> dependencies = dependent.getDependencies();
+ for (PluginDependency dependency : dependencies) {
+ if (dependencyId.equals(dependency.getPluginId())) {
+ return dependency.getPluginVersionSupport();
+ }
+ }
- log.debug("Plugins order: {}", pluginsId);
- List<PluginWrapper> sortedPlugins = new ArrayList<>();
- for (String pluginId : pluginsId) {
- sortedPlugins.add(getPlugin(pluginId));
- }
+ throw new IllegalStateException("Cannot find a dependency with id '" + dependencyId +
+ "' for plugin '" + dependent.getPluginId() + "'");
+ }
- return sortedPlugins;
- }
+ public static class Result {
- private void initGraph() {
- // create graph
- dependenciesGraph = new DirectedGraph<>();
- dependentsGraph = new DirectedGraph<>();
+ private boolean cyclicDependency;
+ private List<String> notFoundDependencies; // value is "pluginId"
+ private List<String> sortedPlugins; // value is "pluginId"
+ private List<WrongDependencyVersion> wrongVersionDependencies;
- // populate graph
- for (PluginWrapper pluginWrapper : plugins) {
- PluginDescriptor descriptor = pluginWrapper.getDescriptor();
- String pluginId = descriptor.getPluginId();
- List<PluginDependency> dependencies = descriptor.getDependencies();
- if (!dependencies.isEmpty()) {
- for (PluginDependency dependency : dependencies) {
- dependenciesGraph.addEdge(pluginId, dependency.getPluginId());
- dependentsGraph.addEdge(dependency.getPluginId(), pluginId);
- }
+ Result(List<String> sortedPlugins) {
+ if (sortedPlugins == null) {
+ cyclicDependency = true;
+ this.sortedPlugins = Collections.emptyList();
} else {
- dependenciesGraph.addVertex(pluginId);
- dependentsGraph.addVertex(pluginId);
+ this.sortedPlugins = new ArrayList<>(sortedPlugins);
}
+
+ notFoundDependencies = new ArrayList<>();
+ wrongVersionDependencies = new ArrayList<>();
+ }
+
+ /**
+ * Returns true is a cyclic dependency was detected.
+ */
+ public boolean hasCyclicDependency() {
+ return cyclicDependency;
+ }
+
+ /**
+ * Returns a list with dependencies required that were not found.
+ */
+ public List<String> getNotFoundDependencies() {
+ return notFoundDependencies;
+ }
+
+ /**
+ * Returns a list with dependencies with wrong version.
+ */
+ public List<WrongDependencyVersion> getWrongVersionDependencies() {
+ return wrongVersionDependencies;
+ }
+
+ /**
+ * Get the list of plugins in dependency sorted order.
+ */
+ public List<String> getSortedPlugins() {
+ return sortedPlugins;
}
+
+ void addNotFoundDependency(String pluginId) {
+ notFoundDependencies.add(pluginId);
+ }
+
+ void addWrongDependencyVersion(WrongDependencyVersion wrongDependencyVersion) {
+ wrongVersionDependencies.add(wrongDependencyVersion);
+ }
+
+ }
+
+ public static class WrongDependencyVersion {
+
+ private String dependencyId; // value is "pluginId"
+ private String dependentId; // value is "pluginId"
+ private Version existingVersion;
+ private String requiredVersion;
+
+ WrongDependencyVersion(String dependencyId, String dependentId, Version existingVersion, String requiredVersion) {
+ this.dependencyId = dependencyId;
+ this.dependentId = dependentId;
+ this.existingVersion = existingVersion;
+ this.requiredVersion = requiredVersion;
+ }
+
+ public String getDependencyId() {
+ return dependencyId;
+ }
+
+ public String getDependentId() {
+ return dependentId;
+ }
+
+ public Version getExistingVersion() {
+ return existingVersion;
+ }
+
+ public String getRequiredVersion() {
+ return requiredVersion;
+ }
+
+ }
+
+ /**
+ * It will be thrown if a cyclic dependency is detected.
+ */
+ public static class CyclicDependencyException extends PluginException {
+
+ public CyclicDependencyException() {
+ super("Cyclic dependencies");
+ }
+
+ }
+
+ /**
+ * Indicates that the dependencies required were not found.
+ */
+ public static class DependenciesNotFoundException extends PluginException {
+
+ private List<String> dependencies;
+
+ public DependenciesNotFoundException(List<String> dependencies) {
+ super("Dependencies '{}' not found", dependencies);
+
+ this.dependencies = dependencies;
+ }
+
+ public List<String> getDependencies() {
+ return dependencies;
+ }
+
}
- private PluginWrapper getPlugin(String pluginId) throws PluginNotFoundException {
- for (PluginWrapper pluginWrapper : plugins) {
- if (pluginId.equals(pluginWrapper.getDescriptor().getPluginId())) {
- return pluginWrapper;
- }
- }
+ /**
+ * Indicates that some dependencies have wrong version.
+ */
+ public static class DependenciesWrongVersionException extends PluginException {
+
+ private List<WrongDependencyVersion> dependencies;
+
+ public DependenciesWrongVersionException(List<WrongDependencyVersion> dependencies) {
+ super("Dependencies '{}' have wrong version", dependencies);
- throw new PluginNotFoundException(pluginId);
- }
+ this.dependencies = dependencies;
+ }
+
+ public List<WrongDependencyVersion> getDependencies() {
+ return dependencies;
+ }
+
+ }
}
if (index == -1) {
this.pluginId = dependency;
} else {
-
this.pluginId = dependency.substring(0, index);
if (dependency.length() > index + 1) {
this.pluginVersionSupport = dependency.substring(index + 1);
+ license + "]";
}
- void setPluginId(String pluginId) {
+ PluginDescriptor setPluginId(String pluginId) {
this.pluginId = pluginId;
+
+ return this;
}
- void setPluginDescription(String pluginDescription) {
+ PluginDescriptor setPluginDescription(String pluginDescription) {
this.pluginDescription = pluginDescription;
+
+ return this;
}
- void setPluginClass(String pluginClassName) {
+ PluginDescriptor setPluginClass(String pluginClassName) {
this.pluginClass = pluginClassName;
+
+ return this;
}
- void setPluginVersion(Version version) {
+ PluginDescriptor setPluginVersion(Version version) {
this.version = version;
+
+ return this;
}
- void setProvider(String provider) {
+ PluginDescriptor setProvider(String provider) {
this.provider = provider;
+
+ return this;
}
- void setRequires(String requires) {
+ PluginDescriptor setRequires(String requires) {
this.requires = requires;
+
+ return this;
}
- void setDependencies(String dependencies) {
+ PluginDescriptor setDependencies(String dependencies) {
if (dependencies != null) {
dependencies = dependencies.trim();
if (dependencies.isEmpty()) {
} else {
this.dependencies = Collections.emptyList();
}
+
+ return this;
}
- public void setLicense(String license) {
+ public PluginDescriptor setLicense(String license) {
this.license = license;
+
+ return this;
}
}
*/
public class PluginException extends Exception {
- private static final long serialVersionUID = 1L;
-
public PluginException() {
super();
}
+++ /dev/null
-/*
- * Copyright 2012 Decebal Suiu
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package ro.fortsoft.pf4j;
-
-/**
- * @author Decebal Suiu
- */
-class PluginNotFoundException extends PluginException {
-
- private static final long serialVersionUID = 1L;
-
- private String pluginId;
-
- public PluginNotFoundException(String pluginId) {
- super("Plugin '" + pluginId + "' not found.");
-
- this.pluginId = pluginId;
- }
-
- public String getPluginId() {
- return pluginId;
- }
-
-}
import java.util.Stack;
/**
+ * See <a href="https://en.wikipedia.org/wiki/Directed_graph">Wikipedia</a> for more information.
+ *
* @author Decebal Suiu
*/
public class DirectedGraph<V> {
* Add a vertex to the graph. Nothing happens if vertex is already in graph.
*/
public void addVertex(V vertex) {
- if (neighbors.containsKey(vertex)) {
+ if (containsVertex(vertex)) {
return;
}
return neighbors.containsKey(vertex);
}
+ public void removeVertex(V vertex) {
+ neighbors.remove(vertex);
+ }
+
/**
* Add an edge to the graph; if either vertex does not exist, it's added.
* This implementation allows the creation of multi-edges and self-loops.
*/
public void addEdge(V from, V to) {
- this.addVertex(from);
- this.addVertex(to);
+ addVertex(from);
+ addVertex(to);
neighbors.get(from).add(to);
}
/**
* Remove an edge from the graph. Nothing happens if no such edge.
- * @throws IllegalArgumentException if either vertex doesn't exist.
+ * @throws {@link IllegalArgumentException} if either vertex doesn't exist.
*/
- public void remove(V from, V to) {
- if (!(this.containsVertex(from) && this.containsVertex(to))) {
- throw new IllegalArgumentException("Nonexistent vertex");
+ public void removeEdge(V from, V to) {
+ if (!containsVertex(from)) {
+ throw new IllegalArgumentException("Nonexistent vertex " + from);
+ }
+
+ if (!containsVertex(to)) {
+ throw new IllegalArgumentException("Nonexistent vertex " + to);
}
neighbors.get(from).remove(to);
}
public List<V> getNeighbors(V vertex) {
- if (!neighbors.containsKey(vertex)) {
- return new ArrayList<V>();
- }
-
- return neighbors.get(vertex);
+ return containsVertex(vertex) ? neighbors.get(vertex) : new ArrayList<V>();
}
/**
- * Report (as a Map) the out-degree of each vertex.
+ * Report (as a Map) the out-degree (the number of tail ends adjacent to a vertex) of each vertex.
*/
public Map<V, Integer> outDegree() {
Map<V, Integer> result = new HashMap<>();
}
/**
- * Report (as a Map) the in-degree of each vertex.
+ * Report (as a Map) the in-degree (the number of head ends adjacent to a vertex) of each vertex.
*/
- public Map<V,Integer> inDegree() {
+ public Map<V, Integer> inDegree() {
Map<V, Integer> result = new HashMap<>();
for (V vertex : neighbors.keySet()) {
result.put(vertex, 0); // all in-degrees are 0
/**
* Report (as a List) the topological sort of the vertices; null for no such sort.
+ * See <a href="https://en.wikipedia.org/wiki/Topological_sorting">this</a> for more information.
*/
public List<V> topologicalSort() {
Map<V, Integer> degree = inDegree();
if (list == null) {
return null;
}
+
Collections.reverse(list);
return list;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.*;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.List;
* @author Decebal Suiu
*/
public class FileUtils {
- private static final Logger log = LoggerFactory.getLogger(FileUtils.class);
- private static final List<String> ZIP_EXTENSIONS = Arrays.asList(".zip", ".ZIP", ".Zip");
+ private static final Logger log = LoggerFactory.getLogger(FileUtils.class);
public static List<String> readLines(File file, boolean ignoreComments) throws IOException {
if (!file.exists() || !file.isFile()) {
*/
public static void delete(Path path) throws IOException {
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
+
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
if (!attrs.isSymbolicLink()) {
Files.delete(path);
}
+
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
+
return FileVisitResult.CONTINUE;
}
+
});
}
return newPath;
}
}
+
return null;
}
if (path == null) {
return;
}
+
try {
Files.delete(path);
} catch (IOException ignored) { }
public static boolean isZipFile(Path path) {
return Files.isRegularFile(path) && path.toString().toLowerCase().endsWith(".zip");
}
+
}
--- /dev/null
+/*
+ * Copyright 2017 Decebal Suiu
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ro.fortsoft.pf4j;
+
+import com.github.zafarkhaja.semver.Version;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Decebal Suiu
+ */
+public class DependencyResolverTest {
+
+ @Test
+ public void sortedPlugins() {
+ // create incomplete plugin descriptor (ignore some attributes)
+ PluginDescriptor pd1 = new PluginDescriptor()
+ .setPluginId("p1")
+ .setDependencies("p2");
+
+ PluginDescriptor pd2 = new PluginDescriptor()
+ .setPluginId("p2")
+ .setPluginVersion(Version.forIntegers(0)); // needed in "checkDependencyVersion" method
+
+ List<PluginDescriptor> plugins = new ArrayList<>();
+ plugins.add(pd1);
+ plugins.add(pd2);
+
+ DependencyResolver resolver = new DependencyResolver();
+ DependencyResolver.Result result = resolver.resolve(plugins);
+
+ assertTrue(result.getNotFoundDependencies().isEmpty());
+ assertEquals(result.getSortedPlugins(), Arrays.asList("p2", "p1"));
+ }
+
+ @Test
+ public void notFoundDependencies() throws Exception {
+ PluginDescriptor pd1 = new PluginDescriptor()
+ .setPluginId("p1")
+ .setDependencies("p2, p3");
+
+ List<PluginDescriptor> plugins = new ArrayList<>();
+ plugins.add(pd1);
+
+ DependencyResolver resolver = new DependencyResolver();
+ DependencyResolver.Result result = resolver.resolve(plugins);
+
+ assertFalse(result.getNotFoundDependencies().isEmpty());
+ assertEquals(result.getNotFoundDependencies(), Arrays.asList("p2", "p3"));
+ }
+
+ @Test
+ public void cyclicDependencies() {
+ PluginDescriptor pd1 = new PluginDescriptor()
+ .setPluginId("p1")
+ .setPluginVersion(Version.forIntegers(0))
+ .setDependencies("p2");
+
+ PluginDescriptor pd2 = new PluginDescriptor()
+ .setPluginId("p2")
+ .setPluginVersion(Version.forIntegers(0))
+ .setDependencies("p3");
+
+ PluginDescriptor pd3 = new PluginDescriptor()
+ .setPluginId("p3")
+ .setPluginVersion(Version.forIntegers(0))
+ .setDependencies("p1");
+
+ List<PluginDescriptor> plugins = new ArrayList<>();
+ plugins.add(pd1);
+ plugins.add(pd2);
+ plugins.add(pd3);
+
+ DependencyResolver resolver = new DependencyResolver();
+ DependencyResolver.Result result = resolver.resolve(plugins);
+
+ assertTrue(result.hasCyclicDependency());
+ }
+
+ @Test
+ public void wrongDependencyVersion() {
+ PluginDescriptor pd1 = new PluginDescriptor()
+ .setPluginId("p1")
+// .setDependencies("p2@2.0.0"); // simple version
+ .setDependencies("p2@>=1.5.0 & <1.6.0"); // range version
+
+ PluginDescriptor pd2 = new PluginDescriptor()
+ .setPluginId("p2")
+ .setPluginVersion(Version.forIntegers(1, 4));
+
+ List<PluginDescriptor> plugins = new ArrayList<>();
+ plugins.add(pd1);
+ plugins.add(pd2);
+
+ DependencyResolver resolver = new DependencyResolver();
+ DependencyResolver.Result result = resolver.resolve(plugins);
+
+ assertFalse(result.getWrongVersionDependencies().isEmpty());
+ }
+
+ @Test
+ public void goodDependencyVersion() {
+ PluginDescriptor pd1 = new PluginDescriptor()
+ .setPluginId("p1")
+ .setDependencies("p2@2.0.0");
+
+ PluginDescriptor pd2 = new PluginDescriptor()
+ .setPluginId("p2")
+ .setPluginVersion(Version.forIntegers(2));
+
+ List<PluginDescriptor> plugins = new ArrayList<>();
+ plugins.add(pd1);
+ plugins.add(pd2);
+
+ DependencyResolver resolver = new DependencyResolver();
+ DependencyResolver.Result result = resolver.resolve(plugins);
+
+ assertTrue(result.getWrongVersionDependencies().isEmpty());
+ }
+
+}