@@ -50,7 +50,7 @@ public abstract class AbstractPluginManager implements PluginManager { | |||
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; | |||
@@ -60,7 +60,7 @@ public abstract class AbstractPluginManager implements PluginManager { | |||
private List<PluginWrapper> unresolvedPlugins; | |||
/** | |||
* A list with resolved plugins (resolved dependency). | |||
* A list with all resolved plugins (resolved dependency). | |||
*/ | |||
private List<PluginWrapper> resolvedPlugins; | |||
@@ -83,7 +83,7 @@ public abstract class AbstractPluginManager implements PluginManager { | |||
/* | |||
* 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; | |||
@@ -394,7 +394,7 @@ public abstract class AbstractPluginManager implements PluginManager { | |||
} | |||
/** | |||
* Stop the specified plugin and it's dependencies. | |||
* Stop the specified plugin and it's dependents. | |||
*/ | |||
@Override | |||
public PluginState stopPlugin(String pluginId) { | |||
@@ -612,7 +612,7 @@ public abstract class AbstractPluginManager implements PluginManager { | |||
} | |||
} | |||
return (version != null) ? Version.valueOf(version) : Version.forIntegers(0, 0, 0); | |||
return (version != null) ? Version.valueOf(version) : Version.forIntegers(0); | |||
} | |||
protected abstract PluginRepository createPluginRepository(); | |||
@@ -700,7 +700,7 @@ public abstract class AbstractPluginManager implements PluginManager { | |||
// 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; | |||
} | |||
@@ -718,15 +718,36 @@ public abstract class AbstractPluginManager implements PluginManager { | |||
} | |||
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); | |||
} | |||
} | |||
@@ -800,6 +821,7 @@ public abstract class AbstractPluginManager implements PluginManager { | |||
return plugin.getPluginId(); | |||
} | |||
} | |||
return null; | |||
} | |||
@@ -841,4 +863,5 @@ public abstract class AbstractPluginManager implements PluginManager { | |||
public void setExactVersionAllowed(boolean exactVersionAllowed) { | |||
this.exactVersionAllowed = exactVersionAllowed; | |||
} | |||
} |
@@ -15,13 +15,16 @@ | |||
*/ | |||
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 | |||
@@ -30,89 +33,263 @@ public class DependencyResolver { | |||
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; | |||
} | |||
} | |||
} |
@@ -28,7 +28,6 @@ public class PluginDependency { | |||
if (index == -1) { | |||
this.pluginId = dependency; | |||
} else { | |||
this.pluginId = dependency.substring(0, index); | |||
if (dependency.length() > index + 1) { | |||
this.pluginVersionSupport = dependency.substring(index + 1); |
@@ -109,31 +109,43 @@ public class PluginDescriptor { | |||
+ 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()) { | |||
@@ -154,10 +166,14 @@ public class PluginDescriptor { | |||
} else { | |||
this.dependencies = Collections.emptyList(); | |||
} | |||
return this; | |||
} | |||
public void setLicense(String license) { | |||
public PluginDescriptor setLicense(String license) { | |||
this.license = license; | |||
return this; | |||
} | |||
} |
@@ -24,8 +24,6 @@ import ro.fortsoft.pf4j.util.StringUtils; | |||
*/ | |||
public class PluginException extends Exception { | |||
private static final long serialVersionUID = 1L; | |||
public PluginException() { | |||
super(); | |||
} |
@@ -1,37 +0,0 @@ | |||
/* | |||
* 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; | |||
} | |||
} |
@@ -23,6 +23,8 @@ import java.util.Map; | |||
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> { | |||
@@ -38,7 +40,7 @@ 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; | |||
} | |||
@@ -52,38 +54,42 @@ public class DirectedGraph<V> { | |||
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<>(); | |||
@@ -95,9 +101,9 @@ public class DirectedGraph<V> { | |||
} | |||
/** | |||
* 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 | |||
@@ -113,6 +119,7 @@ public class DirectedGraph<V> { | |||
/** | |||
* 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(); | |||
@@ -156,6 +163,7 @@ public class DirectedGraph<V> { | |||
if (list == null) { | |||
return null; | |||
} | |||
Collections.reverse(list); | |||
return list; |
@@ -18,7 +18,13 @@ package ro.fortsoft.pf4j.util; | |||
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; | |||
@@ -26,7 +32,6 @@ import java.nio.file.SimpleFileVisitor; | |||
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; | |||
@@ -34,9 +39,8 @@ 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()) { | |||
@@ -86,19 +90,23 @@ public class FileUtils { | |||
*/ | |||
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; | |||
} | |||
}); | |||
} | |||
@@ -140,6 +148,7 @@ public class FileUtils { | |||
return newPath; | |||
} | |||
} | |||
return null; | |||
} | |||
@@ -151,6 +160,7 @@ public class FileUtils { | |||
if (path == null) { | |||
return; | |||
} | |||
try { | |||
Files.delete(path); | |||
} catch (IOException ignored) { } | |||
@@ -202,4 +212,5 @@ public class FileUtils { | |||
public static boolean isZipFile(Path path) { | |||
return Files.isRegularFile(path) && path.toString().toLowerCase().endsWith(".zip"); | |||
} | |||
} |
@@ -0,0 +1,141 @@ | |||
/* | |||
* 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()); | |||
} | |||
} |