Switch over to Error Prone (now with NullAway!)

There is one major change: we now have a separate artifact for the annotation processor.

As for NullAway, we are currently exempting the clientbound join game/respawn packets. They are ugly and need to be refactored.
This commit is contained in:
Andrew Steinborn
2021-05-13 04:13:15 -04:00
parent c496d912ea
commit 3c41211163
79 changed files with 494 additions and 401 deletions

View File

@@ -1,102 +0,0 @@
/*
* Copyright (C) 2018 Velocity Contributors
*
* The Velocity API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the api top-level directory.
*/
package com.velocitypowered.api.plugin.ap;
import com.google.gson.Gson;
import com.velocitypowered.api.plugin.Plugin;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Objects;
import java.util.Set;
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;
@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 synchronized 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 (!SerializedPluginDescription.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;
}
}

View File

@@ -1,177 +0,0 @@
/*
* Copyright (C) 2018 Velocity Contributors
*
* The Velocity API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the api top-level directory.
*/
package com.velocitypowered.api.plugin.ap;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.plugin.Plugin;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.checkerframework.checker.nullness.qual.Nullable;
public final class SerializedPluginDescription {
public static final Pattern ID_PATTERN = Pattern.compile("[a-z][a-z0-9-_]{0,63}");
// @Nullable is used here to make GSON skip these in the serialized file
private final String id;
private final @Nullable String name;
private final @Nullable String version;
private final @Nullable String description;
private final @Nullable String url;
private final @Nullable List<String> authors;
private final @Nullable List<Dependency> dependencies;
private final String main;
private SerializedPluginDescription(String id, String name, String version, String description,
String url,
List<String> authors, List<Dependency> dependencies, String main) {
Preconditions.checkNotNull(id, "id");
Preconditions.checkArgument(ID_PATTERN.matcher(id).matches(), "id is not valid");
this.id = id;
this.name = Strings.emptyToNull(name);
this.version = Strings.emptyToNull(version);
this.description = Strings.emptyToNull(description);
this.url = Strings.emptyToNull(url);
this.authors = authors == null || authors.isEmpty() ? ImmutableList.of() : authors;
this.dependencies =
dependencies == null || dependencies.isEmpty() ? ImmutableList.of() : dependencies;
this.main = Preconditions.checkNotNull(main, "main");
}
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.name(), plugin.version(),
plugin.description(), plugin.url(),
Arrays.stream(plugin.authors()).filter(author -> !author.isEmpty())
.collect(Collectors.toList()), dependencies, qualifiedName);
}
public String getId() {
return id;
}
public @Nullable String getName() {
return name;
}
public @Nullable String getVersion() {
return version;
}
public @Nullable String getDescription() {
return description;
}
public @Nullable String getUrl() {
return url;
}
public List<String> getAuthors() {
return authors == null ? ImmutableList.of() : authors;
}
public List<Dependency> getDependencies() {
return dependencies == null ? ImmutableList.of() : dependencies;
}
public String getMain() {
return main;
}
@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(name, that.name)
&& Objects.equals(version, that.version)
&& Objects.equals(description, that.description)
&& Objects.equals(url, that.url)
&& Objects.equals(authors, that.authors)
&& Objects.equals(dependencies, that.dependencies)
&& Objects.equals(main, that.main);
}
@Override
public int hashCode() {
return Objects.hash(id, name, version, description, url, authors, dependencies);
}
@Override
public String toString() {
return "SerializedPluginDescription{"
+ "id='" + id + '\''
+ ", name='" + name + '\''
+ ", version='" + version + '\''
+ ", description='" + description + '\''
+ ", url='" + url + '\''
+ ", authors=" + authors
+ ", dependencies=" + dependencies
+ ", main='" + main + '\''
+ '}';
}
public static final 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
+ '}';
}
}
}

View File

@@ -1 +0,0 @@
com.velocitypowered.api.plugin.ap.PluginAnnotationProcessor

View File

@@ -7,6 +7,8 @@
package com.velocitypowered.api.event;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Allows a listener to receive direct dispatches of events. This interface can be used directly
* by a listener (using {@link EventManager#register(Object, Class, short, EventHandler)} or
@@ -15,5 +17,5 @@ package com.velocitypowered.api.event;
@FunctionalInterface
public interface EventHandler<E> {
EventTask execute(E event);
@Nullable EventTask execute(E event);
}

View File

@@ -64,7 +64,9 @@ public interface EventManager {
*
* @param event the event to fire
*/
@SuppressWarnings("FutureReturnValueIgnored")
default void fireAndForget(Object event) {
// Calling fire(Object) and not handling it is intentional.
fire(event);
}

View File

@@ -174,6 +174,9 @@ public abstract class EventTask {
* @param future The task to wait for
* @return The event task
*/
// The Error Prone annotation here is spurious. The Future is handled via the CompletableFuture
// API, which does NOT use the traditional blocking model.
@SuppressWarnings("FutureReturnValueIgnored")
public static EventTask.WithContinuation resumeWhenComplete(
final CompletableFuture<?> future) {
requireNonNull(future, "future");

View File

@@ -88,7 +88,7 @@ public interface ResultedEvent<R extends ResultedEvent.Result> {
private final @Nullable Component reason;
protected ComponentResult(@Nullable Component reason) {
private ComponentResult(@Nullable Component reason) {
this.reason = reason;
}

View File

@@ -22,7 +22,7 @@ public final class ConnectionHandshakeEventImpl implements ConnectionHandshakeEv
private final String originalHostname;
private String currentHostname;
private ComponentResult result;
private SocketAddress currentRemoteAddress;
private @Nullable SocketAddress currentRemoteAddress;
public ConnectionHandshakeEventImpl(InboundConnection connection,
String originalHostname) {
@@ -59,7 +59,7 @@ public final class ConnectionHandshakeEventImpl implements ConnectionHandshakeEv
}
@Override
public void setCurrentRemoteHostAddress(SocketAddress address) {
public void setCurrentRemoteHostAddress(@Nullable SocketAddress address) {
currentRemoteAddress = address;
}

View File

@@ -130,7 +130,7 @@ public interface KickedFromServerEvent extends
*/
final class RedirectPlayer implements ServerKickResult {
private final Component message;
private final @Nullable Component message;
private final RegisteredServer server;
private RedirectPlayer(RegisteredServer server,
@@ -148,7 +148,7 @@ public interface KickedFromServerEvent extends
return server;
}
public Component message() {
public @Nullable Component message() {
return message;
}

View File

@@ -19,7 +19,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
public class ServerPostConnectEventImpl implements ServerPostConnectEvent {
private final Player player;
private final RegisteredServer previousServer;
private final @Nullable RegisteredServer previousServer;
public ServerPostConnectEventImpl(Player player,
@Nullable RegisteredServer previousServer) {

View File

@@ -98,7 +98,7 @@ public interface ServerPreConnectEvent extends ResultedEvent<ServerPreConnectEve
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
if (!(o instanceof ServerResult)) {
return false;
}
ServerResult that = (ServerResult) o;

View File

@@ -60,7 +60,7 @@ public enum ProtocolVersion implements Ordered<ProtocolVersion> {
private final int protocol;
private final int snapshotProtocol;
private final String[] names;
private final ImmutableList<String> names;
/**
* Represents the lowest supported version.
@@ -127,7 +127,7 @@ public enum ProtocolVersion implements Ordered<ProtocolVersion> {
}
this.protocol = protocol;
this.names = names;
this.names = ImmutableList.copyOf(names);
}
/**
@@ -146,7 +146,7 @@ public enum ProtocolVersion implements Ordered<ProtocolVersion> {
* @return the version name
*/
public String versionIntroducedIn() {
return names[0];
return names.get(0);
}
/**
@@ -156,7 +156,7 @@ public enum ProtocolVersion implements Ordered<ProtocolVersion> {
* @return the version name
*/
public String mostRecentSupportedVersion() {
return names[names.length - 1];
return names.get(names.size() - 1);
}
/**

View File

@@ -22,7 +22,7 @@ public interface InboundConnection {
*
* @return the player's remote address
*/
SocketAddress remoteAddress();
@Nullable SocketAddress remoteAddress();
/**
* Returns the hostname that the user entered into the client, if applicable.

View File

@@ -148,7 +148,7 @@ public interface Player extends CommandSource, Identified, InboundConnection,
* from the player, you should use the equivalent method on the instance returned by
* {@link #connectedServer()}.
*
* @inheritDoc
* {@inheritDoc}
*/
@Override
boolean sendPluginMessage(PluginChannelId identifier, byte[] data);

View File

@@ -33,14 +33,14 @@ public final class QueryResponse {
private final int maxPlayers;
private final String proxyHost;
private final int proxyPort;
private final ImmutableCollection<String> players;
private final ImmutableList<String> players;
private final String proxyVersion;
private final ImmutableCollection<PluginInformation> plugins;
private final ImmutableList<PluginInformation> plugins;
@VisibleForTesting
QueryResponse(String hostname, String gameVersion, String map, int onlinePlayers,
int maxPlayers, String proxyHost, int proxyPort, ImmutableCollection<String> players,
String proxyVersion, ImmutableCollection<PluginInformation> plugins) {
int maxPlayers, String proxyHost, int proxyPort, ImmutableList<String> players,
String proxyVersion, ImmutableList<PluginInformation> plugins) {
this.hostname = hostname;
this.gameVersion = gameVersion;
this.map = map;

View File

@@ -150,7 +150,7 @@ public final class ServerPing {
private final List<SamplePlayer> samplePlayers = new ArrayList<>();
private String modType = "FML";
private final List<ModInfo.Mod> mods = new ArrayList<>();
private Component description;
private @Nullable Component description;
private @Nullable Favicon favicon;
private boolean nullOutPlayers;
private boolean nullOutModinfo;