diff --git a/build.gradle b/build.gradle
index 41ba86c..f1c8306 100644
--- a/build.gradle
+++ b/build.gradle
@@ -83,6 +83,18 @@ dependencies {
testImplementation 'org.hamcrest:hamcrest:2.2'
}
+task buildResources {
+ doLast {
+ File to = new File("${buildDir}/classes/java/main/META-INF/services/javax.annotation.processing.Processor")
+ to.parentFile.mkdirs()
+ if (!to.exists()) {
+ to.createNewFile()
+ to.append("de.steamwar.linkage.LinkageProcessor\n")
+ }
+ }
+}
+classes.finalizedBy(buildResources)
+
task buildProject {
description 'Build this project'
group "Steamwar"
diff --git a/src/de/steamwar/linkage/AllowedContexts.java b/src/de/steamwar/linkage/AllowedContexts.java
new file mode 100644
index 0000000..775aaf5
--- /dev/null
+++ b/src/de/steamwar/linkage/AllowedContexts.java
@@ -0,0 +1,36 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2022 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.linkage;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.ANNOTATION_TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface AllowedContexts {
+
+ /**
+ * The context in which this annotation is valid.
+ */
+ LinkageType.Context[] value();
+
+}
diff --git a/src/de/steamwar/linkage/DiscordMode.java b/src/de/steamwar/linkage/DiscordMode.java
new file mode 100644
index 0000000..f30bab8
--- /dev/null
+++ b/src/de/steamwar/linkage/DiscordMode.java
@@ -0,0 +1,31 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2022 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.linkage;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@AllowedContexts(LinkageType.Context.BUNGEE)
+@Retention(RetentionPolicy.SOURCE)
+@Target({ElementType.TYPE})
+public @interface DiscordMode {
+}
diff --git a/src/de/steamwar/linkage/EventMode.java b/src/de/steamwar/linkage/EventMode.java
new file mode 100644
index 0000000..86c8528
--- /dev/null
+++ b/src/de/steamwar/linkage/EventMode.java
@@ -0,0 +1,44 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2022 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.linkage;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@AllowedContexts(LinkageType.Context.BUNGEE)
+@Retention(RetentionPolicy.SOURCE)
+@Target({ElementType.TYPE})
+public @interface EventMode {
+ Mode value();
+
+ @AllArgsConstructor
+ enum Mode {
+ EventOnly(""),
+ NonEvent("!");
+
+ @Getter
+ private String prefix;
+ }
+}
diff --git a/src/de/steamwar/linkage/LinkageProcessor.java b/src/de/steamwar/linkage/LinkageProcessor.java
new file mode 100644
index 0000000..6f0fbd9
--- /dev/null
+++ b/src/de/steamwar/linkage/LinkageProcessor.java
@@ -0,0 +1,352 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2022 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.linkage;
+
+import de.steamwar.linkage.plan.BuildPlan;
+import de.steamwar.linkage.plan.FieldBuilder;
+import de.steamwar.linkage.plan.MethodBuilder;
+import de.steamwar.linkage.plan.ParameterBuilder;
+import lombok.Getter;
+import lombok.SneakyThrows;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.*;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+import javax.tools.Diagnostic;
+import java.io.*;
+import java.io.Writer;
+import java.lang.annotation.Annotation;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class LinkageProcessor extends AbstractProcessor {
+
+ @Getter
+ private static LinkageType.Context context;
+
+ @Getter
+ private static String pluginMain;
+
+ private String name;
+ private String packageName;
+ private String className;
+
+ private Messager messager;
+ private boolean processed = false;
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latestSupported();
+ }
+
+ @Override
+ public Set getSupportedAnnotationTypes() {
+ return Stream.of(Linked.class, Linked.Linkages.class)
+ .map(Class::getCanonicalName)
+ .collect(Collectors.toSet());
+ }
+
+ @SneakyThrows
+ @Override
+ public synchronized void init(ProcessingEnvironment processingEnv) {
+ super.init(processingEnv);
+
+ String name = new File(System.getProperty("user.dir")).getName();
+ if (name.contains(".")) name = name.substring(0, name.indexOf('.'));
+ name = name.toLowerCase();
+
+ messager = processingEnv.getMessager();
+
+ this.name = name;
+ packageName = "de.steamwar." + name + ".linkage";
+ className = "LinkageUtils";
+ mainClass();
+ }
+
+ @SneakyThrows
+ private void mainClass() {
+ File file = new File(System.getProperty("user.dir"));
+ Optional pluginYMLFile = Files.walk(file.toPath())
+ .map(Path::toFile)
+ .filter(File::isFile)
+ .filter(f -> f.getName().equals("plugin.yml") || f.getName().equals("bungee.yml"))
+ .findFirst();
+ if (!pluginYMLFile.isPresent()) {
+ messager.printMessage(Diagnostic.Kind.ERROR, "Could not find plugin.yml or bungee.yml");
+ return;
+ }
+ context = pluginYMLFile.get().getName().equals("bungee.yml") ? LinkageType.Context.BUNGEE : LinkageType.Context.SPIGOT;
+ Optional mainName = getMainName(pluginYMLFile.get());
+ if (!mainName.isPresent()) {
+ messager.printMessage(Diagnostic.Kind.ERROR, "Could not find main class in plugin.yml or bungee.yml");
+ return;
+ }
+ pluginMain = mainName.get();
+ }
+
+ @SneakyThrows
+ private Optional getMainName(File pluginYML) {
+ if (!pluginYML.exists()) return Optional.empty();
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(pluginYML)))) {
+ return reader.lines()
+ .filter(line -> line.startsWith("main:"))
+ .map(line -> line.substring(line.indexOf(':') + 1).trim())
+ .findFirst();
+ }
+ }
+
+ @SneakyThrows
+ @Override
+ public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ if (processed) return false;
+ processed = true;
+
+ Writer writer = processingEnv.getFiler().createSourceFile("de.steamwar." + name + ".linkage.LinkageUtils").openWriter();
+
+ BuildPlan buildPlan = new BuildPlan(packageName, className);
+ buildPlan.addImport("java.util.Set");
+ buildPlan.addImport("java.lang.Class");
+ buildPlan.addImport("de.steamwar.linkage.LinkageType");
+ buildPlan.addImport("java.util.HashSet");
+ buildPlan.addField(new FieldBuilder("Set>", "enabled", "new HashSet<>()"));
+
+ MethodBuilder runsMethod = new MethodBuilder("run", "void");
+ runsMethod.addParameter(new ParameterBuilder("Class extends LinkageType>...", "types"));
+ runsMethod.addLine("for (Class extends LinkageType> type : types) _run(type);");
+ buildPlan.addMethod(runsMethod);
+
+
+ MethodBuilder runMethod = new MethodBuilder("_run", "void");
+ runMethod.setPrivate(true);
+ buildPlan.addMethod(runMethod);
+ runMethod.addParameter(new ParameterBuilder("Class extends LinkageType>", "type"));
+ runMethod.addLine("if (!enabled.add(type)) return;");
+ Set> alreadyGenerated = new HashSet<>();
+
+ Set extends Element> elements = roundEnv.getElementsAnnotatedWith(Linked.class);
+ elements.addAll((Set) roundEnv.getElementsAnnotatedWith(Linked.Linkages.class));
+
+ Map neededFields = new HashMap<>();
+ for (Element element : elements) {
+ if (element.getKind() != ElementKind.CLASS) {
+ continue;
+ }
+ TypeElement typeElement = (TypeElement) element;
+ Linked[] linkeds = element.getAnnotationsByType(Linked.class);
+ if (linkeds.length == 0) {
+ continue;
+ }
+
+ if (linkeds.length > 1) {
+ neededFields.put(typeElement.getQualifiedName().toString(), typeElement);
+ }
+
+ List variableElements = typeElement.getEnclosedElements().stream().filter(e -> e.getKind() == ElementKind.FIELD).map(VariableElement.class::cast).filter(e -> {
+ return e.getAnnotation(LinkedInstance.class) != null;
+ }).collect(Collectors.toList());
+ if (variableElements.isEmpty()) {
+ continue;
+ }
+
+ for (VariableElement variableElement : variableElements) {
+ if (!variableElement.getModifiers().contains(Modifier.PUBLIC)) {
+ messager.printMessage(Diagnostic.Kind.ERROR, "Field " + variableElement.getSimpleName() + " must be public", variableElement);
+ continue;
+ }
+ if (variableElement.getModifiers().contains(Modifier.STATIC)) {
+ messager.printMessage(Diagnostic.Kind.ERROR, "Field " + variableElement.getSimpleName() + " must be non static", variableElement);
+ continue;
+ }
+ if (variableElement.getModifiers().contains(Modifier.FINAL)) {
+ messager.printMessage(Diagnostic.Kind.ERROR, "Field " + variableElement.getSimpleName() + " must be non final", variableElement);
+ continue;
+ }
+ neededFields.put(typeElement.getQualifiedName().toString(), typeElement);
+ TypeElement fieldType = (TypeElement) ((DeclaredType) variableElement.asType()).asElement();
+ neededFields.put(fieldType.getQualifiedName().toString(), fieldType);
+
+ specialElements(typeElement, buildPlan, buildPlan::addStaticLine, () -> {
+ buildPlan.addStaticLine(getElement(typeElement, neededFields) + "." + variableElement.getSimpleName().toString() + " = " + getElement((TypeElement) ((DeclaredType) variableElement.asType()).asElement(), neededFields) + ";");
+ });
+ }
+ }
+ neededFields.forEach((s, typeElement) -> {
+ buildPlan.addImport(typeElement.getQualifiedName().toString());
+ buildPlan.addField(new FieldBuilder(typeElement.getSimpleName().toString(), typeElement.getSimpleName().toString()));
+
+ MethodBuilder initializer = new MethodBuilder(typeElement.getSimpleName().toString(), typeElement.getSimpleName().toString());
+ initializer.setPrivate(true);
+ specialElements(typeElement, buildPlan, initializer::addLine, () -> {
+ initializer.addLine("if (" + typeElement.getSimpleName().toString() + " == null) {");
+ initializer.addLine(" " + typeElement.getSimpleName().toString() + " = new " + typeElement.getSimpleName().toString() + "();");
+ initializer.addLine("}");
+ });
+ initializer.addLine("return " + typeElement.getSimpleName().toString() + ";");
+ buildPlan.addMethod(initializer);
+ });
+
+ Map, MethodBuilder> methods = new HashMap<>();
+ for (Element element : elements) {
+ if (element.getKind() != ElementKind.CLASS) {
+ continue;
+ }
+ TypeElement typeElement = (TypeElement) element;
+
+ System.out.println("Found element: " + typeElement.getQualifiedName().toString());
+ element.getAnnotationMirrors().stream()
+ .filter(annotationMirror -> {
+ String annotationNames = annotationMirror.getAnnotationType().asElement().getSimpleName().toString();
+ return annotationNames.equals("Linked") || annotationNames.equals("Linkages");
+ })
+ .flatMap(annotationMirror -> annotationMirror.getElementValues().values().stream())
+ .map(AnnotationValue::getValue)
+ .flatMap(o -> {
+ if (o instanceof List) {
+ return ((List