Add provides API (#1853)
Add provides API Co-authored-by: Shane Freeder <theboyetronic@gmail.com>
This commit is contained in:
@@ -97,6 +97,16 @@ public class PluginAnnotationProcessor extends AbstractProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (String provided : plugin.provides()) {
|
||||||
|
if (!SerializedPluginDescription.ID_PATTERN.matcher(provided).matches()) {
|
||||||
|
environment.getMessager().printMessage(Diagnostic.Kind.ERROR,
|
||||||
|
"Invalid provided ID '" + provided + "' for plugin " + qualifiedName
|
||||||
|
+ ". IDs must start alphabetically, have lowercase alphanumeric characters, and "
|
||||||
|
+ "can contain dashes or underscores.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// All good, generate the velocity-plugin.json.
|
// All good, generate the velocity-plugin.json.
|
||||||
SerializedPluginDescription description = SerializedPluginDescription
|
SerializedPluginDescription description = SerializedPluginDescription
|
||||||
.from(plugin, qualifiedName.toString());
|
.from(plugin, qualifiedName.toString());
|
||||||
|
|||||||
@@ -35,11 +35,12 @@ public final class SerializedPluginDescription {
|
|||||||
private final @Nullable String url;
|
private final @Nullable String url;
|
||||||
private final @Nullable List<String> authors;
|
private final @Nullable List<String> authors;
|
||||||
private final @Nullable List<Dependency> dependencies;
|
private final @Nullable List<Dependency> dependencies;
|
||||||
|
private final @Nullable List<String> provides;
|
||||||
private final String main;
|
private final String main;
|
||||||
|
|
||||||
private SerializedPluginDescription(String id, String name, String version, String description,
|
private SerializedPluginDescription(String id, String name, String version, String description,
|
||||||
String url,
|
String url,
|
||||||
List<String> authors, List<Dependency> dependencies, String main) {
|
List<String> authors, List<Dependency> dependencies, List<String> provides, String main) {
|
||||||
Preconditions.checkNotNull(id, "id");
|
Preconditions.checkNotNull(id, "id");
|
||||||
Preconditions.checkArgument(ID_PATTERN.matcher(id).matches(), "id is not valid");
|
Preconditions.checkArgument(ID_PATTERN.matcher(id).matches(), "id is not valid");
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@@ -50,6 +51,7 @@ public final class SerializedPluginDescription {
|
|||||||
this.authors = authors == null || authors.isEmpty() ? ImmutableList.of() : authors;
|
this.authors = authors == null || authors.isEmpty() ? ImmutableList.of() : authors;
|
||||||
this.dependencies =
|
this.dependencies =
|
||||||
dependencies == null || dependencies.isEmpty() ? ImmutableList.of() : dependencies;
|
dependencies == null || dependencies.isEmpty() ? ImmutableList.of() : dependencies;
|
||||||
|
this.provides = provides == null || provides.isEmpty() ? ImmutableList.of() : provides;
|
||||||
this.main = Preconditions.checkNotNull(main, "main");
|
this.main = Preconditions.checkNotNull(main, "main");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +63,9 @@ public final class SerializedPluginDescription {
|
|||||||
return new SerializedPluginDescription(plugin.id(), plugin.name(), plugin.version(),
|
return new SerializedPluginDescription(plugin.id(), plugin.name(), plugin.version(),
|
||||||
plugin.description(), plugin.url(),
|
plugin.description(), plugin.url(),
|
||||||
Arrays.stream(plugin.authors()).filter(author -> !author.isEmpty())
|
Arrays.stream(plugin.authors()).filter(author -> !author.isEmpty())
|
||||||
.collect(Collectors.toList()), dependencies, qualifiedName);
|
.collect(Collectors.toList()), dependencies,
|
||||||
|
Arrays.stream(plugin.provides()).filter(provided -> !provided.isEmpty())
|
||||||
|
.collect(Collectors.toList()), qualifiedName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
@@ -92,6 +96,10 @@ public final class SerializedPluginDescription {
|
|||||||
return dependencies == null ? ImmutableList.of() : dependencies;
|
return dependencies == null ? ImmutableList.of() : dependencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getProvides() {
|
||||||
|
return provides == null ? ImmutableList.of() : provides;
|
||||||
|
}
|
||||||
|
|
||||||
public String getMain() {
|
public String getMain() {
|
||||||
return main;
|
return main;
|
||||||
}
|
}
|
||||||
@@ -112,12 +120,13 @@ public final class SerializedPluginDescription {
|
|||||||
&& Objects.equals(url, that.url)
|
&& Objects.equals(url, that.url)
|
||||||
&& Objects.equals(authors, that.authors)
|
&& Objects.equals(authors, that.authors)
|
||||||
&& Objects.equals(dependencies, that.dependencies)
|
&& Objects.equals(dependencies, that.dependencies)
|
||||||
|
&& Objects.equals(provides, that.provides)
|
||||||
&& Objects.equals(main, that.main);
|
&& Objects.equals(main, that.main);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(id, name, version, description, url, authors, dependencies);
|
return Objects.hash(id, name, version, description, url, authors, dependencies, provides);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -130,6 +139,7 @@ public final class SerializedPluginDescription {
|
|||||||
+ ", url='" + url + '\''
|
+ ", url='" + url + '\''
|
||||||
+ ", authors=" + authors
|
+ ", authors=" + authors
|
||||||
+ ", dependencies=" + dependencies
|
+ ", dependencies=" + dependencies
|
||||||
|
+ ", provides=" + provides
|
||||||
+ ", main='" + main + '\''
|
+ ", main='" + main + '\''
|
||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,4 +72,12 @@ public @interface Plugin {
|
|||||||
* @return the plugin dependencies
|
* @return the plugin dependencies
|
||||||
*/
|
*/
|
||||||
Dependency[] dependencies() default {};
|
Dependency[] dependencies() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The plugin IDs this plugin "provides" for. Each ID must match
|
||||||
|
* {@link SerializedPluginDescription#ID_PATTERN_STRING}.
|
||||||
|
*
|
||||||
|
* @return the provided IDs
|
||||||
|
*/
|
||||||
|
String[] provides() default {};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,6 +100,16 @@ public interface PluginDescription {
|
|||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a {@link Collection} of the provided IDs of the {@link Plugin} within this container.
|
||||||
|
*
|
||||||
|
* @return the provided plugins IDs, can be empty
|
||||||
|
* @see Plugin#provides()
|
||||||
|
*/
|
||||||
|
default Collection<String> getProvidedIds() {
|
||||||
|
return ImmutableSet.of();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the source the plugin was loaded from.
|
* Returns the source the plugin was loaded from.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -221,7 +221,8 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
|||||||
PluginDescription description = new VelocityPluginDescription(
|
PluginDescription description = new VelocityPluginDescription(
|
||||||
"velocity", version.getName(), version.getVersion(), "The Velocity proxy",
|
"velocity", version.getName(), version.getVersion(), "The Velocity proxy",
|
||||||
version.getName().equals("Velocity") ? VELOCITY_URL : null,
|
version.getName().equals("Velocity") ? VELOCITY_URL : null,
|
||||||
ImmutableList.of(version.getVendor()), Collections.emptyList(), null);
|
ImmutableList.of(version.getVendor()), Collections.emptyList(),
|
||||||
|
Collections.emptyList(), null);
|
||||||
VelocityPluginContainer container = new VelocityPluginContainer(description);
|
VelocityPluginContainer container = new VelocityPluginContainer(description);
|
||||||
container.setInstance(VelocityVirtualPlugin.INSTANCE);
|
container.setInstance(VelocityVirtualPlugin.INSTANCE);
|
||||||
return container;
|
return container;
|
||||||
|
|||||||
@@ -46,10 +46,12 @@ import java.util.Collections;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.IdentityHashMap;
|
import java.util.IdentityHashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
@@ -62,6 +64,7 @@ public class VelocityPluginManager implements PluginManager {
|
|||||||
|
|
||||||
private final Map<String, PluginContainer> pluginsById = new LinkedHashMap<>();
|
private final Map<String, PluginContainer> pluginsById = new LinkedHashMap<>();
|
||||||
private final Map<Object, PluginContainer> pluginInstances = new IdentityHashMap<>();
|
private final Map<Object, PluginContainer> pluginInstances = new IdentityHashMap<>();
|
||||||
|
private final Set<PluginContainer> plugins = new LinkedHashSet<>();
|
||||||
private final VelocityServer server;
|
private final VelocityServer server;
|
||||||
|
|
||||||
public VelocityPluginManager(VelocityServer server) {
|
public VelocityPluginManager(VelocityServer server) {
|
||||||
@@ -74,7 +77,9 @@ public class VelocityPluginManager implements PluginManager {
|
|||||||
* @param plugin the plugin to register
|
* @param plugin the plugin to register
|
||||||
*/
|
*/
|
||||||
public void registerPlugin(PluginContainer plugin) {
|
public void registerPlugin(PluginContainer plugin) {
|
||||||
|
plugins.add(plugin);
|
||||||
pluginsById.put(plugin.getDescription().getId(), plugin);
|
pluginsById.put(plugin.getDescription().getId(), plugin);
|
||||||
|
plugin.getDescription().getProvidedIds().forEach(id -> pluginsById.put(id, plugin));
|
||||||
Optional<?> instance = plugin.getInstance();
|
Optional<?> instance = plugin.getInstance();
|
||||||
instance.ifPresent(o -> pluginInstances.put(o, plugin));
|
instance.ifPresent(o -> pluginInstances.put(o, plugin));
|
||||||
}
|
}
|
||||||
@@ -100,16 +105,34 @@ public class VelocityPluginManager implements PluginManager {
|
|||||||
try {
|
try {
|
||||||
PluginDescription candidate = loader.loadCandidate(path);
|
PluginDescription candidate = loader.loadCandidate(path);
|
||||||
|
|
||||||
// If we found a duplicate candidate (with the same ID), don't load it.
|
// A plugin claims its own ID plus every ID it provides. If any of those are already
|
||||||
PluginDescription maybeExistingCandidate = foundCandidates.putIfAbsent(
|
// claimed by another candidate, don't load this one.
|
||||||
candidate.getId(), candidate);
|
List<String> claimedIds = new ArrayList<>(candidate.getProvidedIds().size() + 1);
|
||||||
|
claimedIds.add(candidate.getId());
|
||||||
|
claimedIds.addAll(candidate.getProvidedIds());
|
||||||
|
|
||||||
if (maybeExistingCandidate != null) {
|
PluginDescription conflict = null;
|
||||||
logger.error("Refusing to load plugin at path {} since we already "
|
String conflictingId = null;
|
||||||
+ "loaded a plugin with the same ID {} from {}",
|
for (String id : claimedIds) {
|
||||||
|
PluginDescription existing = foundCandidates.get(id);
|
||||||
|
if (existing != null) {
|
||||||
|
conflict = existing;
|
||||||
|
conflictingId = id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conflict != null) {
|
||||||
|
logger.error("Refusing to load plugin at path {} since ID {} was already "
|
||||||
|
+ "claimed by a plugin loaded from {}",
|
||||||
candidate.getSource().map(Objects::toString).orElse("<UNKNOWN>"),
|
candidate.getSource().map(Objects::toString).orElse("<UNKNOWN>"),
|
||||||
candidate.getId(),
|
conflictingId,
|
||||||
maybeExistingCandidate.getSource().map(Objects::toString).orElse("<UNKNOWN>"));
|
conflict.getSource().map(Objects::toString).orElse("<UNKNOWN>"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String id : claimedIds) {
|
||||||
|
foundCandidates.put(id, candidate);
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
logger.error("Unable to load plugin {}", path, e);
|
logger.error("Unable to load plugin {}", path, e);
|
||||||
@@ -122,8 +145,10 @@ public class VelocityPluginManager implements PluginManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// foundCandidates indexes each candidate under its ID and any provided IDs, so dedupe before
|
||||||
|
// sorting to avoid loading a plugin more than once.
|
||||||
List<PluginDescription> sortedPlugins = PluginDependencyUtils.sortCandidates(
|
List<PluginDescription> sortedPlugins = PluginDependencyUtils.sortCandidates(
|
||||||
new ArrayList<>(foundCandidates.values()));
|
new ArrayList<>(new LinkedHashSet<>(foundCandidates.values())));
|
||||||
|
|
||||||
Map<String, PluginDescription> loadedCandidates = new HashMap<>();
|
Map<String, PluginDescription> loadedCandidates = new HashMap<>();
|
||||||
Map<PluginContainer, Module> pluginContainers = new LinkedHashMap<>();
|
Map<PluginContainer, Module> pluginContainers = new LinkedHashMap<>();
|
||||||
@@ -144,6 +169,7 @@ public class VelocityPluginManager implements PluginManager {
|
|||||||
VelocityPluginContainer container = new VelocityPluginContainer(realPlugin);
|
VelocityPluginContainer container = new VelocityPluginContainer(realPlugin);
|
||||||
pluginContainers.put(container, loader.createModule(container));
|
pluginContainers.put(container, loader.createModule(container));
|
||||||
loadedCandidates.put(realPlugin.getId(), realPlugin);
|
loadedCandidates.put(realPlugin.getId(), realPlugin);
|
||||||
|
realPlugin.getProvidedIds().forEach(id -> loadedCandidates.putIfAbsent(id, realPlugin));
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
logger.error("Can't create module for plugin {}", candidate.getId(), e);
|
logger.error("Can't create module for plugin {}", candidate.getId(), e);
|
||||||
}
|
}
|
||||||
@@ -201,7 +227,7 @@ public class VelocityPluginManager implements PluginManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<PluginContainer> getPlugins() {
|
public Collection<PluginContainer> getPlugins() {
|
||||||
return Collections.unmodifiableCollection(pluginsById.values());
|
return Collections.unmodifiableCollection(plugins);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+13
-1
@@ -21,6 +21,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.velocitypowered.api.plugin.PluginDescription;
|
import com.velocitypowered.api.plugin.PluginDescription;
|
||||||
import com.velocitypowered.api.plugin.meta.PluginDependency;
|
import com.velocitypowered.api.plugin.meta.PluginDependency;
|
||||||
@@ -43,6 +44,7 @@ public class VelocityPluginDescription implements PluginDescription {
|
|||||||
private final @Nullable String url;
|
private final @Nullable String url;
|
||||||
private final List<String> authors;
|
private final List<String> authors;
|
||||||
private final Map<String, PluginDependency> dependencies;
|
private final Map<String, PluginDependency> dependencies;
|
||||||
|
private final Collection<String> providedIds;
|
||||||
private final Path source;
|
private final Path source;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,11 +57,13 @@ public class VelocityPluginDescription implements PluginDescription {
|
|||||||
* @param url the website for the plugin
|
* @param url the website for the plugin
|
||||||
* @param authors the authors of this plugin
|
* @param authors the authors of this plugin
|
||||||
* @param dependencies the dependencies for this plugin
|
* @param dependencies the dependencies for this plugin
|
||||||
|
* @param providedIds the IDs this plugin provides for
|
||||||
* @param source the original source for the plugin
|
* @param source the original source for the plugin
|
||||||
*/
|
*/
|
||||||
public VelocityPluginDescription(String id, @Nullable String name, @Nullable String version,
|
public VelocityPluginDescription(String id, @Nullable String name, @Nullable String version,
|
||||||
@Nullable String description, @Nullable String url,
|
@Nullable String description, @Nullable String url,
|
||||||
@Nullable List<String> authors, Collection<PluginDependency> dependencies, Path source) {
|
@Nullable List<String> authors, Collection<PluginDependency> dependencies,
|
||||||
|
@Nullable Collection<String> providedIds, Path source) {
|
||||||
this.id = checkNotNull(id, "id");
|
this.id = checkNotNull(id, "id");
|
||||||
this.name = Strings.emptyToNull(name);
|
this.name = Strings.emptyToNull(name);
|
||||||
this.version = Strings.emptyToNull(version);
|
this.version = Strings.emptyToNull(version);
|
||||||
@@ -67,6 +71,8 @@ public class VelocityPluginDescription implements PluginDescription {
|
|||||||
this.url = Strings.emptyToNull(url);
|
this.url = Strings.emptyToNull(url);
|
||||||
this.authors = authors == null ? ImmutableList.of() : ImmutableList.copyOf(authors);
|
this.authors = authors == null ? ImmutableList.of() : ImmutableList.copyOf(authors);
|
||||||
this.dependencies = Maps.uniqueIndex(dependencies, d -> d == null ? null : d.getId());
|
this.dependencies = Maps.uniqueIndex(dependencies, d -> d == null ? null : d.getId());
|
||||||
|
this.providedIds =
|
||||||
|
providedIds == null ? ImmutableSet.of() : ImmutableSet.copyOf(providedIds);
|
||||||
this.source = source;
|
this.source = source;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +116,11 @@ public class VelocityPluginDescription implements PluginDescription {
|
|||||||
return Optional.ofNullable(dependencies.get(id));
|
return Optional.ofNullable(dependencies.get(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> getProvidedIds() {
|
||||||
|
return providedIds;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Path> getSource() {
|
public Optional<Path> getSource() {
|
||||||
return Optional.ofNullable(source);
|
return Optional.ofNullable(source);
|
||||||
@@ -125,6 +136,7 @@ public class VelocityPluginDescription implements PluginDescription {
|
|||||||
+ ", url='" + url + '\''
|
+ ", url='" + url + '\''
|
||||||
+ ", authors=" + authors
|
+ ", authors=" + authors
|
||||||
+ ", dependencies=" + dependencies
|
+ ", dependencies=" + dependencies
|
||||||
|
+ ", providedIds=" + providedIds
|
||||||
+ ", source=" + source
|
+ ", source=" + source
|
||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
|
|||||||
+10
@@ -78,6 +78,14 @@ public class JavaPluginLoader implements PluginLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (String providedId : pd.getProvides()) {
|
||||||
|
if (!SerializedPluginDescription.ID_PATTERN.matcher(providedId).matches()) {
|
||||||
|
throw new InvalidPluginException(
|
||||||
|
"Provided ID '" + providedId + "' for plugin '" + pd.getId() + "' is invalid."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return createCandidateDescription(pd, source);
|
return createCandidateDescription(pd, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,6 +189,7 @@ public class JavaPluginLoader implements PluginLoader {
|
|||||||
description.getUrl(),
|
description.getUrl(),
|
||||||
description.getAuthors(),
|
description.getAuthors(),
|
||||||
dependencies,
|
dependencies,
|
||||||
|
description.getProvides(),
|
||||||
source,
|
source,
|
||||||
description.getMain()
|
description.getMain()
|
||||||
);
|
);
|
||||||
@@ -197,6 +206,7 @@ public class JavaPluginLoader implements PluginLoader {
|
|||||||
description.getUrl().orElse(null),
|
description.getUrl().orElse(null),
|
||||||
description.getAuthors(),
|
description.getAuthors(),
|
||||||
description.getDependencies(),
|
description.getDependencies(),
|
||||||
|
description.getProvidedIds(),
|
||||||
description.getSource().orElse(null),
|
description.getSource().orElse(null),
|
||||||
mainClass
|
mainClass
|
||||||
);
|
);
|
||||||
|
|||||||
+3
-3
@@ -32,9 +32,9 @@ class JavaVelocityPluginDescription extends VelocityPluginDescription {
|
|||||||
|
|
||||||
JavaVelocityPluginDescription(String id, @Nullable String name, @Nullable String version,
|
JavaVelocityPluginDescription(String id, @Nullable String name, @Nullable String version,
|
||||||
@Nullable String description, @Nullable String url,
|
@Nullable String description, @Nullable String url,
|
||||||
@Nullable List<String> authors, Collection<PluginDependency> dependencies, Path source,
|
@Nullable List<String> authors, Collection<PluginDependency> dependencies,
|
||||||
Class<?> mainClass) {
|
@Nullable Collection<String> providedIds, Path source, Class<?> mainClass) {
|
||||||
super(id, name, version, description, url, authors, dependencies, source);
|
super(id, name, version, description, url, authors, dependencies, providedIds, source);
|
||||||
this.mainClass = checkNotNull(mainClass);
|
this.mainClass = checkNotNull(mainClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -32,9 +32,9 @@ class JavaVelocityPluginDescriptionCandidate extends VelocityPluginDescription {
|
|||||||
|
|
||||||
JavaVelocityPluginDescriptionCandidate(String id, @Nullable String name, @Nullable String version,
|
JavaVelocityPluginDescriptionCandidate(String id, @Nullable String name, @Nullable String version,
|
||||||
@Nullable String description, @Nullable String url,
|
@Nullable String description, @Nullable String url,
|
||||||
@Nullable List<String> authors, Collection<PluginDependency> dependencies, Path source,
|
@Nullable List<String> authors, Collection<PluginDependency> dependencies,
|
||||||
String mainClass) {
|
@Nullable Collection<String> providedIds, Path source, String mainClass) {
|
||||||
super(id, name, version, description, url, authors, dependencies, source);
|
super(id, name, version, description, url, authors, dependencies, providedIds, source);
|
||||||
this.mainClass = checkNotNull(mainClass);
|
this.mainClass = checkNotNull(mainClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-4
@@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
package com.velocitypowered.proxy.plugin.util;
|
package com.velocitypowered.proxy.plugin.util;
|
||||||
|
|
||||||
import com.google.common.collect.Maps;
|
|
||||||
import com.google.common.graph.Graph;
|
import com.google.common.graph.Graph;
|
||||||
import com.google.common.graph.GraphBuilder;
|
import com.google.common.graph.GraphBuilder;
|
||||||
import com.google.common.graph.MutableGraph;
|
import com.google.common.graph.MutableGraph;
|
||||||
@@ -60,8 +59,19 @@ public class PluginDependencyUtils {
|
|||||||
.allowsSelfLoops(false)
|
.allowsSelfLoops(false)
|
||||||
.expectedNodeCount(sortedCandidates.size())
|
.expectedNodeCount(sortedCandidates.size())
|
||||||
.build();
|
.build();
|
||||||
Map<String, PluginDescription> candidateMap = Maps.uniqueIndex(sortedCandidates,
|
|
||||||
PluginDescription::getId);
|
// Index candidates by their own ID and any IDs they provide, so a dependency can be satisfied
|
||||||
|
// by a plugin that provides that ID. Real IDs take precedence over provided IDs, and the first
|
||||||
|
// provider of a given ID wins; upstream loading rejects such conflicts before we get here.
|
||||||
|
Map<String, PluginDescription> candidateMap = new HashMap<>();
|
||||||
|
for (PluginDescription description : sortedCandidates) {
|
||||||
|
candidateMap.putIfAbsent(description.getId(), description);
|
||||||
|
}
|
||||||
|
for (PluginDescription description : sortedCandidates) {
|
||||||
|
for (String provided : description.getProvidedIds()) {
|
||||||
|
candidateMap.putIfAbsent(provided, description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (PluginDescription description : sortedCandidates) {
|
for (PluginDescription description : sortedCandidates) {
|
||||||
graph.addNode(description);
|
graph.addNode(description);
|
||||||
@@ -69,7 +79,8 @@ public class PluginDependencyUtils {
|
|||||||
for (PluginDependency dependency : description.getDependencies()) {
|
for (PluginDependency dependency : description.getDependencies()) {
|
||||||
PluginDescription in = candidateMap.get(dependency.getId());
|
PluginDescription in = candidateMap.get(dependency.getId());
|
||||||
|
|
||||||
if (in != null) {
|
// Guard against self-loops: a plugin may name an ID it itself provides.
|
||||||
|
if (in != null && !in.equals(description)) {
|
||||||
graph.putEdge(description, in);
|
graph.putEdge(description, in);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-1
@@ -44,6 +44,15 @@ class PluginDependencyUtilsTest {
|
|||||||
private static final PluginDescription CIRCULAR_DEPENDENCY_2 = testDescription("oval",
|
private static final PluginDescription CIRCULAR_DEPENDENCY_2 = testDescription("oval",
|
||||||
new PluginDependency("circle", "", false));
|
new PluginDependency("circle", "", false));
|
||||||
|
|
||||||
|
// "provider" is loaded from a real ID but provides the virtual ID "some-api"; "consumer" and
|
||||||
|
// "zdependent" depend on it only through that provided ID / a chain that reaches it.
|
||||||
|
private static final PluginDescription PROVIDES_API = providingDescription("provider",
|
||||||
|
ImmutableList.of("some-api"));
|
||||||
|
private static final PluginDescription DEPENDS_ON_PROVIDED = providingDescription("consumer",
|
||||||
|
ImmutableList.of(), new PluginDependency("some-api", null, false));
|
||||||
|
private static final PluginDescription DEPENDS_ON_CONSUMER = testDescription("zdependent",
|
||||||
|
new PluginDependency("consumer", null, false));
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void sortCandidatesTrivial() throws Exception {
|
void sortCandidatesTrivial() throws Exception {
|
||||||
List<PluginDescription> descriptionList = new ArrayList<>();
|
List<PluginDescription> descriptionList = new ArrayList<>();
|
||||||
@@ -96,10 +105,31 @@ class PluginDependencyUtilsTest {
|
|||||||
assertThrows(IllegalStateException.class, () -> PluginDependencyUtils.sortCandidates(descs));
|
assertThrows(IllegalStateException.class, () -> PluginDependencyUtils.sortCandidates(descs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void sortCandidatesResolvesProvidedDependency() throws Exception {
|
||||||
|
List<PluginDescription> plugins = ImmutableList.of(DEPENDS_ON_PROVIDED, PROVIDES_API);
|
||||||
|
List<PluginDescription> expected = ImmutableList.of(PROVIDES_API, DEPENDS_ON_PROVIDED);
|
||||||
|
assertEquals(expected, PluginDependencyUtils.sortCandidates(plugins));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void sortCandidatesResolvesTransitiveProvidedDependency() throws Exception {
|
||||||
|
List<PluginDescription> plugins = ImmutableList.of(DEPENDS_ON_CONSUMER, DEPENDS_ON_PROVIDED,
|
||||||
|
PROVIDES_API);
|
||||||
|
List<PluginDescription> expected = ImmutableList.of(PROVIDES_API, DEPENDS_ON_PROVIDED,
|
||||||
|
DEPENDS_ON_CONSUMER);
|
||||||
|
assertEquals(expected, PluginDependencyUtils.sortCandidates(plugins));
|
||||||
|
}
|
||||||
|
|
||||||
private static PluginDescription testDescription(String id, PluginDependency... dependencies) {
|
private static PluginDescription testDescription(String id, PluginDependency... dependencies) {
|
||||||
|
return providingDescription(id, ImmutableList.of(), dependencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PluginDescription providingDescription(String id, List<String> provides,
|
||||||
|
PluginDependency... dependencies) {
|
||||||
return new VelocityPluginDescription(
|
return new VelocityPluginDescription(
|
||||||
id, "tuxed", "0.1", null, null, ImmutableList.of(),
|
id, "tuxed", "0.1", null, null, ImmutableList.of(),
|
||||||
ImmutableList.copyOf(dependencies), null
|
ImmutableList.copyOf(dependencies), provides, null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user