Skip to content

Commit

Permalink
Plugin API (#34)
Browse files Browse the repository at this point in the history
The Velocity API has had a lot of community input (special thanks to @hugmanrique who started the work, @lucko who contributed permissions support, and @Minecrell for providing initial feedback and an initial version of ServerListPlus).

While the API is far from complete, there is enough available for people to start doing useful stuff with Velocity.
  • Loading branch information
astei committed Aug 20, 2018
1 parent 8e836a5 commit a028467
Show file tree
Hide file tree
Showing 89 changed files with 3,453 additions and 358 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
.idea/**/compiler.xml

# Sensitive or high-churn files
.idea/**/dataSources/
Expand Down
39 changes: 39 additions & 0 deletions api/build.gradle
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
plugins {
id 'java'
id 'com.github.johnrengelman.shadow' version '2.0.4'
id 'maven-publish'
}

sourceSets {
ap {
compileClasspath += main.compileClasspath + main.output
}
}

dependencies {
compile 'com.google.code.gson:gson:2.8.5'
compile "com.google.guava:guava:${guavaVersion}"
compile 'net.kyori:text:1.12-1.6.4'
compile 'com.moandjiezana.toml:toml4j:0.7.2'
compile "org.slf4j:slf4j-api:${slf4jVersion}"
compile 'com.google.inject:guice:4.2.0'
compile 'org.checkerframework:checker-qual:2.5.4'

testCompile "org.junit.jupiter:junit-jupiter-api:${junitVersion}"
testCompile "org.junit.jupiter:junit-jupiter-engine:${junitVersion}"
}
Expand All @@ -20,10 +31,38 @@ task javadocJar(type: Jar) {
task sourcesJar(type: Jar) {
classifier 'sources'
from sourceSets.main.allSource
from sourceSets.ap.output
}

jar {
from sourceSets.ap.output
}

shadowJar {
from sourceSets.ap.output
}

artifacts {
archives javadocJar
archives shadowJar
archives sourcesJar
}

publishing {
publications {
mavenJava(MavenPublication) {
from components.java

artifact sourcesJar
artifact javadocJar
}
}

// TODO: Set up a Maven repository on Velocity's infrastructure, preferably something lightweight.
/*repositories {
maven {
name = 'myRepo'
url = "file://${buildDir}/repo"
}
}*/
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.velocitypowered.api.plugin.ap;

import com.google.gson.Gson;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.plugin.PluginDescription;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.*;

@SupportedAnnotationTypes({"com.velocitypowered.api.plugin.Plugin"})
public class PluginAnnotationProcessor extends AbstractProcessor {
private ProcessingEnvironment environment;
private String pluginClassFound;
private boolean warnedAboutMultiplePlugins;

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
this.environment = processingEnv;
}

@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (roundEnv.processingOver()) {
return false;
}

for (Element element : roundEnv.getElementsAnnotatedWith(Plugin.class)) {
if (element.getKind() != ElementKind.CLASS) {
environment.getMessager().printMessage(Diagnostic.Kind.ERROR, "Only classes can be annotated with "
+ Plugin.class.getCanonicalName());
return false;
}

Name qualifiedName = ((TypeElement) element).getQualifiedName();

if (Objects.equals(pluginClassFound, qualifiedName.toString())) {
if (!warnedAboutMultiplePlugins) {
environment.getMessager().printMessage(Diagnostic.Kind.WARNING, "Velocity does not yet currently support " +
"multiple plugins. We are using " + pluginClassFound + " for your plugin's main class.");
warnedAboutMultiplePlugins = true;
}
return false;
}

Plugin plugin = element.getAnnotation(Plugin.class);
if (!PluginDescription.ID_PATTERN.matcher(plugin.id()).matches()) {
environment.getMessager().printMessage(Diagnostic.Kind.ERROR, "Invalid ID for plugin "
+ qualifiedName + ". IDs must start alphabetically, have alphanumeric characters, and can " +
"contain dashes or underscores.");
return false;
}

// All good, generate the velocity-plugin.json.
SerializedPluginDescription description = SerializedPluginDescription.from(plugin, qualifiedName.toString());
try {
FileObject object = environment.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", "velocity-plugin.json");
try (Writer writer = new BufferedWriter(object.openWriter())) {
new Gson().toJson(description, writer);
}
pluginClassFound = qualifiedName.toString();
} catch (IOException e) {
environment.getMessager().printMessage(Diagnostic.Kind.ERROR, "Unable to generate plugin file");
}
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package com.velocitypowered.api.plugin.ap;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.plugin.Plugin;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class SerializedPluginDescription {
private final String id;
private final String author;
private final String main;
private final String version;
private final List<Dependency> dependencies;

public SerializedPluginDescription(String id, String author, String main, String version) {
this(id, author, main, version, ImmutableList.of());
}

public SerializedPluginDescription(String id, String author, String main, String version, List<Dependency> dependencies) {
this.id = Preconditions.checkNotNull(id, "id");
this.author = Preconditions.checkNotNull(author, "author");
this.main = Preconditions.checkNotNull(main, "main");
this.version = Preconditions.checkNotNull(version, "version");
this.dependencies = ImmutableList.copyOf(dependencies);
}

public static SerializedPluginDescription from(Plugin plugin, String qualifiedName) {
List<Dependency> dependencies = new ArrayList<>();
for (com.velocitypowered.api.plugin.Dependency dependency : plugin.dependencies()) {
dependencies.add(new Dependency(dependency.id(), dependency.optional()));
}
return new SerializedPluginDescription(plugin.id(), plugin.author(), qualifiedName, plugin.version(), dependencies);
}

public String getId() {
return id;
}

public String getAuthor() {
return author;
}

public String getMain() {
return main;
}

public String getVersion() {
return version;
}

public List<Dependency> getDependencies() {
return dependencies;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SerializedPluginDescription that = (SerializedPluginDescription) o;
return Objects.equals(id, that.id) &&
Objects.equals(author, that.author) &&
Objects.equals(main, that.main) &&
Objects.equals(version, that.version) &&
Objects.equals(dependencies, that.dependencies);
}

@Override
public int hashCode() {
return Objects.hash(id, author, main, version, dependencies);
}

@Override
public String toString() {
return "SerializedPluginDescription{" +
"id='" + id + '\'' +
", author='" + author + '\'' +
", main='" + main + '\'' +
", version='" + version + '\'' +
", dependencies=" + dependencies +
'}';
}

public static class Dependency {
private final String id;
private final boolean optional;

public Dependency(String id, boolean optional) {
this.id = id;
this.optional = optional;
}

public String getId() {
return id;
}

public boolean isOptional() {
return optional;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Dependency that = (Dependency) o;
return optional == that.optional &&
Objects.equals(id, that.id);
}

@Override
public int hashCode() {
return Objects.hash(id, optional);
}

@Override
public String toString() {
return "Dependency{" +
"id='" + id + '\'' +
", optional=" + optional +
'}';
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.velocitypowered.api.plugin.ap.PluginAnnotationProcessor
29 changes: 29 additions & 0 deletions api/src/main/java/com/velocitypowered/api/command/Command.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.velocitypowered.api.command;

import com.google.common.collect.ImmutableList;
import org.checkerframework.checker.nullness.qual.NonNull;

import java.util.List;

/**
* Represents a command that can be executed by a {@link CommandSource}, such as a {@link com.velocitypowered.api.proxy.Player}
* or the console.
*/
public interface Command {
/**
* Executes the command for the specified {@link CommandSource}.
* @param source the source of this command
* @param args the arguments for this command
*/
void execute(@NonNull CommandSource source, @NonNull String[] args);

/**
* Provides tab complete suggestions for a command for a specified {@link CommandSource}.
* @param source the source to run the command for
* @param currentArgs the current, partial arguments for this command
* @return tab complete suggestions
*/
default List<String> suggest(@NonNull CommandSource source, @NonNull String[] currentArgs) {
return ImmutableList.of();
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.velocitypowered.api.command;

import org.checkerframework.checker.nullness.qual.NonNull;

/**
* Represents an interface to register a command executor with the proxy.
*/
public interface CommandManager {
void register(@NonNull Command command, String... aliases);

void unregister(@NonNull String alias);

boolean execute(@NonNull CommandSource source, @NonNull String cmdLine);
}
Loading

0 comments on commit a028467

Please sign in to comment.