diff --git a/.checkstyle/suppressions.xml b/.checkstyle/suppressions.xml index d329e952..d451b3c6 100644 --- a/.checkstyle/suppressions.xml +++ b/.checkstyle/suppressions.xml @@ -2,6 +2,6 @@ - + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 19d3dd76..eb346ad0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ blossom = "2.1.0" shadow = "9.3.1" paper = "1.21.10-R0.1-SNAPSHOT" -velocity = "3.4.0-SNAPSHOT" +velocity = "3.5.0-SNAPSHOT" minecraft = "1.21.10" brigadier = "1.4.0" guice = "7.0.0" @@ -22,6 +22,8 @@ jakarta-inject = "2.0.1" jspecify = "1.0.0" jetbrains-annotations = "24.0.0" +junit = "6.0.3" + [libraries] paper-api = { module = "io.papermc.paper:paper-api", version.ref = "paper" } velocity-api = { module = "com.velocitypowered:velocity-api", version.ref = "velocity" } @@ -36,6 +38,11 @@ minecraft = { module = "com.mojang:minecraft", version.ref = "minecraft" } fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" } fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric" } +# Junit +junit-bom = { module = "org.junit:junit-bom", version.ref = "junit" } +junit-jupiter = { module = "org.junit.jupiter:junit-jupiter" } +junit-platform = { module = "org.junit.platform:junit-platform-launcher" } + [plugins] publishdata = { id = "de.chojo.publishdata", version.ref = "publishdata" } run-paper = { id = "xyz.jpenilla.run-paper", version.ref = "run" } diff --git a/processor/common/build.gradle.kts b/processor/common/build.gradle.kts index b3992acf..1b39fae5 100644 --- a/processor/common/build.gradle.kts +++ b/processor/common/build.gradle.kts @@ -1,10 +1,36 @@ plugins { alias(libs.plugins.blossom) + id("jacoco") id("commands-publish") } dependencies { compileOnlyApi(project(":annotations-common")) + + testImplementation(platform(libs.junit.bom)) + testImplementation(libs.junit.jupiter) + testRuntimeOnly(libs.junit.platform) +} + +jacoco { + toolVersion = "0.8.14" +} + +tasks { + test { + useJUnitPlatform() + testLogging { + events("skipped", "failed") + } + } + + jacocoTestReport { + dependsOn(test) + reports { + xml.required = true + html.required = true + } + } } sourceSets.main { diff --git a/processor/common/src/main/external-annotations/java/util/annotations.xml b/processor/common/src/main/external-annotations/java/util/annotations.xml new file mode 100644 index 00000000..82f579ba --- /dev/null +++ b/processor/common/src/main/external-annotations/java/util/annotations.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/processor/common/src/main/java/module-info.java b/processor/common/src/main/java/module-info.java index 5614c916..ec355bd3 100644 --- a/processor/common/src/main/java/module-info.java +++ b/processor/common/src/main/java/module-info.java @@ -9,10 +9,18 @@ requires static transitive org.jspecify; requires jdk.sctp; requires java.xml; + requires java.desktop; + requires jakarta.inject; exports net.strokkur.commands.internal; exports net.strokkur.commands.internal.abstraction; exports net.strokkur.commands.internal.arguments; + exports net.strokkur.commands.internal.codegen; + exports net.strokkur.commands.internal.codegen.builder; + exports net.strokkur.commands.internal.codegen.adapter; + exports net.strokkur.commands.internal.codegen.as; + exports net.strokkur.commands.internal.codegen.javadoc; + exports net.strokkur.commands.internal.codegen.visitor; exports net.strokkur.commands.internal.exceptions; exports net.strokkur.commands.internal.intermediate; exports net.strokkur.commands.internal.intermediate.access; @@ -21,6 +29,8 @@ exports net.strokkur.commands.internal.intermediate.tree; exports net.strokkur.commands.internal.parsing; exports net.strokkur.commands.internal.printer; + exports net.strokkur.commands.internal.printer.javadoc; + exports net.strokkur.commands.internal.printer.source; exports net.strokkur.commands.internal.util; exports net.strokkur.commands.internal.intermediate.executable; } diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/StrokkCommandsProcessor.java b/processor/common/src/main/java/net/strokkur/commands/internal/StrokkCommandsProcessor.java index 98e2e9d9..f05fcb75 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/StrokkCommandsProcessor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/StrokkCommandsProcessor.java @@ -25,6 +25,8 @@ import net.strokkur.commands.internal.abstraction.impl.SourceRecordImpl; import net.strokkur.commands.internal.abstraction.impl.SourceTypeUtils; import net.strokkur.commands.internal.arguments.BrigadierArgumentConverter; +import net.strokkur.commands.internal.codegen.CodePackage; +import net.strokkur.commands.internal.codegen.CodeType; import net.strokkur.commands.internal.exceptions.ProviderAlreadyRegisteredException; import net.strokkur.commands.internal.intermediate.CommonTreePostProcessor; import net.strokkur.commands.internal.intermediate.registrable.ExecutorWrapperRegistry; @@ -36,7 +38,10 @@ import net.strokkur.commands.internal.parsing.CommandParserImpl; import net.strokkur.commands.internal.parsing.DefaultExecutesTransform; import net.strokkur.commands.internal.parsing.ExecutesTransform; -import net.strokkur.commands.internal.printer.CommonCommandTreePrinter; +import net.strokkur.commands.internal.printer.CommonClassBuilder; +import net.strokkur.commands.internal.printer.javadoc.AbstractJavadocPrintingVisitor; +import net.strokkur.commands.internal.printer.javadoc.JavaMarkdownJavadocVisitor; +import net.strokkur.commands.internal.printer.javadoc.JavaStarJavadocVisitor; import net.strokkur.commands.internal.util.CommandInformation; import net.strokkur.commands.internal.util.MessagerWrapper; import net.strokkur.commands.meta.StrokkCommandsDebug; @@ -50,7 +55,7 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.tools.JavaFileObject; -import java.io.PrintWriter; +import java.io.Writer; import java.lang.annotation.Annotation; import java.util.Optional; import java.util.Set; @@ -80,12 +85,29 @@ protected void init() { protected abstract CommonTreePostProcessor createPostProcessor(MessagerWrapper messager); - protected abstract CommonCommandTreePrinter createPrinter(CommandNode node, C commandInformation); + protected abstract CommonClassBuilder createBuilder(CommandNode node, C commandInformation); protected abstract BrigadierArgumentConverter getConverter(MessagerWrapper messager); protected abstract C getCommandInformation(SourceClass sourceClass); + protected AbstractJavadocPrintingVisitor createJavadocVisitor(CodePackage pkg, Set imports) { + if (isJavaVersion(25)) { + return new JavaMarkdownJavadocVisitor(pkg, imports); + } + // We are on Java 24 or below, so use the star Javadoc visitor. + return new JavaStarJavadocVisitor(pkg, imports); + } + + private boolean isJavaVersion(int version) { + try { + SourceVersion.valueOf("RELEASE_" + version); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + @Override @NullUnmarked public boolean process(Set annotations, RoundEnvironment roundEnv) { @@ -192,12 +214,11 @@ private void processElement( } try { - final CommonCommandTreePrinter printer = createPrinter(commandTree, commandInformation); - final JavaFileObject obj = processingEnv.getFiler().createSourceFile(printer.getPackageName() + "." + printer.getBrigadierClassName()); + final CommonClassBuilder printer = createBuilder(commandTree, commandInformation); + final JavaFileObject obj = processingEnv.getFiler().createSourceFile(commandInformation.sourceClass().getFullyQualifiedName() + "Brigadier"); - try (PrintWriter out = new PrintWriter(obj.openWriter())) { - printer.setWriter(out); - printer.print(); + try (Writer writer = obj.openWriter()) { + writer.write(printer.getAsString()); } } catch (Exception ex) { messagerWrapper.errorSource("A fatal exception occurred whilst printing source file: {}", sourceClass, ex.getMessage()); diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/arguments/BrigadierArgumentConverter.java b/processor/common/src/main/java/net/strokkur/commands/internal/arguments/BrigadierArgumentConverter.java index 6fe5bdda..2d319505 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/arguments/BrigadierArgumentConverter.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/arguments/BrigadierArgumentConverter.java @@ -24,7 +24,11 @@ import net.strokkur.commands.arguments.StringArg; import net.strokkur.commands.arguments.StringArgType; import net.strokkur.commands.internal.abstraction.SourceVariable; +import net.strokkur.commands.internal.codegen.CodeExpression; +import net.strokkur.commands.internal.codegen.as.AsExpression; +import net.strokkur.commands.internal.codegen.builder.Builders; import net.strokkur.commands.internal.exceptions.ConversionException; +import net.strokkur.commands.internal.util.Classes; import net.strokkur.commands.internal.util.ForwardingMessagerWrapper; import net.strokkur.commands.internal.util.MessagerWrapper; import org.jspecify.annotations.Nullable; @@ -32,7 +36,6 @@ import java.lang.annotation.Annotation; import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.TreeMap; import java.util.function.BiFunction; import java.util.function.Function; @@ -57,41 +60,58 @@ public BrigadierArgumentConverter(MessagerWrapper messagerWrapper) { protected void initializeArguments() { putFor((unused, name) -> BrigadierArgumentType.of( - "BoolArgumentType.bool()", - "BoolArgumentType.getBool(ctx, \"%s\")".formatted(name), - "com.mojang.brigadier.arguments.BoolArgumentType" + Builders.methodInvocation("bool").setStatic(Classes.BOOL_ARGUMENT_TYPE), + Builders.methodInvocation("getBool").setStatic(Classes.BOOL_ARGUMENT_TYPE) + .addParameter(CodeExpression.variable("ctx")) + .addParameter(CodeExpression.string(name)) ), "boolean", "java.lang.Boolean"); putFor((p, name) -> annotatedOr(p, IntArg.class, - a -> "IntegerArgumentType.integer(%s, %s)".formatted(a.min(), a.max()), - "IntegerArgumentType.integer()", "IntegerArgumentType.getInteger(ctx, \"%s\")".formatted(name), - "com.mojang.brigadier.arguments.IntegerArgumentType" + a -> Builders.methodInvocation("integer").setStatic(Classes.INTEGER_ARGUMENT_TYPE) + .addParameter(CodeExpression.number(a.min())) + .addParameter(CodeExpression.number(a.max())), + Builders.methodInvocation("integer").setStatic(Classes.INTEGER_ARGUMENT_TYPE), + Builders.methodInvocation("getInteger").setStatic(Classes.INTEGER_ARGUMENT_TYPE) + .addParameter(CodeExpression.variable("ctx")) + .addParameter(CodeExpression.string(name)) ), "int", "java.lang.Integer"); putFor((p, name) -> annotatedOr(p, LongArg.class, - a -> "LongArgumentType.longArg(%s, %s)".formatted(a.min(), a.max()), - "LongArgumentType.longArg()", "LongArgumentType.getLong(ctx, \"%s\")".formatted(name), - "com.mojang.brigadier.arguments.LongArgumentType" + a -> Builders.methodInvocation("longArg").setStatic(Classes.LONG_ARGUMENT_TYPE) + .addParameter(CodeExpression.number(a.min())) + .addParameter(CodeExpression.number(a.max())), + Builders.methodInvocation("longArg").setStatic(Classes.LONG_ARGUMENT_TYPE), + Builders.methodInvocation("getLong").setStatic(Classes.LONG_ARGUMENT_TYPE) + .addParameter(CodeExpression.variable("ctx")) + .addParameter(CodeExpression.string(name)) ), "long", "java.lang.Long"); putFor((p, name) -> annotatedOr(p, FloatArg.class, - a -> "FloatArgumentType.floatArg(%s, %s)".formatted(a.min(), a.max()), - "FloatArgumentType.floatArg()", - "FloatArgumentType.getFloat(ctx, \"%s\")".formatted(name), - "com.mojang.brigadier.arguments.FloatArgumentType" + a -> Builders.methodInvocation("floatArg").setStatic(Classes.FLOAT_ARGUMENT_TYPE) + .addParameter(CodeExpression.number(a.min())) + .addParameter(CodeExpression.number(a.max())), + Builders.methodInvocation("floatArg").setStatic(Classes.FLOAT_ARGUMENT_TYPE), + Builders.methodInvocation("getFloat").setStatic(Classes.FLOAT_ARGUMENT_TYPE) + .addParameter(CodeExpression.variable("ctx")) + .addParameter(CodeExpression.string(name)) ), "float", "java.lang.Float"); putFor((p, name) -> annotatedOr(p, DoubleArg.class, - a -> "DoubleArgumentType.doubleArg(%s, %s)".formatted(a.min(), a.max()), - "DoubleArgumentType.doubleArg()", "DoubleArgumentType.getDouble(ctx, \"%s\")".formatted(name), - "com.mojang.brigadier.arguments.DoubleArgumentType" + a -> Builders.methodInvocation("doubleArg").setStatic(Classes.DOUBLE_ARGUMENT_TYPE) + .addParameter(CodeExpression.number(a.min())) + .addParameter(CodeExpression.number(a.max())), + Builders.methodInvocation("doubleArg").setStatic(Classes.DOUBLE_ARGUMENT_TYPE), + Builders.methodInvocation("getDouble").setStatic(Classes.DOUBLE_ARGUMENT_TYPE) + .addParameter(CodeExpression.variable("ctx")) + .addParameter(CodeExpression.string(name)) ), "double", "java.lang.Double"); putFor((p, name) -> annotatedOr(p, StringArg.class, - a -> "StringArgumentType.%s()".formatted(a.value().getBrigadierType()), - "StringArgumentType.%s()".formatted(StringArgType.WORD.getBrigadierType()), - "StringArgumentType.getString(ctx, \"%s\")".formatted(name), - "com.mojang.brigadier.arguments.StringArgumentType" + a -> Builders.methodInvocation(a.value().getBrigadierType()).setStatic(Classes.STRING_ARGUMENT_TYPE), + Builders.methodInvocation(StringArgType.WORD.getBrigadierType()).setStatic(Classes.STRING_ARGUMENT_TYPE), + Builders.methodInvocation("getString").setStatic(Classes.STRING_ARGUMENT_TYPE) + .addParameter(CodeExpression.variable("ctx")) + .addParameter(CodeExpression.string(name)) ), "java.lang.String"); } @@ -104,25 +124,13 @@ protected final void putFor(BiFunction BrigadierArgumentType annotatedOr( SourceVariable variable, Class annotation, - Function withAnnotation, - String withoutAnnotation, - String retrieval, - String singleImport - ) { - return annotatedOr(variable, annotation, withAnnotation, withoutAnnotation, retrieval, Set.of(singleImport)); - } - - protected final BrigadierArgumentType annotatedOr( - SourceVariable variable, - Class annotation, - Function withAnnotation, - String withoutAnnotation, - String retrieval, - Set imports + Function withAnnotation, + AsExpression withoutAnnotation, + AsExpression retrieval ) { return variable.getAnnotationOptional(annotation) - .map(annotated -> BrigadierArgumentType.of(withAnnotation.apply(annotated), retrieval, imports)) - .orElseGet(() -> BrigadierArgumentType.of(withoutAnnotation, retrieval, imports)); + .map(annotated -> BrigadierArgumentType.of(withAnnotation.apply(annotated), retrieval)) + .orElseGet(() -> BrigadierArgumentType.of(withoutAnnotation, retrieval)); } public final BrigadierArgumentType getAsArgumentType(SourceVariable parameter) throws ConversionException { diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/arguments/BrigadierArgumentType.java b/processor/common/src/main/java/net/strokkur/commands/internal/arguments/BrigadierArgumentType.java index e3ec0a71..30c3c1de 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/arguments/BrigadierArgumentType.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/arguments/BrigadierArgumentType.java @@ -17,38 +17,11 @@ */ package net.strokkur.commands.internal.arguments; -import org.jspecify.annotations.Nullable; +import net.strokkur.commands.internal.codegen.as.AsExpression; -import java.util.Objects; -import java.util.Set; +public record BrigadierArgumentType(AsExpression initializer, AsExpression retriever) { -public record BrigadierArgumentType(String initializer, String retriever, Set imports) { - - public static BrigadierArgumentType of(String initializer, String retriever) { - return new BrigadierArgumentType(initializer, retriever, Set.of()); - } - - public static BrigadierArgumentType of(String initializer, String retriever, String singleImport) { - return new BrigadierArgumentType(initializer, retriever, Set.of(singleImport)); - } - - public static BrigadierArgumentType of(String initializer, String retriever, Set imports) { - return new BrigadierArgumentType(initializer, retriever, imports); - } - - @Override - public boolean equals(@Nullable Object o) { - if (!(o instanceof BrigadierArgumentType(String initializer1, String retriever1, Set imports1))) { - return false; - } - - return Objects.equals(retriever(), retriever1) - && Objects.equals(initializer(), initializer1) - && Objects.equals(imports(), imports1); - } - - @Override - public int hashCode() { - return Objects.hash(initializer(), retriever(), imports()); + public static BrigadierArgumentType of(AsExpression initializer, AsExpression retriever) { + return new BrigadierArgumentType(initializer, retriever); } } diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeAnnotation.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeAnnotation.java new file mode 100644 index 00000000..c9bbf0ab --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeAnnotation.java @@ -0,0 +1,37 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen; + +import net.strokkur.commands.internal.codegen.visitor.CodeVisitable; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitor; +import net.strokkur.commands.internal.util.Classes; + +public record CodeAnnotation(CodeType.ClassType type) implements CodeVisitable { + public static CodeAnnotation NULL_MARKED = CodeAnnotation.of(Classes.NULL_MARKED.getAsCodeType()); + public static CodeAnnotation NULLABLE = CodeAnnotation.of(Classes.NULLABLE.getAsCodeType()); + public static CodeAnnotation INJECT = CodeAnnotation.of(Classes.INJECT.getAsCodeType()); + + public static CodeAnnotation of(CodeType.ClassType type) { + return new CodeAnnotation(type); + } + + @Override + public R accept(CodeVisitor visitor) { + return visitor.visitAnnotation(this); + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeBlock.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeBlock.java new file mode 100644 index 00000000..3e3128ce --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeBlock.java @@ -0,0 +1,26 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen; + +import java.util.List; + +public record CodeBlock(List statements) { + public CodeBlock(List statements) { + this.statements = List.copyOf(statements); + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeClass.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeClass.java new file mode 100644 index 00000000..ac932828 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeClass.java @@ -0,0 +1,114 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen; + +import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitable; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitor; +import net.strokkur.commands.internal.util.ConvertableTo; +import org.jetbrains.annotations.UnmodifiableView; +import org.jspecify.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public record CodeClass( + CodePackage codePackage, @Nullable CodeClass parentClass, String name, Set modifiers, + List annotations, List methods, List fields, + @Nullable CodeJavadoc javadoc, + List typeParameters +) implements CodeVisitable, ConvertableTo.Self { + public static CodeClass OBJECT = CodeClass.simple("java.lang.Object"); + public static CodeClass LIST = CodeClass.simple("java.util.List"); + public static CodeClass STRING = CodeClass.simple("java.lang.String"); + + private CodeClass( + CodePackage codePackage, + @Nullable CodeClass parentClass, + String name + ) { + this(codePackage, parentClass, name, Set.of(), List.of(), new ArrayList<>(), new ArrayList<>(), null, List.of()); + } + + public static CodeClass simple(String string) { + final String[] split = string.split("\\."); + final String[] splitName = split[split.length - 1].split("\\$"); + + final CodePackage codePackage = new CodePackage(Arrays.copyOf(split, split.length - 1)); + if (splitName.length > 1) { + // This is an inner class + return nested(codePackage, List.of(splitName)); + } + + return new CodeClass(codePackage, null, splitName[0]); + } + + public static CodeClass nested(CodePackage codePackage, List innerClassNames) { + if (innerClassNames.size() > 1) { + return new CodeClass( + codePackage, + nested(codePackage, innerClassNames.subList(0, innerClassNames.size() - 1)), + innerClassNames.getLast() + ); + } + // Finally, top level! + return new CodeClass(codePackage, null, innerClassNames.getLast()); + } + + @Override + public R accept(CodeVisitor visitor) { + return visitor.visitClass(this); + } + + public String fullyQualifiedName() { + return codePackage().path() + "." + name(); + } + + @Override + @UnmodifiableView + public Set modifiers() { + return Collections.unmodifiableSet(modifiers); + } + + @Override + @UnmodifiableView + public List annotations() { + return Collections.unmodifiableList(annotations); + } + + @Override + @UnmodifiableView + public List methods() { + return Collections.unmodifiableList(methods); + } + + @Override + @UnmodifiableView + public List fields() { + return Collections.unmodifiableList(fields); + } + + @Override + @UnmodifiableView + public List typeParameters() { + return Collections.unmodifiableList(typeParameters); + } +} diff --git a/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityInstanceFieldPrinter.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeConstructor.java similarity index 56% rename from processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityInstanceFieldPrinter.java rename to processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeConstructor.java index 106cbd61..9c7c110b 100644 --- a/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityInstanceFieldPrinter.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeConstructor.java @@ -15,23 +15,24 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, see . */ -package net.strokkur.commands.internal.velocity; +package net.strokkur.commands.internal.codegen; -import net.strokkur.commands.internal.abstraction.SourceParameter; -import net.strokkur.commands.internal.printer.CommonCommandTreePrinter; -import net.strokkur.commands.internal.printer.CommonInstanceFieldPrinter; -import net.strokkur.commands.internal.velocity.util.VelocityClasses; +import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc; +import org.jspecify.annotations.Nullable; -final class VelocityInstanceFieldPrinter extends CommonInstanceFieldPrinter { - VelocityInstanceFieldPrinter(CommonCommandTreePrinter printer) { - super(printer); - } +import java.util.List; +import java.util.Set; - @Override - public String getParameterName(SourceParameter parameter) { - if (parameter.getType().getFullyQualifiedName().equals(VelocityClasses.PROXY_SERVER)) { - return "server"; - } - return super.getParameterName(parameter); +public class CodeConstructor extends CodeMethod { + public CodeConstructor( + CodeClass declaredClass, + List parameters, + Set modifiers, + @Nullable CodeJavadoc javadoc, + CodeBlock codeBlock, + List generics, + List throwsExceptions + ) { + super(declaredClass, CodeType.ofClass(declaredClass), declaredClass.name(), parameters, modifiers, javadoc, codeBlock, generics, throwsExceptions); } } diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeExpression.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeExpression.java new file mode 100644 index 00000000..4e16a86b --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeExpression.java @@ -0,0 +1,265 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen; + +import net.strokkur.commands.internal.codegen.as.AsBooleanExpression; +import net.strokkur.commands.internal.codegen.as.AsExpression; +import net.strokkur.commands.internal.codegen.as.AsStatement; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitable; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitor; +import net.strokkur.commands.internal.util.ConvertableTo; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Unmodifiable; +import org.jspecify.annotations.Nullable; + +import java.util.Arrays; +import java.util.List; + +public sealed interface CodeExpression extends CodeVisitable, AsExpression { + static Null nullExpr() { + return Null.INSTANCE; + } + + static StringLiteral string(String value) { + return new StringLiteral(value); + } + + static Variable variable(String name) { + return new Variable(name); + } + + static NumberConstant number(Number number) { + return new NumberConstant(number); + } + + static BooleanConstant bool(boolean value) { + return new BooleanConstant(value); + } + + static MethodReference methodReference(CodeType type, String methodName) { + return new MethodReference(type, methodName); + } + + static SingleLineLambda lambda(List parameters, AsExpression expression) { + return new SingleLineLambda(parameters, expression.getAsExpression()); + } + + static MultiLineLambda lambda(List parameters, AsStatement... statements) { + return new MultiLineLambda( + parameters, + Arrays.stream(statements) + .map(AsStatement::getAsStatement) + .toList() + ); + } + + static Instanceof instanceofExpr(AsExpression left, CodeType.ClassType type, @Nullable String name) { + return new Instanceof(left.getAsExpression(), type, name, false); + } + + @Override + default R accept(CodeVisitor visitor) { + return visitor.visitExpression(this); + } + + @Override + default CodeExpression getAsExpression() { + return this; + } + + sealed abstract class BooleanExpression> + implements CodeExpression, AsBooleanExpression + permits Instanceof, BooleanConstant { + private final boolean inverted; + + public BooleanExpression(boolean inverted) { + this.inverted = inverted; + } + + public boolean isInverted() { + return inverted; + } + + @Override + public BooleanExpression getAsBooleanExpression() { + return this; + } + + public abstract S invert(); + } + + final class StringLiteral implements CodeExpression { + private final String value; + + private StringLiteral(String value) { + this.value = value; + } + + public String value() { + return value; + } + } + + final class NumberConstant implements CodeExpression { + private final Number value; + + private NumberConstant(Number value) { + this.value = value; + } + + public Number value() { + return value; + } + } + + final class BooleanConstant extends BooleanExpression { + private final boolean value; + + private BooleanConstant(boolean value) { + super(false); + this.value = value; + } + + @Override + public BooleanConstant invert() { + return new BooleanConstant(!value); + } + } + + final class Null implements CodeExpression { + private static final Null INSTANCE = new Null(); + + private Null() { + } + } + + final class Variable implements CodeExpression { + private final String name; + + private Variable(String name) { + this.name = name; + } + + public String name() { + return name; + } + } + + record MethodInvocation(InvokesMethod invokes) implements CodeExpression { + } + + final class MethodReference implements CodeExpression { + private final CodeType type; + private final String methodName; + + private MethodReference(CodeType type, String methodName) { + this.type = type; + this.methodName = methodName; + } + + public CodeType type() { + return type; + } + + public String methodName() { + return methodName; + } + } + + final class SingleLineLambda implements CodeExpression { + private final List lambdaParams; + private final CodeExpression lambdaExpression; + + private SingleLineLambda(List lambdaParams, CodeExpression lambdaExpression) { + this.lambdaParams = lambdaParams; + this.lambdaExpression = lambdaExpression; + } + + @Contract(pure = true) + public @Unmodifiable List lambdaParams() { + return List.copyOf(lambdaParams); + } + + public CodeExpression lambdaExpression() { + return lambdaExpression; + } + } + + final class MultiLineLambda implements CodeExpression { + private final List lambdaParams; + private final List statements; + + private MultiLineLambda(List lambdaParams, List statements) { + this.lambdaParams = lambdaParams; + this.statements = statements; + } + + @Contract(pure = true) + public @Unmodifiable List lambdaParams() { + return List.copyOf(lambdaParams); + } + + @Contract(pure = true) + public @Unmodifiable List statements() { + return List.copyOf(statements); + } + } + + final class Instanceof extends BooleanExpression { + private final CodeExpression left; + private final CodeType.ClassType type; + private final @Nullable String name; + + private Instanceof(CodeExpression left, CodeType.ClassType type, @Nullable String name, boolean inverted) { + super(inverted); + this.left = left; + this.type = type; + this.name = name; + } + + public CodeExpression left() { + return left; + } + + public CodeType.ClassType type() { + return type; + } + + public @Nullable String name() { + return name; + } + + @Override + public Instanceof invert() { + return new Instanceof( + left, + type, + name, + !isInverted() + ); + } + } + + record FieldAccess( + CodeType.@Nullable ClassType type, + @Nullable CodeExpression source, + String fieldName, + boolean isStatic + ) implements CodeExpression, ConvertableTo.Self { + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeField.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeField.java new file mode 100644 index 00000000..a05a2ba7 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeField.java @@ -0,0 +1,40 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen; + +import net.strokkur.commands.internal.codegen.visitor.CodeVisitable; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitor; +import net.strokkur.commands.internal.util.ConvertableTo; +import org.jspecify.annotations.Nullable; + +import java.util.List; +import java.util.Set; + +public record CodeField( + String name, + CodeType type, + @Nullable CodeExpression initialiser, + Set modifiers, + List annotations +) implements CodeVisitable, ConvertableTo.Self { + + @Override + public R accept(CodeVisitor visitor) { + return visitor.visitField(this); + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeMethod.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeMethod.java new file mode 100644 index 00000000..4a188f10 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeMethod.java @@ -0,0 +1,103 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen; + +import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitable; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitor; +import net.strokkur.commands.internal.util.ConvertableTo; +import org.jspecify.annotations.Nullable; + +import java.util.List; +import java.util.Set; + +public class CodeMethod implements CodeVisitable, ConvertableTo.Self { + private final CodeClass declaredClass; + private final CodeType returnType; + private final String name; + private final List parameters; + private final Set modifiers; + private final @Nullable CodeJavadoc javadoc; + private final CodeBlock codeBlock; + private final List generics; + private final List throwsExceptions; + + public CodeMethod( + CodeClass declaredClass, + CodeType returnType, + String name, + List parameters, + Set modifiers, + @Nullable CodeJavadoc javadoc, + CodeBlock codeBlock, + List generics, + List throwsExceptions + ) { + this.declaredClass = declaredClass; + this.returnType = returnType; + this.name = name; + this.parameters = parameters; + this.modifiers = modifiers; + this.javadoc = javadoc; + this.codeBlock = codeBlock; + this.generics = generics; + this.throwsExceptions = throwsExceptions; + } + + @Override + public R accept(CodeVisitor visitor) { + return visitor.visitMethod(this); + } + + /// Example: `methodName` + public String name() { + return name; + } + + public CodeClass declaredClass() { + return declaredClass; + } + + public CodeType returnType() { + return returnType; + } + + public List parameters() { + return parameters; + } + + public Set modifiers() { + return modifiers; + } + + public @Nullable CodeJavadoc javadoc() { + return javadoc; + } + + public CodeBlock codeBlock() { + return codeBlock; + } + + public List throwsExceptions() { + return throwsExceptions; + } + + public List generics() { + return generics; + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodePackage.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodePackage.java new file mode 100644 index 00000000..ec9370f6 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodePackage.java @@ -0,0 +1,70 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen; + +import net.strokkur.commands.internal.codegen.visitor.CodeVisitable; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitor; +import org.jspecify.annotations.Nullable; + +import java.util.Arrays; +import java.util.Objects; + +public class CodePackage implements CodeVisitable, Comparable { + private static final CodePackage JAVA_LANG = new CodePackage(new String[]{"java", "lang"}); + + private final String[] paths; + + public static CodePackage of(String packageString) { + return new CodePackage(packageString.split("\\.")); + } + + public CodePackage(String[] paths) { + this.paths = paths; + } + + public String path() { + return String.join(".", paths); + } + + @Override + public R accept(CodeVisitor visitor) { + return visitor.visitPackage(this); + } + + public static boolean isRedundantImport(@Nullable CodePackage maybeRoot, CodePackage other) { + return other.equals(JAVA_LANG) || Objects.equals(maybeRoot, other); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof final CodePackage that)) { + return false; + } + return Arrays.deepEquals(paths, that.paths); + } + + @Override + public int hashCode() { + return Arrays.hashCode(paths); + } + + @Override + public int compareTo(CodePackage o) { + return this.path().compareTo(o.path()); + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeParameter.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeParameter.java new file mode 100644 index 00000000..71fd3f56 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeParameter.java @@ -0,0 +1,32 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen; + +import net.strokkur.commands.internal.codegen.visitor.CodeVisitable; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitor; + +public record CodeParameter(CodeType type, String name) implements CodeVisitable { + public static CodeParameter of(CodeType type, String name) { + return new CodeParameter(type, name); + } + + @Override + public R accept(CodeVisitor visitor) { + return visitor.visitParameter(this); + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeStatement.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeStatement.java new file mode 100644 index 00000000..733a1dcd --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeStatement.java @@ -0,0 +1,155 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen; + +import net.strokkur.commands.internal.codegen.as.AsBooleanExpression; +import net.strokkur.commands.internal.codegen.as.AsCodeType; +import net.strokkur.commands.internal.codegen.as.AsExpression; +import net.strokkur.commands.internal.codegen.as.AsStatement; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitable; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitor; +import org.jetbrains.annotations.Contract; +import org.jspecify.annotations.Nullable; + +import java.util.Arrays; +import java.util.List; + +public sealed interface CodeStatement extends CodeVisitable, AsStatement { + static VariableDeclaration variableDeclaration(AsCodeType type, String name, @Nullable AsExpression assignment) { + return new VariableDeclaration(type.getAsCodeType(), name, assignment == null ? null : assignment.getAsExpression(), false); + } + + static VariableDeclaration variableDeclarationFinal(AsCodeType type, String name, @Nullable AsExpression assignment) { + return new VariableDeclaration(type.getAsCodeType(), name, assignment == null ? null : assignment.getAsExpression(), true); + } + + static ReturnStatement returnStatement(@Nullable AsExpression returnExpression) { + return new ReturnStatement(returnExpression == null ? null : returnExpression.getAsExpression()); + } + + static ThrowStatement throwStatement(AsExpression throwExpression) { + return new ThrowStatement(throwExpression.getAsExpression()); + } + + static Blank blank() { + return Blank.INSTANCE; + } + + static If ifStmt(AsBooleanExpression booleanExpr, AsStatement... ifTrue) { + return new If( + booleanExpr.getAsBooleanExpression(), + Arrays.stream(ifTrue) + .map(AsStatement::getAsStatement) + .toList() + ); + } + + @Override + default CodeStatement getAsStatement() { + return this; + } + + @Override + default R accept(CodeVisitor visitor) { + return visitor.visitStatement(this); + } + + final class VariableDeclaration implements CodeStatement { + private final CodeType type; + private final String name; + private final @Nullable CodeExpression assignment; + private final boolean isFinal; + + private VariableDeclaration(CodeType type, String name, @Nullable CodeExpression assignment, boolean isFinal) { + this.type = type; + this.name = name; + this.assignment = assignment; + this.isFinal = isFinal; + } + + public CodeType type() { + return type; + } + + public String name() { + return name; + } + + @Contract(pure = true) + public @Nullable CodeExpression assignment() { + return assignment; + } + + public boolean isFinal() { + return isFinal; + } + } + + final class ReturnStatement implements CodeStatement { + private final @Nullable CodeExpression returnExpression; + + private ReturnStatement(@Nullable CodeExpression returnExpression) { + this.returnExpression = returnExpression; + } + + @Contract(pure = true) + public @Nullable CodeExpression returnValue() { + return returnExpression; + } + } + + final class ThrowStatement implements CodeStatement { + private final CodeExpression throwExpression; + + private ThrowStatement(CodeExpression throwExpression) { + this.throwExpression = throwExpression; + } + + public CodeExpression throwExpression() { + return throwExpression; + } + } + + record MethodInvocation(InvokesMethod invokes) implements CodeStatement { + } + + final class If implements CodeStatement { + private final CodeExpression.BooleanExpression booleanExpr; + private final List ifTrue; + + private If(CodeExpression.BooleanExpression booleanExpr, List ifTrue) { + this.booleanExpr = booleanExpr; + this.ifTrue = ifTrue; + } + + public CodeExpression.BooleanExpression booleanExpr() { + return booleanExpr; + } + + public List ifTrue() { + return ifTrue; + } + } + + final class Blank implements CodeStatement { + private static final Blank INSTANCE = new Blank(); + + private Blank() { + } + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeType.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeType.java new file mode 100644 index 00000000..48e97f21 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeType.java @@ -0,0 +1,199 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen; + +import net.strokkur.commands.internal.codegen.as.AsCodeType; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitable; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitor; +import org.jetbrains.annotations.Contract; +import org.jspecify.annotations.Nullable; + +import java.util.List; +import java.util.Objects; + +public interface CodeType extends CodeVisitable, Comparable { + VoidType VOID = new VoidType(); + PrimitiveType BYTE = new PrimitiveType("byte"); + PrimitiveType CHAR = new PrimitiveType("char"); + PrimitiveType SHORT = new PrimitiveType("short"); + PrimitiveType INT = new PrimitiveType("int"); + PrimitiveType LONG = new PrimitiveType("long"); + PrimitiveType FLOAT = new PrimitiveType("float"); + PrimitiveType DOUBLE = new PrimitiveType("double"); + + ClassType OBJECT = CodeType.ofClass(CodeClass.OBJECT); + ClassType STRING = CodeType.ofClass(CodeClass.STRING); + ArrayType STRING_ARRAY = CodeType.ofArray(CodeType.STRING); + + ClassType LIST = CodeType.ofClass(CodeClass.LIST); + ClassType LIST_STRING = CodeType.ofClassTyped(CodeClass.LIST, STRING); + + static GenericType generic(String name, @Nullable String definition) { + return new GenericType(name, definition); + } + + static ClassType ofClass(CodeClass codeClass) { + return new ClassType(codeClass, null); + } + + static ClassType ofClass(String fqn) { + return new ClassType(CodeClass.simple(fqn), null); + } + + static ClassType ofClassTyped(CodeClass codeClass, CodeType... typed) { + return new ClassType(codeClass, List.of(typed)); + } + + static ClassType ofClassTyped(String fqn, CodeType... typed) { + return new ClassType(CodeClass.simple(fqn), List.of(typed)); + } + + static ArrayType ofArray(CodeType inner) { + return new ArrayType(inner); + } + + String name(); + + String fullyQualifiedName(); + + @Override + default R accept(CodeVisitor visitor) { + return visitor.visitType(this); + } + + @Override + default int compareTo(CodeType o) { + return this.fullyQualifiedName().compareTo(o.fullyQualifiedName()); + } + + abstract class SimpleType implements CodeType { + private final String name; + + private SimpleType(String name) { + this.name = name; + } + + @Override + public String name() { + return name; + } + + @Override + public String fullyQualifiedName() { + return name; + } + } + + class VoidType extends PrimitiveType { + private VoidType() { + super("void"); + } + } + + class PrimitiveType extends SimpleType { + private PrimitiveType(String name) { + super(name); + } + } + + class GenericType extends SimpleType { + private final @Nullable String definition; + + private GenericType(String name, @Nullable String definition) { + super(name); + this.definition = definition; + } + + public @Nullable String definition() { + return definition; + } + } + + class ClassType implements CodeType, AsCodeType { + private final CodeClass codeClass; + private final @Nullable List types; + + private ClassType(CodeClass codeClass, @Nullable List types) { + this.codeClass = codeClass; + this.types = types; + } + + @Override + public CodeType.ClassType getAsCodeType() { + return this; + } + + @Contract(pure = true) + public @Nullable List types() { + return types == null ? null : List.copyOf(types); + } + + @Override + public String name() { + return codeClass.name(); + } + + @Override + public String fullyQualifiedName() { + return codeClass.fullyQualifiedName(); + } + + public CodePackage codePackage() { + return codeClass.codePackage(); + } + + public CodeClass codeClass() { + return codeClass; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof final ClassType type)) { + return false; + } + return Objects.equals(codeClass.fullyQualifiedName(), type.codeClass.fullyQualifiedName()); + } + + @Override + public int hashCode() { + return Objects.hash(codeClass.fullyQualifiedName()); + } + } + + class ArrayType implements CodeType { + private final CodeType inner; + + private ArrayType(CodeType inner) { + this.inner = inner; + } + + @Override + public String name() { + return inner.name() + "[]"; + } + + @Override + public String fullyQualifiedName() { + return inner.name() + "[]"; + } + + public CodeType inner() { + return inner; + } + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/InvokesMethod.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/InvokesMethod.java new file mode 100644 index 00000000..fe6d5a61 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/InvokesMethod.java @@ -0,0 +1,64 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen; + +import net.strokkur.commands.internal.util.ConvertableTo; +import org.jspecify.annotations.Nullable; + +import java.util.List; + +public record InvokesMethod( + String methodName, + CodeType.@Nullable ClassType type, + List parameters, + @Nullable CodeExpression instanceSource, + boolean isStatic, + boolean isCtor, + StyleConfig style, + List chained +) implements ConvertableTo.Self { + + public record Chained( + String methodName, + List parameters, + StyleConfig style + ) { + } + + public record StyleConfig( + boolean newline, + boolean multilineParameters, + boolean newlineClosingBrace + ) { + public static final StyleConfig DEFAULT = new StyleConfig( + false, false, false + ); + public static final StyleConfig NEWLINE = new StyleConfig( + true, false, false + ); + public static final StyleConfig MULTILINE = new StyleConfig( + false, true, false + ); + public static final StyleConfig NEWLINE_MULTILINE = new StyleConfig( + true, true, false + ); + public static final StyleConfig NEWLINE_BOTH = new StyleConfig( + true, false, true + ); + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/Printable.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/Modifiers.java similarity index 58% rename from processor/common/src/main/java/net/strokkur/commands/internal/printer/Printable.java rename to processor/common/src/main/java/net/strokkur/commands/internal/codegen/Modifiers.java index c6bebd89..6a45762f 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/Printable.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/Modifiers.java @@ -15,28 +15,34 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, see . */ -package net.strokkur.commands.internal.printer; +package net.strokkur.commands.internal.codegen; -import java.io.IOException; +import java.util.Locale; -interface Printable { +public enum Modifiers { + // Visibility + PUBLIC(0), + PRIVATE(1), - void incrementIndent(); + // OOP + ABSTRACT(10), + DEFAULT(10), - void decrementIndent(); + // Misc. + FINAL(5), + STATIC(4); + private final int priority; - void print(String message, Object... format) throws IOException; - - void printIndent() throws IOException; - - void println(String message, Object... format) throws IOException; - - default void printIndented(String message, Object... format) throws IOException { - printIndent(); - print(message, format); + Modifiers(int priority) { + this.priority = priority; } - void println() throws IOException; + public int priority() { + return priority; + } - void printBlock(String block, Object... format) throws IOException; + @Override + public String toString() { + return this.name().toLowerCase(Locale.ROOT); + } } diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/adapter/CodeTypeAdapter.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/adapter/CodeTypeAdapter.java new file mode 100644 index 00000000..c53e713c --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/adapter/CodeTypeAdapter.java @@ -0,0 +1,51 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen.adapter; + +import net.strokkur.commands.internal.abstraction.SourceClass; +import net.strokkur.commands.internal.abstraction.SourceType; +import net.strokkur.commands.internal.abstraction.SourceTypeVariable; +import net.strokkur.commands.internal.codegen.CodeClass; +import net.strokkur.commands.internal.codegen.CodePackage; +import net.strokkur.commands.internal.codegen.CodeType; + +import java.util.List; + +public final class CodeTypeAdapter { + + public static CodeType from(SourceType sourceType) { + if (sourceType instanceof SourceTypeVariable typeVariable) { + return CodeType.generic(typeVariable.getName(), null); + } + if (sourceType instanceof SourceClass sourceClass) { + return from(sourceClass); + } + + throw new IllegalStateException("No adapter mapping for " + sourceType.getClass().getName()); + } + + public static CodeType.ClassType from(SourceClass sourceType) { + return CodeType.ofClass(CodeClass.nested( + CodePackage.of(sourceType.getPackageName()), + List.of(sourceType.getSourceName().split("\\.")) + )); + } + + private CodeTypeAdapter() { + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/as/AsBooleanExpression.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/as/AsBooleanExpression.java new file mode 100644 index 00000000..6e732e3d --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/as/AsBooleanExpression.java @@ -0,0 +1,24 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen.as; + +import net.strokkur.commands.internal.codegen.CodeExpression; + +public interface AsBooleanExpression { + CodeExpression.BooleanExpression getAsBooleanExpression(); +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/as/AsCodeType.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/as/AsCodeType.java new file mode 100644 index 00000000..fe03377c --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/as/AsCodeType.java @@ -0,0 +1,24 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen.as; + +import net.strokkur.commands.internal.codegen.CodeType; + +public interface AsCodeType { + S getAsCodeType(); +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/as/AsExpression.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/as/AsExpression.java new file mode 100644 index 00000000..02f107ea --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/as/AsExpression.java @@ -0,0 +1,24 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen.as; + +import net.strokkur.commands.internal.codegen.CodeExpression; + +public interface AsExpression { + CodeExpression getAsExpression(); +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/as/AsField.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/as/AsField.java new file mode 100644 index 00000000..31443e1a --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/as/AsField.java @@ -0,0 +1,24 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen.as; + +import net.strokkur.commands.internal.codegen.CodeField; + +public interface AsField { + CodeField getAsField(); +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/as/AsStatement.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/as/AsStatement.java new file mode 100644 index 00000000..328997fa --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/as/AsStatement.java @@ -0,0 +1,24 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen.as; + +import net.strokkur.commands.internal.codegen.CodeStatement; + +public interface AsStatement { + CodeStatement getAsStatement(); +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/Builders.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/Builders.java new file mode 100644 index 00000000..aa285d25 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/Builders.java @@ -0,0 +1,90 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen.builder; + +import net.strokkur.commands.internal.codegen.CodeClass; +import net.strokkur.commands.internal.codegen.CodeMethod; +import net.strokkur.commands.internal.codegen.CodePackage; +import net.strokkur.commands.internal.codegen.CodeType; +import net.strokkur.commands.internal.codegen.Modifiers; +import net.strokkur.commands.internal.codegen.as.AsCodeType; +import net.strokkur.commands.internal.util.ConvertableTo; + +import java.util.Arrays; + +public class Builders { + public static MethodBuilder method(CodeClass declaringClass) { + return new MethodBuilder(declaringClass.name()) + .setDeclaringClass(declaringClass); + } + + public static MethodBuilder method(String name) { + return new MethodBuilder(name); + } + + public static MethodBuilder method(CodeClass declaringClass, String name) { + return new MethodBuilder(name) + .setDeclaringClass(declaringClass); + } + + public static FieldBuilder field(String name, CodeType type) { + return new FieldBuilder() + .setName(name) + .setType(type); + } + + public static ClassBuilder classBuilder(String name, CodePackage codePackage) { + return new ClassBuilder(name, codePackage); + } + + public static ClassBuilder classBuilder(String fqn) { + final String[] split = fqn.split("\\."); + return new ClassBuilder(split[split.length - 1], new CodePackage(Arrays.copyOf(split, split.length - 1))); + } + + public static MethodInvocationBuilder methodInvocation(String methodName) { + return new MethodInvocationBuilder(methodName); + } + + public static MethodInvocationBuilder methodInvocation(ConvertableTo methodConvertable) { + final CodeMethod method = methodConvertable.convert(); + final MethodInvocationBuilder builder = new MethodInvocationBuilder(method.name()); + builder.setType(CodeType.ofClass(method.declaredClass())); + if (method.modifiers().contains(Modifiers.STATIC)) { + builder.setStatic(); + } + return builder; + } + + public static MethodInvocationBuilder ctorInvocation(AsCodeType type) { + return ctorInvocation(type.getAsCodeType()); + } + + public static MethodInvocationBuilder ctorInvocation(CodeType.ClassType type) { + return new MethodInvocationBuilder(type.name()) + .setCtor() + .setType(type); + } + + public static FieldAccessBuilder fieldAccess(String name) { + return new FieldAccessBuilder(name); + } + + private Builders() { + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/ClassBuilder.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/ClassBuilder.java new file mode 100644 index 00000000..b1ad541b --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/ClassBuilder.java @@ -0,0 +1,134 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen.builder; + +import net.strokkur.commands.internal.codegen.CodeAnnotation; +import net.strokkur.commands.internal.codegen.CodeClass; +import net.strokkur.commands.internal.codegen.CodeField; +import net.strokkur.commands.internal.codegen.CodeMethod; +import net.strokkur.commands.internal.codegen.CodePackage; +import net.strokkur.commands.internal.codegen.CodeType; +import net.strokkur.commands.internal.codegen.Modifiers; +import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc; +import net.strokkur.commands.internal.util.ConvertableTo; +import org.jspecify.annotations.Nullable; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +public class ClassBuilder implements ConvertableTo { + private final CodePackage codePackage; + private @Nullable CodeClass parentClass = null; + private final String name; + private Set modifiers = new HashSet<>(); + private List annotations = new ArrayList<>(); + private final List methods = new ArrayList<>(); + private final List fields = new ArrayList<>(); + private @Nullable CodeJavadoc javadoc = null; + private List typeParameters = new ArrayList<>(); + + ClassBuilder(String name, CodePackage codePackage) { + this.codePackage = codePackage; + this.name = name; + } + + public ClassBuilder setParentClass(@Nullable ConvertableTo parentClass) { + this.parentClass = Optional.ofNullable(parentClass).map(ConvertableTo::convert).orElse(null); + return this; + } + + public ClassBuilder setModifiers(Modifiers... modifiers) { + return setModifiers(Set.of(modifiers)); + } + + public ClassBuilder addModifiers(Modifiers... modifiers) { + this.modifiers.addAll(Set.of(modifiers)); + return this; + } + + public ClassBuilder setModifiers(Set modifiers) { + this.modifiers = new HashSet<>(modifiers); + return this; + } + + public ClassBuilder setAnnotations(List annotations) { + this.annotations = new ArrayList<>(annotations); + return this; + } + + public ClassBuilder addAnnotations(CodeAnnotation... annotations) { + this.annotations.addAll(List.of(annotations)); + return this; + } + + public ClassBuilder setJavadoc(@Nullable CodeJavadoc javadoc) { + this.javadoc = javadoc; + return this; + } + + public ClassBuilder setTypeParameters(List typeParameters) { + this.typeParameters = typeParameters; + return this; + } + + public ClassBuilder addMethod(ConvertableTo method) { + this.methods.add(method.convert()); + return this; + } + + public ClassBuilder addField(ConvertableTo field) { + this.fields.add(field.convert()); + return this; + } + + public CodeClass build() { + return new CodeClass( + codePackage, + parentClass, + name, + new HashSet<>(modifiers), + new ArrayList<>(annotations), + new ArrayList<>(methods), + new ArrayList<>(fields), + javadoc, + typeParameters + ); + } + + @Override + public CodeClass convert() { + return build(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof final ClassBuilder that)) { + return false; + } + return Objects.equals(codePackage, that.codePackage) && Objects.equals(parentClass, that.parentClass) && Objects.equals(name, that.name) && Objects.equals(modifiers, that.modifiers) && Objects.equals(annotations, that.annotations) && Objects.equals(methods, that.methods) && Objects.equals(fields, that.fields) && Objects.equals(javadoc, that.javadoc) && Objects.equals(typeParameters, that.typeParameters); + } + + @Override + public int hashCode() { + return Objects.hash(codePackage, parentClass, name, modifiers, annotations, methods, fields, javadoc, typeParameters); + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/FieldAccessBuilder.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/FieldAccessBuilder.java new file mode 100644 index 00000000..ac0042f0 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/FieldAccessBuilder.java @@ -0,0 +1,73 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen.builder; + +import net.strokkur.commands.internal.codegen.CodeExpression; +import net.strokkur.commands.internal.codegen.CodeType; +import net.strokkur.commands.internal.codegen.as.AsCodeType; +import net.strokkur.commands.internal.codegen.as.AsExpression; +import net.strokkur.commands.internal.util.ConvertableTo; +import org.jspecify.annotations.Nullable; + +public class FieldAccessBuilder implements ConvertableTo, AsExpression { + private final String fieldName; + + private CodeType.@Nullable ClassType type = null; + private @Nullable CodeExpression source = null; + private boolean isStatic = false; + + public FieldAccessBuilder(String fieldName) { + this.fieldName = fieldName; + } + + public FieldAccessBuilder setType(CodeType.ClassType type) { + this.type = type; + return this; + } + + public FieldAccessBuilder setSource(AsExpression source) { + this.source = source.getAsExpression(); + return this; + } + + public FieldAccessBuilder setStatic(AsCodeType type) { + return setStatic(type.getAsCodeType()); + } + + public FieldAccessBuilder setStatic(CodeType.ClassType type) { + this.isStatic = true; + this.type = type; + return this; + } + + public CodeExpression.FieldAccess build() { + return new CodeExpression.FieldAccess( + type, source, fieldName, isStatic + ); + } + + @Override + public CodeExpression.FieldAccess convert() { + return build(); + } + + @Override + public CodeExpression getAsExpression() { + return build(); + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/FieldBuilder.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/FieldBuilder.java new file mode 100644 index 00000000..9a5f3c98 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/FieldBuilder.java @@ -0,0 +1,88 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen.builder; + +import net.strokkur.commands.internal.codegen.CodeAnnotation; +import net.strokkur.commands.internal.codegen.CodeExpression; +import net.strokkur.commands.internal.codegen.CodeField; +import net.strokkur.commands.internal.codegen.CodeType; +import net.strokkur.commands.internal.codegen.Modifiers; +import net.strokkur.commands.internal.codegen.as.AsExpression; +import net.strokkur.commands.internal.util.ConvertableTo; +import org.jspecify.annotations.Nullable; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +public class FieldBuilder implements ConvertableTo { + private @Nullable String name = null; + private @Nullable CodeType type = null; + private @Nullable CodeExpression initialiser; + private final Set modifiers = new HashSet<>(); + private final List annotations = new ArrayList<>(); + + FieldBuilder() { + // package-private ctor + } + + public FieldBuilder setName(String name) { + this.name = name; + return this; + } + + public FieldBuilder setType(CodeType type) { + this.type = type; + return this; + } + + public FieldBuilder setInitialiser(AsExpression initialiser) { + this.initialiser = initialiser.getAsExpression(); + return this; + } + + public FieldBuilder setModifiers(Modifiers... modifiers) { + return setModifiers(Set.of(modifiers)); + } + + public FieldBuilder setModifiers(Set modifiers) { + this.modifiers.clear(); + this.modifiers.addAll(modifiers); + return this; + } + + public FieldBuilder addAnnotation(CodeAnnotation annotation) { + this.annotations.add(annotation); + return this; + } + + public CodeField build() { + Objects.requireNonNull(name); + Objects.requireNonNull(type); + return new CodeField( + name, type, initialiser, Set.copyOf(modifiers), List.copyOf(annotations) + ); + } + + @Override + public CodeField convert() { + return build(); + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/MethodBuilder.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/MethodBuilder.java new file mode 100644 index 00000000..2bf8d46f --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/MethodBuilder.java @@ -0,0 +1,160 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen.builder; + +import net.strokkur.commands.internal.codegen.CodeBlock; +import net.strokkur.commands.internal.codegen.CodeClass; +import net.strokkur.commands.internal.codegen.CodeConstructor; +import net.strokkur.commands.internal.codegen.CodeMethod; +import net.strokkur.commands.internal.codegen.CodeParameter; +import net.strokkur.commands.internal.codegen.CodeStatement; +import net.strokkur.commands.internal.codegen.CodeType; +import net.strokkur.commands.internal.codegen.Modifiers; +import net.strokkur.commands.internal.codegen.as.AsCodeType; +import net.strokkur.commands.internal.codegen.as.AsStatement; +import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc; +import net.strokkur.commands.internal.util.ConvertableTo; +import org.jspecify.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +public class MethodBuilder implements ConvertableTo { + private @Nullable CodeClass declaredClass = null; + private final String name; + + private CodeType returnType = CodeType.VOID; + private final List parameters = new ArrayList<>(); + private Set modifiers = new HashSet<>(); + private @Nullable CodeJavadoc javadoc = null; + private List methodStatements = new ArrayList<>(); + private final List generics = new ArrayList<>(); + private List throwsExceptions = List.of(); + + MethodBuilder(String name) { + this.name = name; + } + + public MethodBuilder setDeclaringClass(CodeClass declaringClass) { + this.declaredClass = declaringClass; + return this; + } + + public MethodBuilder addGeneric(CodeType.GenericType generic) { + this.generics.add(generic); + return this; + } + + public MethodBuilder addParameter(CodeType type, String name) { + this.parameters.add(CodeParameter.of(type, name)); + return this; + } + + public MethodBuilder setReturnType(CodeType returnType) { + this.returnType = returnType; + return this; + } + + public MethodBuilder setModifiers(Modifiers... modifiers) { + return setModifiers(Set.of(modifiers)); + } + + public MethodBuilder addModifiers(Modifiers... modifiers) { + this.modifiers.addAll(Set.of(modifiers)); + return this; + } + + public MethodBuilder setModifiers(Set modifiers) { + this.modifiers = new HashSet<>(modifiers); + return this; + } + + public MethodBuilder setJavadoc(@Nullable CodeJavadoc javadoc) { + this.javadoc = javadoc; + return this; + } + + public MethodBuilder addMethodStatements(AsStatement... statements) { + this.methodStatements.addAll(Arrays.stream(statements) + .map(AsStatement::getAsStatement) + .toList()); + return this; + } + + public MethodBuilder setMethodStatements(AsStatement... statements) { + return setMethodStatements(List.of(statements)); + } + + public MethodBuilder setMethodStatements(List statements) { + this.methodStatements = new ArrayList<>(statements.stream() + .map(AsStatement::getAsStatement) + .toList()); + return this; + } + + @SafeVarargs + public final MethodBuilder setThrowsExceptions(AsCodeType... throwsExceptions) { + this.throwsExceptions = Arrays.stream(throwsExceptions) + .map(AsCodeType::getAsCodeType) + .toList(); + return this; + } + + public MethodBuilder setThrowsExceptions(CodeType.ClassType... throwsExceptions) { + this.throwsExceptions = List.of(throwsExceptions); + return this; + } + + public CodeMethod build() { + Objects.requireNonNull(this.name); + return new CodeMethod( + Optional.ofNullable(declaredClass).orElse(CodeClass.simple("no.class.provided.in.MethodBuilder")), + returnType, + name, + List.copyOf(parameters), + Set.copyOf(modifiers), + javadoc, + new CodeBlock(List.copyOf(methodStatements)), + List.copyOf(generics), + List.copyOf(throwsExceptions) + ); + } + + public CodeConstructor buildConstructor() { + Objects.requireNonNull(this.declaredClass); + return new CodeConstructor( + declaredClass, + List.copyOf(parameters), + Set.copyOf(modifiers), + javadoc, + new CodeBlock(List.copyOf(methodStatements)), + List.copyOf(generics), + List.copyOf(throwsExceptions) + ); + } + + @Override + public CodeMethod convert() { + return build(); + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/MethodInvocationBuilder.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/MethodInvocationBuilder.java new file mode 100644 index 00000000..ded36210 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/MethodInvocationBuilder.java @@ -0,0 +1,138 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen.builder; + +import net.strokkur.commands.internal.codegen.CodeExpression; +import net.strokkur.commands.internal.codegen.CodeStatement; +import net.strokkur.commands.internal.codegen.CodeType; +import net.strokkur.commands.internal.codegen.InvokesMethod; +import net.strokkur.commands.internal.codegen.as.AsCodeType; +import net.strokkur.commands.internal.codegen.as.AsExpression; +import net.strokkur.commands.internal.codegen.as.AsStatement; +import org.jspecify.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class MethodInvocationBuilder implements AsExpression, AsStatement { + private final String methodName; + private CodeType.@Nullable ClassType type = null; + private final List parameters = new ArrayList<>(); + private @Nullable CodeExpression instanceSource = null; + private boolean newline = false; + private boolean isStatic = false; + private boolean isCtor = false; + private boolean multilineParameters = false; + private boolean newlineClosingBrace = false; + private final List chained = new ArrayList<>(); + + MethodInvocationBuilder(String methodName) { + this.methodName = methodName; + } + + public MethodInvocationBuilder setType(CodeType.ClassType type) { + this.type = type; + return this; + } + + public MethodInvocationBuilder addParameter(AsExpression expression) { + this.parameters.add(expression.getAsExpression()); + return this; + } + + public MethodInvocationBuilder setInstanceSource(AsExpression instanceSource) { + this.instanceSource = instanceSource.getAsExpression(); + return this; + } + + public MethodInvocationBuilder setInstanceVariable(String instanceVariable) { + this.instanceSource = CodeExpression.variable(instanceVariable); + return this; + } + + public MethodInvocationBuilder setNewline() { + this.newline = true; + return this; + } + + public MethodInvocationBuilder setStatic(AsCodeType type) { + return setStatic(type.getAsCodeType()); + } + + public MethodInvocationBuilder setStatic(CodeType.ClassType type) { + return setType(type).setStatic(); + } + + public MethodInvocationBuilder setStatic() { + this.isStatic = true; + return this; + } + + public MethodInvocationBuilder setCtor() { + this.isCtor = true; + return this; + } + + public MethodInvocationBuilder setMultilineParameters() { + this.multilineParameters = true; + return this; + } + + public MethodInvocationBuilder setNewlineClosingBrace() { + this.newlineClosingBrace = true; + return this; + } + + public MethodInvocationBuilder chain(String methodName, AsExpression... parameters) { + return chain(methodName, InvokesMethod.StyleConfig.DEFAULT, parameters); + } + + public MethodInvocationBuilder chain(String methodName, InvokesMethod.StyleConfig config, AsExpression... parameters) { + this.chained.add(new InvokesMethod.Chained( + methodName, + Arrays.stream(parameters) + .map(AsExpression::getAsExpression) + .toList(), + config + )); + return this; + } + + public InvokesMethod build() { + return new InvokesMethod(methodName, + type, + List.copyOf(parameters), + instanceSource, + isStatic, + isCtor, + new InvokesMethod.StyleConfig(newline, multilineParameters, newlineClosingBrace), + List.copyOf(chained) + ); + } + + @Override + public CodeExpression.MethodInvocation getAsExpression() { + return new CodeExpression.MethodInvocation(build()); + } + + @Override + public CodeStatement.MethodInvocation getAsStatement() { + return new CodeStatement.MethodInvocation(build()); + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/javadoc/CodeJavadoc.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/javadoc/CodeJavadoc.java new file mode 100644 index 00000000..2eb748f1 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/javadoc/CodeJavadoc.java @@ -0,0 +1,221 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen.javadoc; + +import net.strokkur.commands.internal.codegen.CodeClass; +import net.strokkur.commands.internal.codegen.CodeMethod; +import net.strokkur.commands.internal.codegen.CodeType; +import net.strokkur.commands.internal.codegen.as.AsCodeType; +import net.strokkur.commands.internal.codegen.visitor.JavadocVisitor; +import net.strokkur.commands.internal.util.ConvertableTo; +import org.jspecify.annotations.Nullable; + +import java.util.List; + +public interface CodeJavadoc { + void accept(JavadocVisitor visitor); + + // + static CodeJavadoc combine(CodeJavadoc... children) { + return new JavadocComponentList(List.of(children), false); + } + + static CodeJavadoc combineLines(CodeJavadoc... children) { + return new JavadocComponentList(List.of(children), true); + } + + static CodeJavadoc text(String text) { + return new PlainText(text); + } + + static CodeJavadoc header(String text, int level) { + return new Header(text, level); + } + + static CodeJavadoc author(String author) { + return new Meta("author", author); + } + + static CodeJavadoc version(String version) { + return new Meta("version", version); + } + + static CodeJavadoc see(ConvertableTo method, @Nullable String description) { + return see(method, description, false); + } + + static CodeJavadoc see(ConvertableTo method, @Nullable String description, boolean localMethod) { + return new MethodReferenceMeta("see", method.convert(), description, localMethod); + } + + static CodeJavadoc throwsMeta(AsCodeType exception, @Nullable String description) { + return throwsMeta(exception.getAsCodeType(), description); + } + + static CodeJavadoc throwsMeta(CodeType.ClassType exception, @Nullable String description) { + return new ClassReferenceMeta("throws", exception, description); + } + + static CodeJavadoc newline() { + return new Newline(); + } + + /// Intended to be used inside [#combineLines(CodeJavadoc...)] for a true blank line. + static CodeJavadoc blank() { + return visitor -> { + // noop + }; + } + + static CodeJavadoc linebreak() { + return new Linebreak(); + } + + static CodeJavadoc inlineCode(String code) { + return new InlineCode(code); + } + + static CodeJavadoc codeBlock(String code) { + return new CodeBlock(code); + } + + static CodeJavadoc url(String text, String url) { + return new Url(text, url); + } + + static CodeJavadoc classReference(CodeClass ref) { + return classReference(ref, null); + } + + static CodeJavadoc classReference(CodeClass ref, @Nullable String description) { + return new ClassReference(ref, description); + } + + static CodeJavadoc methodReference(ConvertableTo ref) { + return methodReference(ref, null); + } + + static CodeJavadoc methodReference(ConvertableTo ref, @Nullable String description) { + return methodReference(ref, description, false); + } + + static CodeJavadoc methodReference(ConvertableTo ref, boolean localMethod) { + return methodReference(ref, null, localMethod); + } + + static CodeJavadoc methodReference(ConvertableTo ref, @Nullable String description, boolean localMethod) { + return new MethodReference(ref.convert(), description, localMethod); + } + // + + record JavadocComponentList(List components, boolean insertNewlines) implements CodeJavadoc { + @Override + public void accept(JavadocVisitor visitor) { + for (CodeJavadoc component : components) { + component.accept(visitor); + if (insertNewlines) { + new Newline().accept(visitor); + } + } + } + } + + record PlainText(String text) implements CodeJavadoc { + @Override + public void accept(JavadocVisitor visitor) { + visitor.visit(this); + } + } + + record Meta(String descriptor, String value) implements CodeJavadoc { + @Override + public void accept(JavadocVisitor visitor) { + visitor.visit(this); + } + } + + record ClassReferenceMeta(String descriptor, CodeType.ClassType type, @Nullable String text) implements CodeJavadoc { + @Override + public void accept(JavadocVisitor visitor) { + visitor.visit(this); + } + } + + record MethodReferenceMeta(String descriptor, CodeMethod codeMethod, @Nullable String text, boolean localMethod) implements CodeJavadoc { + @Override + public void accept(JavadocVisitor visitor) { + visitor.visit(this); + } + } + + record Header(String text, int level) implements CodeJavadoc { + @Override + public void accept(JavadocVisitor visitor) { + visitor.visit(this); + } + } + + record Newline() implements CodeJavadoc { + @Override + public void accept(JavadocVisitor visitor) { + visitor.visit(this); + } + } + + record Linebreak() implements CodeJavadoc { + @Override + public void accept(JavadocVisitor visitor) { + visitor.visit(this); + } + } + + record InlineCode(String code) implements CodeJavadoc { + @Override + public void accept(JavadocVisitor visitor) { + visitor.visit(this); + } + } + + record CodeBlock(String code) implements CodeJavadoc { + @Override + public void accept(JavadocVisitor visitor) { + visitor.visit(this); + } + } + + record Url(String text, String url) implements CodeJavadoc { + @Override + public void accept(JavadocVisitor visitor) { + visitor.visit(this); + } + } + + record ClassReference(CodeClass codeClass, @Nullable String name) implements CodeJavadoc { + @Override + public void accept(JavadocVisitor visitor) { + visitor.visit(this); + } + } + + record MethodReference(CodeMethod method, @Nullable String name, boolean localMethod) implements CodeJavadoc { + @Override + public void accept(JavadocVisitor visitor) { + visitor.visit(this); + } + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/visitor/CodeVisitable.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/visitor/CodeVisitable.java new file mode 100644 index 00000000..243164f0 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/visitor/CodeVisitable.java @@ -0,0 +1,22 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen.visitor; + +public interface CodeVisitable { + R accept(CodeVisitor visitor); +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/visitor/CodeVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/visitor/CodeVisitor.java new file mode 100644 index 00000000..009a1b76 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/visitor/CodeVisitor.java @@ -0,0 +1,48 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen.visitor; + +import net.strokkur.commands.internal.codegen.CodeAnnotation; +import net.strokkur.commands.internal.codegen.CodeClass; +import net.strokkur.commands.internal.codegen.CodeExpression; +import net.strokkur.commands.internal.codegen.CodeField; +import net.strokkur.commands.internal.codegen.CodeMethod; +import net.strokkur.commands.internal.codegen.CodePackage; +import net.strokkur.commands.internal.codegen.CodeParameter; +import net.strokkur.commands.internal.codegen.CodeStatement; +import net.strokkur.commands.internal.codegen.CodeType; + +public interface CodeVisitor { + R visitClass(CodeClass codeClass); + + R visitMethod(CodeMethod codeMethod); + + R visitPackage(CodePackage codePackage); + + R visitParameter(CodeParameter codeParameter); + + R visitType(CodeType codeType); + + R visitAnnotation(CodeAnnotation codeAnnotation); + + R visitField(CodeField codeField); + + R visitExpression(CodeExpression codeExpression); + + R visitStatement(CodeStatement codeStatement); +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/visitor/JavadocVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/visitor/JavadocVisitor.java new file mode 100644 index 00000000..c223fb54 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/visitor/JavadocVisitor.java @@ -0,0 +1,47 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen.visitor; + +import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc; + +public interface JavadocVisitor { + + void visit(CodeJavadoc.PlainText value); + + void visit(CodeJavadoc.Meta value); + + void visit(CodeJavadoc.MethodReferenceMeta value); + + void visit(CodeJavadoc.ClassReferenceMeta value); + + void visit(CodeJavadoc.Header value); + + void visit(CodeJavadoc.Newline value); + + void visit(CodeJavadoc.Linebreak value); + + void visit(CodeJavadoc.InlineCode value); + + void visit(CodeJavadoc.CodeBlock value); + + void visit(CodeJavadoc.Url value); + + void visit(CodeJavadoc.ClassReference value); + + void visit(CodeJavadoc.MethodReference value); +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/access/ExecuteAccess.java b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/access/ExecuteAccess.java index 650499ee..edb6d2d7 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/access/ExecuteAccess.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/access/ExecuteAccess.java @@ -20,8 +20,12 @@ import net.strokkur.commands.internal.abstraction.SourceClass; import net.strokkur.commands.internal.abstraction.SourceElement; import net.strokkur.commands.internal.abstraction.SourceField; +import net.strokkur.commands.internal.codegen.CodeType; +import net.strokkur.commands.internal.codegen.as.AsCodeType; -public sealed interface ExecuteAccess permits ExecuteAccessImpl, FieldAccess, InstanceAccess { +public sealed interface ExecuteAccess + extends AsCodeType + permits ExecuteAccessImpl, FieldAccess, InstanceAccess { static FieldAccess of(SourceField field) { return new FieldAccessImpl(field); diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/access/FieldAccessImpl.java b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/access/FieldAccessImpl.java index 1b1bca01..4fd526b0 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/access/FieldAccessImpl.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/access/FieldAccessImpl.java @@ -18,6 +18,8 @@ package net.strokkur.commands.internal.intermediate.access; import net.strokkur.commands.internal.abstraction.SourceField; +import net.strokkur.commands.internal.codegen.CodeType; +import net.strokkur.commands.internal.codegen.adapter.CodeTypeAdapter; final class FieldAccessImpl extends ExecuteAccessImpl implements FieldAccess { @@ -25,6 +27,11 @@ final class FieldAccessImpl extends ExecuteAccessImpl implements Fi super(element); } + @Override + public CodeType getAsCodeType() { + return CodeTypeAdapter.from(element.getType()); + } + @Override public String toString() { return "FieldAccessImpl[" + element + ']'; diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/access/InstanceAccessImpl.java b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/access/InstanceAccessImpl.java index ccaf76f6..34e2c02b 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/access/InstanceAccessImpl.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/access/InstanceAccessImpl.java @@ -18,6 +18,8 @@ package net.strokkur.commands.internal.intermediate.access; import net.strokkur.commands.internal.abstraction.SourceClass; +import net.strokkur.commands.internal.codegen.CodeType; +import net.strokkur.commands.internal.codegen.adapter.CodeTypeAdapter; final class InstanceAccessImpl extends ExecuteAccessImpl implements InstanceAccess { @@ -25,6 +27,11 @@ final class InstanceAccessImpl extends ExecuteAccessImpl implements super(element); } + @Override + public CodeType.ClassType getAsCodeType() { + return CodeTypeAdapter.from(element); + } + @Override public String toString() { return "InstanceAccessImpl[" + element.getName() + ']'; diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/attributes/Attributable.java b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/attributes/Attributable.java index 6ea65b12..e76fe8de 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/attributes/Attributable.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/attributes/Attributable.java @@ -91,4 +91,8 @@ default void editAttributeMutable(AttributeKey key, Consumer action, @ default T getAttributeNotNull(AttributeKey key) { return Objects.requireNonNull(getAttribute(key), "Attribute key " + key + " is null"); } + + default Optional getAttributeOptional(AttributeKey key) { + return Optional.ofNullable(getAttribute(key)); + } } diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/attributes/AttributableHelper.java b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/attributes/AttributableHelper.java index bb073b3c..9842cef0 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/attributes/AttributableHelper.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/attributes/AttributableHelper.java @@ -27,6 +27,7 @@ public interface AttributableHelper extends Attributable { @Override default @Nullable T getAttribute(AttributeKey key) { + //noinspection unchecked return (T) attributeMap().getOrDefault(key.key(), key.defaultValue()); } diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/executable/DefaultExecutable.java b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/executable/DefaultExecutable.java index 435d748e..cdcb508a 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/executable/DefaultExecutable.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/executable/DefaultExecutable.java @@ -18,37 +18,38 @@ package net.strokkur.commands.internal.intermediate.executable; import net.strokkur.commands.internal.abstraction.SourceVariable; +import net.strokkur.commands.internal.codegen.CodeExpression; +import net.strokkur.commands.internal.codegen.as.AsExpression; +import net.strokkur.commands.internal.codegen.builder.Builders; import net.strokkur.commands.internal.intermediate.attributes.Attributable; import net.strokkur.commands.internal.util.Classes; import org.jspecify.annotations.Nullable; -import java.util.Set; - public interface DefaultExecutable extends Executable, Attributable { enum Type { - NONE(null, Set.of()), - ARRAY("ctx.getInput().split(\" \")", Set.of()), - LIST("Collections.unmodifiableList(Arrays.asList(ctx.getInput().split(\" \")))", Set.of(Classes.COLLECTIONS, Classes.ARRAYS)); - - private final @Nullable String getter; - private final Set imports; - - Type(@Nullable String getter, Set imports) { + NONE(null), + ARRAY(Builders.methodInvocation("getInput").setInstanceVariable("ctx").chain("split", CodeExpression.string(" "))), + LIST(Builders.methodInvocation("unmodifiableList").setStatic(Classes.COLLECTIONS) + .addParameter(Builders.methodInvocation("asList").setStatic(Classes.ARRAYS) + .addParameter(Builders.methodInvocation("getInput") + .setInstanceVariable("ctx") + .chain("split", CodeExpression.string(" ")) + )) + ); + + private final @Nullable AsExpression getter; + + Type(@Nullable AsExpression getter) { this.getter = getter; - this.imports = imports; } - public @Nullable String getGetter() { + public @Nullable AsExpression getGetter() { return this.getter; } - public Set getImports() { - return this.imports; - } - public static DefaultExecutable.Type getType(SourceVariable variable) { - if (variable.getType().getFullyQualifiedAndTypedName().equals(Classes.LIST_STRING)) { + if (variable.getType().getFullyQualifiedAndTypedName().equals("java.util.List")) { return LIST; } if (variable.getType().getFullyQualifiedName().equals("java.lang.String[]")) { diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/CombinedRequirementProvider.java b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/CombinedRequirementProvider.java index bf5bb7b4..63c3399d 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/CombinedRequirementProvider.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/CombinedRequirementProvider.java @@ -18,6 +18,8 @@ package net.strokkur.commands.internal.intermediate.registrable; import net.strokkur.commands.internal.abstraction.SourceClass; +import net.strokkur.commands.internal.codegen.CodeExpression; +import net.strokkur.commands.internal.codegen.as.AsExpression; import java.util.Collections; import java.util.LinkedList; @@ -33,6 +35,11 @@ public CombinedRequirementProvider(List providers) { .toList(); } + @Override + public AsExpression getRequirementExpression() { + return CodeExpression.lambda(List.of("source"), CodeExpression.nullExpr()); + } + @Override public String getRequirementString() { if (providers.size() == 1) { diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/FieldImpl.java b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/FieldImpl.java index 981b9a2c..8010532a 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/FieldImpl.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/FieldImpl.java @@ -19,6 +19,10 @@ import net.strokkur.commands.internal.abstraction.SourceClass; import net.strokkur.commands.internal.abstraction.SourceField; +import net.strokkur.commands.internal.codegen.CodeExpression; +import net.strokkur.commands.internal.codegen.adapter.CodeTypeAdapter; +import net.strokkur.commands.internal.codegen.as.AsExpression; +import net.strokkur.commands.internal.codegen.builder.Builders; import java.util.List; @@ -33,6 +37,22 @@ public String getRequirementString() { return "%s.%s.test(source)".formatted(sourceClass.getSourceName(), field.getName()); } + @Override + public AsExpression getRequirementExpression() { + return CodeExpression.lambda(List.of("source"), Builders.methodInvocation("test") + .setInstanceSource(Builders.fieldAccess(field.getName()) + .setStatic(CodeTypeAdapter.from(sourceClass))) + .addParameter(CodeExpression.variable("source")) + ); + } + + @Override + public AsExpression getSuggestionExpression() { + return CodeExpression.lambda(List.of("source"), + Builders.fieldAccess(field.getName()).setStatic(CodeTypeAdapter.from(sourceClass)) + ); + } + @Override public SourceClass getSourceClass() { return this.sourceClass; diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/InstanceImpl.java b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/InstanceImpl.java index 9c75e8c1..708360f0 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/InstanceImpl.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/InstanceImpl.java @@ -18,6 +18,7 @@ package net.strokkur.commands.internal.intermediate.registrable; import net.strokkur.commands.internal.abstraction.SourceClass; +import net.strokkur.commands.internal.codegen.as.AsExpression; import java.util.List; @@ -32,6 +33,16 @@ public String getRequirementString() { return "new %s().test(source)".formatted(sourceClass.getSourceName()); } + @Override + public AsExpression getRequirementExpression() { + return null; + } + + @Override + public AsExpression getSuggestionExpression() { + return null; + } + @Override public List getSourceClasses() { return List.of(this.sourceClass); diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/MethodImpl.java b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/MethodImpl.java index 0ae6ac56..76ffb9a4 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/MethodImpl.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/MethodImpl.java @@ -19,10 +19,15 @@ import net.strokkur.commands.internal.abstraction.SourceClass; import net.strokkur.commands.internal.abstraction.SourceMethod; +import net.strokkur.commands.internal.codegen.CodeExpression; +import net.strokkur.commands.internal.codegen.adapter.CodeTypeAdapter; +import net.strokkur.commands.internal.codegen.as.AsExpression; +import net.strokkur.commands.internal.codegen.builder.Builders; import java.util.List; -record MethodImpl(SourceClass sourceClass, SourceMethod sourceMethod, boolean inline) implements SuggestionProvider, RequirementProvider { +record MethodImpl(SourceClass sourceClass, SourceMethod sourceMethod, + boolean inline) implements SuggestionProvider, RequirementProvider { @Override public String getSuggestionString() { if (inline) { @@ -41,6 +46,38 @@ public String getRequirementString() { return "%s.%s().test(source)".formatted(sourceClass.getSourceName(), sourceMethod.getName()); } + @Override + public AsExpression getRequirementExpression() { + if (inline) { + return CodeExpression.lambda(List.of("source"), + Builders.methodInvocation(sourceMethod.getName()) + .setStatic(CodeTypeAdapter.from(sourceClass)) + .addParameter(CodeExpression.variable("source")) + ); + } + + return CodeExpression.lambda(List.of("source"), + Builders.methodInvocation(sourceMethod.getName()) + .setStatic(CodeTypeAdapter.from(sourceClass)) + .chain("test", CodeExpression.variable("source")) + ); + } + + @Override + public AsExpression getSuggestionExpression() { + if (inline) { + return CodeExpression.methodReference( + CodeTypeAdapter.from(sourceClass), + sourceMethod.getName() + ); + } + + return CodeExpression.lambda( + List.of("source"), + Builders.methodInvocation(sourceMethod.getName()).setStatic(CodeTypeAdapter.from(sourceClass)) + ); + } + @Override public SourceClass getSourceClass() { return this.sourceClass; diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/RequirementProvider.java b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/RequirementProvider.java index dfb4a6ad..4db2ee6a 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/RequirementProvider.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/RequirementProvider.java @@ -18,10 +18,13 @@ package net.strokkur.commands.internal.intermediate.registrable; import net.strokkur.commands.internal.abstraction.SourceClass; +import net.strokkur.commands.internal.codegen.as.AsExpression; import java.util.List; public interface RequirementProvider { + AsExpression getRequirementExpression(); + String getRequirementString(); List getSourceClasses(); diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/SuggestionProvider.java b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/SuggestionProvider.java index a8766aad..e64e5881 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/SuggestionProvider.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/SuggestionProvider.java @@ -18,8 +18,11 @@ package net.strokkur.commands.internal.intermediate.registrable; import net.strokkur.commands.internal.abstraction.SourceClass; +import net.strokkur.commands.internal.codegen.as.AsExpression; public interface SuggestionProvider { + AsExpression getSuggestionExpression(); + String getSuggestionString(); SourceClass getSourceClass(); diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/SuggestionsRegistry.java b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/SuggestionsRegistry.java index a234a7e6..5bf1906b 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/SuggestionsRegistry.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/intermediate/registrable/SuggestionsRegistry.java @@ -34,10 +34,10 @@ public SuggestionsRegistry(String platformType) { @Override protected boolean inlineMethodPredicate(SourceMethod source) { final List params = source.getParameters(); - return source.getReturnType().getFullyQualifiedAndTypedName().equals(Classes.COMPLETABLE_FUTURE + "<" + Classes.SUGGESTIONS + ">") + return source.getReturnType().getFullyQualifiedAndTypedName().equals(Classes.COMPLETABLE_FUTURE.getAsCodeType().fullyQualifiedName() + "<" + Classes.SUGGESTIONS.getAsCodeType().fullyQualifiedName() + ">") && params.size() == 2 - && params.getFirst().getType().getFullyQualifiedAndTypedName().equals(Classes.COMMAND_CONTEXT + "<" + getPlatformType() + ">") - && params.get(1).getType().getFullyQualifiedName().equals(Classes.SUGGESTIONS_BUILDER); + && params.getFirst().getType().getFullyQualifiedAndTypedName().equals(Classes.COMMAND_CONTEXT.getAsCodeType().fullyQualifiedName() + "<" + getPlatformType() + ">") + && params.get(1).getType().getFullyQualifiedName().equals(Classes.SUGGESTIONS_BUILDER.getAsCodeType().fullyQualifiedName()); } @Override diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/AbstractPrinter.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/AbstractPrinter.java deleted file mode 100644 index dec66bfc..00000000 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/AbstractPrinter.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * StrokkCommands - A super simple annotation based zero-shade Paper command API library. - * Copyright (C) 2025 Strokkur24 - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see . - */ -package net.strokkur.commands.internal.printer; - -import org.intellij.lang.annotations.Language; -import org.intellij.lang.annotations.PrintFormat; -import org.jspecify.annotations.Nullable; - -import java.io.IOException; -import java.io.Writer; - -public abstract class AbstractPrinter implements Printable { - private final String indentPreset = "\s\s\s\s"; - private @Nullable Writer writer; - private String indentString; - private int indent; - - public AbstractPrinter(int indent, @Nullable Writer writer) { - this.writer = writer; - this.indent = indent; - this.indentString = indentPreset.repeat(this.indent); - } - - public void setWriter(@Nullable Writer writer) { - this.writer = writer; - } - - @Override - public void incrementIndent() { - this.indent++; - this.indentString = indentPreset.repeat(this.indent); - } - - @Override - public void decrementIndent() { - if (indent == 0) { - return; - } - - this.indent--; - this.indentString = indentPreset.repeat(this.indent); - } - - @Override - public void print(String message, Object... format) throws IOException { - if (writer == null) { - throw new IOException("No writer set."); - } - - writer.append(message.replace("{}", "%s").formatted(format)); - } - - @Override - public void printIndent() throws IOException { - if (writer == null) { - throw new IOException("No writer set."); - } - - writer.append(indentString); - } - - @Override - public void println(String message, Object... format) throws IOException { - if (writer == null) { - throw new IOException("No writer set."); - } - - final String stripped = message.stripTrailing(); - if (!stripped.isBlank()) { - writer.append(indentString); - writer.append(stripped.replace("{}", "%s").formatted(format)); - } - writer.append("\n"); - } - - @Override - public void println() throws IOException { - if (writer == null) { - throw new IOException("No writer set."); - } - - writer.append("\n"); - } - - @Override - public void printBlock(@PrintFormat String block, Object... format) throws IOException { - if (writer == null) { - throw new IOException("No writer set."); - } - - final String parsedBlock = block.replace("{}", "%s").formatted(format); - for (@Language("JAVA") String line : parsedBlock.split("\n")) { - println(line); - } - } -} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonBrigadierStatementBuilder.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonBrigadierStatementBuilder.java new file mode 100644 index 00000000..0205f618 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonBrigadierStatementBuilder.java @@ -0,0 +1,224 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.printer; + +import net.strokkur.commands.internal.abstraction.SourceVariable; +import net.strokkur.commands.internal.arguments.CommandArgument; +import net.strokkur.commands.internal.arguments.LiteralCommandArgument; +import net.strokkur.commands.internal.arguments.MultiLiteralCommandArgument; +import net.strokkur.commands.internal.arguments.RequiredCommandArgument; +import net.strokkur.commands.internal.codegen.CodeExpression; +import net.strokkur.commands.internal.codegen.CodeStatement; +import net.strokkur.commands.internal.codegen.InvokesMethod; +import net.strokkur.commands.internal.codegen.adapter.CodeTypeAdapter; +import net.strokkur.commands.internal.codegen.as.AsExpression; +import net.strokkur.commands.internal.codegen.as.AsStatement; +import net.strokkur.commands.internal.codegen.builder.Builders; +import net.strokkur.commands.internal.codegen.builder.MethodInvocationBuilder; +import net.strokkur.commands.internal.intermediate.access.ExecuteAccess; +import net.strokkur.commands.internal.intermediate.attributes.AttributeKey; +import net.strokkur.commands.internal.intermediate.executable.Executable; +import net.strokkur.commands.internal.intermediate.executable.Parameterizable; +import net.strokkur.commands.internal.intermediate.executable.SourceParameterType; +import net.strokkur.commands.internal.intermediate.registrable.RequirementProvider; +import net.strokkur.commands.internal.intermediate.registrable.SuggestionProvider; +import net.strokkur.commands.internal.intermediate.tree.CommandNode; +import net.strokkur.commands.internal.util.Classes; +import org.jetbrains.annotations.MustBeInvokedByOverriders; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.Stack; + +public abstract class CommonBrigadierStatementBuilder { + protected final Set requiredPaths = new HashSet<>(); + private final Stack> accessStack = new Stack<>(); + private final Stack literalStack = new Stack<>(); + private int literalPointer = 0; + + protected abstract MethodInvocationBuilder literalBuilder(AsExpression name); + + protected abstract MethodInvocationBuilder argumentBuilder(AsExpression name, AsExpression argument); + + protected abstract List validationStatements(Executable executable); + + public final AsExpression build(CommandNode node, AsExpression rootNameExpression) { + final MethodInvocationBuilder builder = literalBuilder(rootNameExpression); + fill(builder, node); + builder.chain("build", InvokesMethod.StyleConfig.NEWLINE); + return builder; + } + + /// Resets the state of this builder to the original state for reuse. + @MustBeInvokedByOverriders + protected void reset() { + requiredPaths.clear(); + if (!accessStack.isEmpty()) { + System.err.println("The access stack was not empty; something is leaking resources."); + accessStack.clear(); + } + if (!literalStack.isEmpty()) { + System.err.println("The literal stack was not empty; something is leaking resources."); + literalStack.clear(); + } + if (literalPointer != 0) { + System.err.printf("The literal pointer was not 0 (was: %s); something is leaking resources.%n", literalPointer); + literalPointer = 0; + } + } + + protected void fill(MethodInvocationBuilder builder, CommandNode node) { + scopeAccessStack(node, () -> { + populateNode(builder, node); + for (CommandNode child : node.children()) { + appendNode(builder, child); + } + }); + } + + protected void populateNode(MethodInvocationBuilder builder, CommandNode node) { + // Requirements + if (node.hasAttribute(AttributeKey.REQUIREMENT_PROVIDER)) { + final RequirementProvider provider = node.getAttributeNotNull(AttributeKey.REQUIREMENT_PROVIDER); + builder.chain("requires", InvokesMethod.StyleConfig.NEWLINE, provider.getRequirementExpression()); + } + + // Suggestions + if (node.argument() instanceof RequiredCommandArgument req && req.hasAttribute(AttributeKey.SUGGESTION_PROVIDER)) { + final SuggestionProvider provider = req.getAttributeNotNull(AttributeKey.SUGGESTION_PROVIDER); + builder.chain("suggests", InvokesMethod.StyleConfig.NEWLINE, provider.getSuggestionExpression()); + } + + final Executable executable = node.getEitherAttribute(AttributeKey.EXECUTABLE, AttributeKey.DEFAULT_EXECUTABLE); + if (executable != null) { + scopeLiteralAccess(() -> { + builder.chain("executes", InvokesMethod.StyleConfig.NEWLINE, getExecutesExpression(node, executable)); + }); + } + } + + private AsExpression getExecutesExpression(CommandNode node, Executable executable) { + final List statements = new ArrayList<>(validationStatements(executable)); + + if (node.parent() instanceof CommandNode parent && parent.getAttribute(AttributeKey.RECORD_ARGUMENTS) instanceof Parameterizable recordArguments) { + // Method defined inside a record class + final MethodInvocationBuilder builder = Builders.ctorInvocation(CodeTypeAdapter.from(executable.executesMethod().getEnclosed())); + if (recordArguments.parameterArguments().size() > 2) { + builder.setMultilineParameters(); + } + + recordArguments.parameterArguments().stream() + .map(CommandArgument.class::cast) + .forEach(arg -> builder.addParameter(getArgumentValueExpr(arg))); + + statements.add(createCallStatement(builder.getAsExpression(), executable)); + } else { + final PrintedAccessPath path = new PrintedAccessPath(accessStack); + statements.add(createCallStatement(path.getVariableAccess(), executable)); + requiredPaths.add(path); + } + + statements.add(CodeStatement.returnStatement(Builders.fieldAccess("SINGLE_SUCCESS").setStatic(Classes.COMMAND))); + return CodeExpression.lambda(List.of("ctx"), statements.toArray(AsStatement[]::new)); + } + + private AsStatement createCallStatement(AsExpression source, Executable executable) { + final MethodInvocationBuilder builder = Builders.methodInvocation(executable.executesMethod().getName()) + .setInstanceSource(source); + + executable.parameterArguments() + .forEach(arg -> builder.addParameter(switch (arg) { + case CommandArgument argument -> getArgumentValueExpr(argument); + case SourceParameterType(SourceVariable parameter) -> getParameterValueExpr(parameter); + })); + + return builder; + } + + private AsExpression getArgumentValueExpr(CommandArgument argument) { + return switch (argument) { + case RequiredCommandArgument required -> required.argumentType().retriever(); + case LiteralCommandArgument ignored -> CodeExpression.string(nextLiteral()); + case MultiLiteralCommandArgument ignored -> CodeExpression.string(nextLiteral()); + default -> throw new IllegalStateException("Unexpected argument type: " + argument.getClass().getName()); + }; + } + + protected abstract AsExpression getParameterValueExpr(SourceVariable parameter); + + protected void appendNode(MethodInvocationBuilder builder, CommandNode node) { + switch (node.argument()) { + case LiteralCommandArgument literal -> { + scopeLiteral(literal.literal(), () -> { + final MethodInvocationBuilder nested = literalBuilder(CodeExpression.string(literal.literal())); + fill(nested, node); + builder.chain("then", InvokesMethod.StyleConfig.NEWLINE_BOTH, nested); + }); + } + case RequiredCommandArgument required -> { + final MethodInvocationBuilder nested = argumentBuilder( + CodeExpression.string(required.argumentName()), + required.argumentType().initializer() + ); + fill(nested, node); + builder.chain("then", InvokesMethod.StyleConfig.NEWLINE_BOTH, nested); + } + case MultiLiteralCommandArgument multiLiteral -> { + for (String literal : multiLiteral.literals()) { + scopeLiteral(literal, () -> { + final MethodInvocationBuilder nested = literalBuilder(CodeExpression.string(literal)); + fill(nested, node); + builder.chain("then", InvokesMethod.StyleConfig.NEWLINE_BOTH, nested); + }); + } + } + default -> throw new IllegalArgumentException("Unknown argument class: " + node.argument().getClass().getName()); + } + } + + protected final String nextLiteral() { + return literalStack.get(literalPointer++); + } + + protected final void scopeAccessStack(CommandNode node, Runnable run) { + final Optional>> access = node.getAttributeOptional(AttributeKey.ACCESS_STACK); + final int numberOfPushes = access.map(List::size).orElse(0); + access.ifPresent(list -> list.forEach(accessStack::push)); + + run.run(); + + for (int i = 0; i < numberOfPushes; i++) { + accessStack.pop(); + } + } + + protected final void scopeLiteral(String literal, Runnable run) { + literalStack.push(literal); + run.run(); + literalStack.pop(); + } + + protected final void scopeLiteralAccess(Runnable run) { + final int pointerPosition = literalPointer; + run.run(); + literalPointer = pointerPosition; + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonClassBuilder.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonClassBuilder.java new file mode 100644 index 00000000..33f04c0c --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonClassBuilder.java @@ -0,0 +1,312 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.printer; + +import net.strokkur.commands.internal.BuildConstants; +import net.strokkur.commands.internal.abstraction.SourceConstructor; +import net.strokkur.commands.internal.abstraction.SourceParameter; +import net.strokkur.commands.internal.abstraction.SourceTypeAnnotation; +import net.strokkur.commands.internal.abstraction.SourceVariable; +import net.strokkur.commands.internal.codegen.CodeAnnotation; +import net.strokkur.commands.internal.codegen.CodeClass; +import net.strokkur.commands.internal.codegen.CodeExpression; +import net.strokkur.commands.internal.codegen.CodeMethod; +import net.strokkur.commands.internal.codegen.CodePackage; +import net.strokkur.commands.internal.codegen.CodeStatement; +import net.strokkur.commands.internal.codegen.CodeType; +import net.strokkur.commands.internal.codegen.Modifiers; +import net.strokkur.commands.internal.codegen.adapter.CodeTypeAdapter; +import net.strokkur.commands.internal.codegen.as.AsExpression; +import net.strokkur.commands.internal.codegen.builder.Builders; +import net.strokkur.commands.internal.codegen.builder.ClassBuilder; +import net.strokkur.commands.internal.codegen.builder.MethodBuilder; +import net.strokkur.commands.internal.codegen.builder.MethodInvocationBuilder; +import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc; +import net.strokkur.commands.internal.intermediate.tree.CommandNode; +import net.strokkur.commands.internal.printer.source.AbstractSourcePrintingVisitor; +import net.strokkur.commands.internal.printer.source.ImportGatheringVisitor; +import net.strokkur.commands.internal.util.Classes; +import net.strokkur.commands.internal.util.CommandInformation; +import net.strokkur.commands.internal.util.ConvertableTo; +import org.jetbrains.annotations.MustBeInvokedByOverriders; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public abstract class CommonClassBuilder { + private final CommandNode rootNode; + protected final C commandInformation; + private final CommonBrigadierStatementBuilder statementBuilder; + private final BiFunction, AbstractSourcePrintingVisitor> sourceVisitor; + + protected final CodeType.ClassType sourceType; + protected final CodeType.ClassType selfType; + + public CommonClassBuilder( + CommandNode rootNode, + C commandInformation, + CommonBrigadierStatementBuilder statementBuilder, + BiFunction, AbstractSourcePrintingVisitor> sourceVisitor + ) { + this.rootNode = rootNode; + this.commandInformation = commandInformation; + this.statementBuilder = statementBuilder; + this.sourceVisitor = sourceVisitor; + + this.sourceType = CodeType.ofClass(commandInformation.sourceClass().getFullyQualifiedName()); + this.selfType = CodeType.ofClass(sourceType.fullyQualifiedName() + "Brigadier"); + } + + /// Converts the passed root command node into a proper Java file. + public final String getAsString() { + final CodeClass brigadierClass = createClass(); + + final StringBuilder out = new StringBuilder(); + out.append("package ").append(selfType.codePackage().path()).append(";\n\n"); + + final Set imports = gatherAndAppendImports(out, brigadierClass); + out.append("\n"); + + final AbstractSourcePrintingVisitor visitor = sourceVisitor.apply(brigadierClass.codePackage(), imports); + out.append(brigadierClass.accept(visitor)); + return out.toString(); + } + + /// Creates the actual class, which will be printed to a file. + private CodeClass createClass() { + // Create skeletons for create and register methods for use in Javadocs. + final MethodBuilder createMethod = getCreateMethodBuilder(); + final MethodBuilder registerMethod = getRegisterMethodBuilder(); + + applyCreateMethodJavadoc(createMethod, registerMethod); + applyRegisterMethodJavadoc(registerMethod, createMethod); + + // Start building up the actual class + final ClassBuilder classBuilder = Builders.classBuilder(selfType.fullyQualifiedName()); + classBuilder.setJavadoc(getClassJavadoc(createMethod, registerMethod)); + classBuilder.setModifiers(Modifiers.PUBLIC, Modifiers.FINAL); + classBuilder.addAnnotations(CodeAnnotation.NULL_MARKED); + + populateStaticFields(classBuilder); + + // Run the brigadier tree builder so we can use the statements + statementBuilder.reset(); + final AsExpression treeExpr = statementBuilder.build(rootNode, CodeExpression.variable("NAME")); + + final List required = statementBuilder.requiredPaths.stream() + .map(PrintedAccessPath::requiredParent) + .distinct() + .sorted(Comparator.comparing(PrintedAccessPath::name)) + .toList(); + if (commandInformation.useInjection()) { + // Use injection for the required fields + required.forEach(path -> { + classBuilder.addField(Builders.field(path.name(), path.access().getLast().getAsCodeType()) + .setModifiers(Modifiers.PRIVATE) + .addAnnotation(CodeAnnotation.INJECT) + ); + }); + } else { + // We are not using injection, so instead create the instances inside the create method + required.forEach(path -> { + createMethod.addMethodStatements(CodeStatement.variableDeclarationFinal( + path.access().getLast(), + path.name(), + createInstanceConstructor((CodeType.ClassType) path.access().getLast().getAsCodeType()) + )); + }); + if (!required.isEmpty()) { + createMethod.addMethodStatements(CodeStatement.blank()); + } + } + + createMethod.addMethodStatements(CodeStatement.returnStatement(treeExpr)); + + // Add the methods to the class + classBuilder.addMethod(registerMethod); + classBuilder.addMethod(createMethod); + + // If the class is not injectable, the ctor should be private + if (!commandInformation.useInjection()) { + classBuilder.addMethod(Builders.method(selfType.codeClass()) + .setJavadoc(CodeJavadoc.combineLines( + CodeJavadoc.text("The constructor is not accessible. There is no need for an instance"), + CodeJavadoc.text("to be created, as no state is stored and all methods are static."), + CodeJavadoc.blank(), + CodeJavadoc.throwsMeta(Classes.ILLEGAL_ACCESS_EXCEPTION, "always") + )) + .setThrowsExceptions(Classes.ILLEGAL_ACCESS_EXCEPTION) + .setMethodStatements(CodeStatement.throwStatement( + Builders.ctorInvocation(Classes.ILLEGAL_ACCESS_EXCEPTION).addParameter(CodeExpression.string( + "This class cannot be instantiated." + )) + )) + ); + } + + return classBuilder.build(); + } + + private void addSourceConstructorParameters(MethodInvocationBuilder builder) { + if (commandInformation.useInjection()) { + // Don't add constructor parameters + return; + } + + if (commandInformation.constructor() instanceof SourceConstructor sourceCtor) { + for (SourceParameter parameter : sourceCtor.getParameters()) { + builder.addParameter(CodeExpression.variable(parameter.getName())); + } + } + } + + protected AsExpression createInstanceConstructor(CodeType.ClassType classType) { + final MethodInvocationBuilder ctor = Builders.ctorInvocation(classType); + if (sourceType.equals(classType)) { + addSourceConstructorParameters(ctor); + } + return ctor; + } + + /// The transmutation logic for the top-level constructor call, intended + /// for using existing instances (i.e., a Server instance) multiple times + /// to save on duplicate parameters in the create/register methods. + protected AsExpression transmuteConstructorParameter(SourceVariable parameter) { + return CodeExpression.variable(parameter.getName()); + } + + protected void addConstructorParametersTo(MethodBuilder builder, Predicate filter) { + if (!commandInformation.useInjection() && commandInformation.constructor() instanceof SourceConstructor ctor) { + for (SourceTypeAnnotation typeAnnotation : ctor.getTypeAnnotations()) { + builder.addGeneric(CodeType.generic(typeAnnotation.getName(), typeAnnotation.getDefinitionString())); + } + + for (SourceParameter parameter : ctor.getParameters()) { + if (filter.test(parameter)) { + builder.addParameter(CodeTypeAdapter.from(parameter.getType()), parameter.getName()); + } + } + } + } + + /// Creates the builder for the create method. + /// + /// @apiNote this method should always be overridden. Overriders should implement the create method logic now. + @MustBeInvokedByOverriders + protected MethodBuilder getCreateMethodBuilder() { + final MethodBuilder builder = Builders.method("create"); + builder.setModifiers(Modifiers.PUBLIC); + if (!commandInformation.useInjection()) { + builder.addModifiers(Modifiers.STATIC); + } + + // Propagate constructor parameters + addConstructorParametersTo(builder, f -> true); + + return builder; + } + + /// Creates the builder for the register method. + /// + /// @apiNote this method should always be overridden. Overrides should **not** implement any logic at this point. + @MustBeInvokedByOverriders + protected MethodBuilder getRegisterMethodBuilder() { + final MethodBuilder builder = Builders.method("register"); + builder.setModifiers(Modifiers.PUBLIC); + if (!commandInformation.useInjection()) { + builder.addModifiers(Modifiers.STATIC); + } + return builder; + } + + /// Populates the class with static fields, intended to hold information supplied from the + /// source class, such as the command name, command description, and aliases. + @MustBeInvokedByOverriders + protected void populateStaticFields(ClassBuilder builder) { + builder.addField(Builders.field("NAME", CodeType.STRING) + .setModifiers(Modifiers.PUBLIC, Modifiers.STATIC, Modifiers.FINAL) + .setInitialiser(CodeExpression.string(rootNode.argument().argumentName())) // The name of the command + ); + } + + /// Sets the Javadoc for the create method. + protected void applyCreateMethodJavadoc(MethodBuilder createMethod, ConvertableTo registerMethod) { + createMethod.setJavadoc(CodeJavadoc.combineLines( + CodeJavadoc.text("A method for creating a Brigadier command node which denotes the declared command"), + CodeJavadoc.combine( + CodeJavadoc.text("in "), + CodeJavadoc.classReference(sourceType.codeClass()), + CodeJavadoc.text(". You can either retrieve the unregistered node with this method")), + CodeJavadoc.combine( + CodeJavadoc.text("or register it directly with "), + CodeJavadoc.methodReference(registerMethod, true), + CodeJavadoc.text(".")) + )); + } + + /// Sets the Javadoc for the register method. Not directly implemented due to platform-dependent differences + /// in command registration. + protected abstract void applyRegisterMethodJavadoc(MethodBuilder registerMethod, ConvertableTo createMethod); + + /// Gets the Javadoc for the class file, cannot currently be overriden. + private CodeJavadoc getClassJavadoc(ConvertableTo createMethod, ConvertableTo registerMethod) { + return CodeJavadoc.combineLines( + CodeJavadoc.text("A class holding the Brigadier source tree generated from"), + CodeJavadoc.combine( + CodeJavadoc.classReference(CodeClass.simple(commandInformation.sourceClass().getFullyQualifiedName())), + CodeJavadoc.text(" using "), + CodeJavadoc.url("StrokkCommands", "https://commands.strokkur.net") + ), + CodeJavadoc.blank(), + CodeJavadoc.author("Strokkur24 - StrokkCommands"), + CodeJavadoc.version(BuildConstants.VERSION), + CodeJavadoc.see(createMethod, "creating the command", true), + CodeJavadoc.see(registerMethod, "registering the command", true) + ); + } + + /// This method constructs an import-gathering visitor, gathers all imports from the class, + /// and finally splits them by Java and non-Java imports, sorts them, and appends them to the string builder. + /// This mimics IntelliJ IDEA's own import format behavior. + /// + /// @return all collected imports + private Set gatherAndAppendImports(StringBuilder builder, CodeClass brigadierClass) { + final ImportGatheringVisitor importVisitor = new ImportGatheringVisitor(); + final Set imports = importVisitor.collectFilteredImports(brigadierClass); + + final Map> split = imports.stream() + .collect(Collectors.partitioningBy(type -> type.codePackage().path().startsWith("java"))); + + split.get(false).stream().sorted() + .forEach(type -> builder.append("import ").append(type.fullyQualifiedName()).append(";\n")); + + if (!split.get(false).isEmpty() && !split.get(true).isEmpty()) { + builder.append("\n"); + } + + split.get(true).stream().sorted() + .forEach(type -> builder.append("import ").append(type.fullyQualifiedName()).append(";\n")); + + return imports; + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonCommandTreePrinter.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonCommandTreePrinter.java deleted file mode 100644 index db486f89..00000000 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonCommandTreePrinter.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * StrokkCommands - A super simple annotation based zero-shade Paper command API library. - * Copyright (C) 2025 Strokkur24 - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see . - */ -package net.strokkur.commands.internal.printer; - -import net.strokkur.commands.internal.BuildConstants; -import net.strokkur.commands.internal.PlatformUtils; -import net.strokkur.commands.internal.abstraction.SourceMethod; -import net.strokkur.commands.internal.intermediate.access.ExecuteAccess; -import net.strokkur.commands.internal.intermediate.attributes.AttributeKey; -import net.strokkur.commands.internal.intermediate.registrable.ExecutorWrapperProvider; -import net.strokkur.commands.internal.intermediate.tree.CommandNode; -import net.strokkur.commands.internal.util.CommandInformation; -import net.strokkur.commands.internal.util.PrintParamsHolder; -import org.jetbrains.annotations.MustBeInvokedByOverriders; -import org.jspecify.annotations.Nullable; - -import javax.annotation.processing.ProcessingEnvironment; -import java.io.IOException; -import java.io.Writer; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.Stack; -import java.util.TreeSet; - -public abstract class CommonCommandTreePrinter extends AbstractPrinter { - private final Stack> executeAccessStack = new Stack<>(); - protected final CommandNode node; - private final Set printedInstances = new TreeSet<>(); - private final ProcessingEnvironment environment; - protected final PlatformUtils utils; - - private final CommonImportPrinter importPrinter; - private final CommonTreePrinter treePrinter; - private final CommonInstanceFieldPrinter instanceFieldPrinter; - - private @Nullable ExecutorWrapperProvider executorWrapper = null; - private Stack> executorWrapperAccessStack = new Stack<>(); - - private final C commandInformation; - - public CommonCommandTreePrinter( - int indent, - @Nullable Writer writer, - CommandNode node, - C commandInformation, - ProcessingEnvironment environment, - PlatformUtils utils - ) { - super(indent, writer); - this.node = node; - this.commandInformation = commandInformation; - this.environment = environment; - this.utils = utils; - - this.importPrinter = createImportPrinter(); - this.treePrinter = createTreePrinter(); - this.instanceFieldPrinter = createInstanceFieldPrinter(); - } - - protected abstract CommonImportPrinter createImportPrinter(); - - protected abstract CommonTreePrinter createTreePrinter(); - - protected CommonInstanceFieldPrinter createInstanceFieldPrinter() { - return new CommonInstanceFieldPrinter(this); - } - - public final String getPackageName() { - return commandInformation.sourceClass().getPackageName(); - } - - public final String getBrigadierClassName() { - return commandInformation.sourceClass().getName() + "Brigadier"; - } - - protected abstract PrintParamsHolder getParamsHolder(); - - protected abstract void printRegisterMethod(PrintParamsHolder holder) throws IOException; - - public @Nullable ExecutorWrapperProvider getExecutorWrapper() { - return this.executorWrapper; - } - - public Stack> getExecutorWrapperAccessStack() { - return this.executorWrapperAccessStack; - } - - public void updateExecutorWrapper(@Nullable ExecutorWrapperProvider provider) { - this.executorWrapper = provider; - this.executorWrapperAccessStack = new Stack<>(); - this.executorWrapperAccessStack.addAll(this.getAccessStack()); - } - - protected void printSemicolon() throws IOException { - print(";"); - } - - public void print() throws IOException { - final String packageName = getPackageName(); - final PrintParamsHolder printParams = getParamsHolder(); - - println("package {};", packageName); - println(); - importPrinter.printImports(importPrinter.getImports()); - println(); - - printBlock(""" - /** - * A class holding the Brigadier source tree generated from - * {@link %s} using StrokkCommands. - * - * @author Strokkur24 - StrokkCommands - * @version %s - * @see #create(%s) creating the %s - * @see #register(%s) registering the command - */ - @NullMarked""", - getCommandInformation().sourceClass().getName(), - BuildConstants.VERSION, - printParams.createJdParams(), - utils.getNodeReturnType(), - printParams.registerJdParams() - ); - - println("public final class {} {", getBrigadierClassName()); - incrementIndent(); - printExtraClassStart(); - println(); - - if (commandInformation.useInjection()) { - instanceFieldPrinter.printInjectedFields(); - } - - printRegisterMethod(printParams); - - println(); - - printBlock(""" - /** - * A method for creating a Brigadier command node which denotes the declared command - * in {@link %s}. You can either retrieve the unregistered node with this method - * or register it directly with {@link #register(%s)}. - */""", - getCommandInformation().sourceClass().getName(), - commandInformation.useInjection() ? "Commands" : printParams.registerJdParams() - ); - - final String typeAnnotations = Optional.ofNullable(getCommandInformation().constructor()) - .map(SourceMethod::getCombinedTypeAnnotationsString) - .orElse(""); - if (commandInformation.useInjection()) { - println("public%s %s<%s> create() {", - typeAnnotations, - utils.getNodeReturnType(), - List.of(utils.platformType().split("\\.")).getLast() - ); - } else { - println("public static%s %s<%s> create(%s) {", - typeAnnotations, - utils.getNodeReturnType(), - List.of(utils.platformType().split("\\.")).getLast(), - printParams.createParams() - ); - } - - incrementIndent(); - - instanceFieldPrinter.printInstanceFields(); - - printIndent(); - print("return "); - incrementIndent(); - treePrinter.printNode(node); - printSemicolon(); - - println(); - decrementIndent(); - decrementIndent(); - println("}"); - - printReflectionHelper(node); - - if (!commandInformation.useInjection()) { - println(); - printBlock(""" - /** - * The constructor is not accessible. There is no need for an instance - * to be created, as no state is stored and all methods are static. - * - * @throws IllegalAccessException always - */ - private %s() throws IllegalAccessException { - throw new IllegalAccessException("This class cannot be instantiated."); - }""", - getBrigadierClassName()); - } - decrementIndent(); - println("}"); - } - - @MustBeInvokedByOverriders - protected void printExtraClassStart() throws IOException { - println("public static final String NAME = \"%s\";", node.argument().argumentName()); - } - - private boolean printReflectionHelper(CommandNode node) throws IOException { - if (Optional.ofNullable(node.getAttribute(AttributeKey.EXECUTOR_WRAPPER)) - .map(ExecutorWrapperProvider::wrapperType) - .map(ExecutorWrapperProvider.WrapperType::withMethod) - .orElse(false)) { - println(); - printBlock(""" - private static Method getMethodViaReflection(final Class clazz, final String name, final Class... parameters) { - try { - return clazz.getDeclaredMethod(name, parameters); - } catch (ReflectiveOperationException ex) { - throw new RuntimeException(ex); - } - }"""); - return true; - } - - for (CommandNode child : node.children()) { - if (printReflectionHelper(child)) { - return true; - } - } - return false; - } - - public final ProcessingEnvironment environment() { - return this.environment; - } - - public final Set getPrintedInstances() { - return printedInstances; - } - - public final Stack> getAccessStack() { - return executeAccessStack; - } - - public final CommandNode getNode() { - return node; - } - - public final C getCommandInformation() { - return this.commandInformation; - } -} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonImportPrinter.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonImportPrinter.java deleted file mode 100644 index 3010f75d..00000000 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonImportPrinter.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * StrokkCommands - A super simple annotation based zero-shade Paper command API library. - * Copyright (C) 2025 Strokkur24 - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see . - */ -package net.strokkur.commands.internal.printer; - -import net.strokkur.commands.internal.abstraction.SourceConstructor; -import net.strokkur.commands.internal.abstraction.SourceTypeAnnotation; -import net.strokkur.commands.internal.arguments.RequiredCommandArgument; -import net.strokkur.commands.internal.intermediate.access.ExecuteAccess; -import net.strokkur.commands.internal.intermediate.access.FieldAccess; -import net.strokkur.commands.internal.intermediate.access.InstanceAccess; -import net.strokkur.commands.internal.intermediate.attributes.AttributeKey; -import net.strokkur.commands.internal.intermediate.executable.DefaultExecutable; -import net.strokkur.commands.internal.intermediate.executable.Executable; -import net.strokkur.commands.internal.intermediate.executable.SourceParameterType; -import net.strokkur.commands.internal.intermediate.registrable.ExecutorWrapperProvider; -import net.strokkur.commands.internal.intermediate.registrable.RequirementProvider; -import net.strokkur.commands.internal.intermediate.registrable.SuggestionProvider; -import net.strokkur.commands.internal.intermediate.tree.CommandNode; -import net.strokkur.commands.internal.util.Classes; - -import java.io.IOException; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -public abstract class CommonImportPrinter { - protected final CommonCommandTreePrinter printer; - - public CommonImportPrinter(CommonCommandTreePrinter printer) { - this.printer = printer; - } - - protected abstract Set standardImports(); - - void printImports(Set imports) throws IOException { - final Map> splitImports = imports.stream() - .sorted() - .collect(Collectors.partitioningBy(str -> str.startsWith("java"))); - - final List javaImports = splitImports.get(true); - final List otherImports = splitImports.get(false); - - for (String i : otherImports) { - printer.println("import {};", i); - } - - if (!javaImports.isEmpty()) { - printer.println(); - - for (String i : javaImports) { - printer.println("import {};", i); - } - } - } - - public Set getImports() { - final Set imports = new HashSet<>(standardImports()); - gatherImports(imports, printer.getNode()); - - if (printer.getCommandInformation().useInjection()) { - imports.add(Classes.INJECT); - } - - final String sourceClassFqn = printer.getCommandInformation().sourceClass().getFullyQualifiedName(); - final int numberOfDots = sourceClassFqn.split("\\.").length; - imports.removeIf(importString -> { - // Don't remove imports from subpackages of java.lang - if (importString.startsWith("java.lang") && importString.split("\\.").length > 3) { - return false; - } - if (importString.startsWith("java.lang")) { - return true; - } - - if (!importString.startsWith(sourceClassFqn)) { - return false; - } - - return importString.split("\\.").length == numberOfDots; - }); - - return imports; - } - - protected void gatherAdditionalArgumentImports(Set imports, RequiredCommandArgument argument) { - // noop - } - - protected void gatherAdditionalNodeImports(Set imports, CommandNode node) { - // noop - } - - private void gatherImports(Set imports, CommandNode node) { - if (node.hasAttribute(AttributeKey.DEFAULT_EXECUTABLE)) { - final Executable exec = node.getAttributeNotNull(AttributeKey.DEFAULT_EXECUTABLE); - exec.parameterArguments().stream() - .filter(SourceParameterType.class::isInstance) - .map(SourceParameterType.class::cast) - .map(SourceParameterType::parameter) - .filter(type -> type.getType().getFullyQualifiedAndTypedName().equalsIgnoreCase(Classes.LIST_STRING) - || type.getType().getFullyQualifiedName().equalsIgnoreCase("java.lang.String") && type.getType().isArray()) - .forEach(type -> imports.addAll(DefaultExecutable.Type.getType(type).getImports())); - } - - final ExecutorWrapperProvider currentWrapper = printer.getExecutorWrapper(); - if (node.hasAttribute(AttributeKey.EXECUTOR_WRAPPER)) { - final ExecutorWrapperProvider wrapper = node.getAttributeNotNull(AttributeKey.EXECUTOR_WRAPPER); - printer.updateExecutorWrapper(wrapper); - if (wrapper.wrapperType().withMethod()) { - imports.add(Classes.METHOD); - } - } - - final Executable executable = node.getEitherAttribute(AttributeKey.EXECUTABLE, AttributeKey.DEFAULT_EXECUTABLE); - if (executable != null) { - executable.executesMethod().getParameters().forEach(param -> imports.addAll(param.getImports())); - } - - if (printer.getCommandInformation().constructor() instanceof SourceConstructor ctor) { - imports.addAll(ctor.getImports()); - } - for (SourceTypeAnnotation typeParameter : printer.getCommandInformation().sourceClass().getTypeAnnotations()) { - imports.addAll(typeParameter.getImports()); - } - - if (node.hasAttribute(AttributeKey.ACCESS_STACK)) { - for (ExecuteAccess access : node.getAttributeNotNull(AttributeKey.ACCESS_STACK)) { - if (access instanceof InstanceAccess instanceAccess) { - imports.addAll(instanceAccess.getElement().getImports()); - } else if (access instanceof FieldAccess fieldAccess) { - imports.addAll(fieldAccess.getElement().getImports()); - } - } - } - - if (node.argument() instanceof RequiredCommandArgument req) { - imports.addAll(req.argumentType().imports()); - - final SuggestionProvider suggestionProvider = req.getAttribute(AttributeKey.SUGGESTION_PROVIDER); - if (suggestionProvider != null) { - imports.addAll(suggestionProvider.getSourceClass().getImports()); - } - - final RequirementProvider requirementProvider = req.getAttribute(AttributeKey.REQUIREMENT_PROVIDER); - if (requirementProvider != null) { - imports.addAll(requirementProvider.getSourceClasses().stream() - .flatMap(source -> source.getImports().stream()) - .toList()); - } - - gatherAdditionalArgumentImports(imports, req); - } - - gatherAdditionalNodeImports(imports, node); - - for (CommandNode child : node.children()) { - gatherImports(imports, child); - } - - if (node.hasAttribute(AttributeKey.EXECUTOR_WRAPPER)) { - printer.updateExecutorWrapper(currentWrapper); - } - } -} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonInstanceFieldPrinter.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonInstanceFieldPrinter.java deleted file mode 100644 index bf161c26..00000000 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonInstanceFieldPrinter.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * StrokkCommands - A super simple annotation based zero-shade Paper command API library. - * Copyright (C) 2025 Strokkur24 - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see . - */ -package net.strokkur.commands.internal.printer; - -import net.strokkur.commands.internal.abstraction.SourceClass; -import net.strokkur.commands.internal.abstraction.SourceConstructor; -import net.strokkur.commands.internal.abstraction.SourceField; -import net.strokkur.commands.internal.abstraction.SourceParameter; -import net.strokkur.commands.internal.abstraction.SourceTypeAnnotation; -import net.strokkur.commands.internal.intermediate.access.ExecuteAccess; -import net.strokkur.commands.internal.intermediate.access.FieldAccess; -import net.strokkur.commands.internal.intermediate.access.InstanceAccess; -import net.strokkur.commands.internal.intermediate.attributes.AttributeKey; -import net.strokkur.commands.internal.intermediate.executable.DefaultExecutable; -import net.strokkur.commands.internal.intermediate.executable.Executable; -import net.strokkur.commands.internal.intermediate.tree.CommandNode; -import net.strokkur.commands.internal.util.Utils; - -import javax.lang.model.element.Modifier; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -public class CommonInstanceFieldPrinter { - private final CommonCommandTreePrinter printer; - private final Set handledDefaults = new HashSet<>(); - - public CommonInstanceFieldPrinter(CommonCommandTreePrinter printer) { - this.printer = printer; - } - - public void printInstanceFields() throws IOException { - if (printInstanceFields(printer.getNode()) > 0) { - printer.println(); // Extra newline for styling reasons - } - } - - public void printInjectedFields() throws IOException { - if (printInjectedFields(printer.getNode()) > 0) { - printer.println(); - } - } - - private List collectAccessEntries(CommandNode node) { - final List out = new ArrayList<>(); - - int pushed = 0; - if (node.hasAttribute(AttributeKey.ACCESS_STACK)) { - for (ExecuteAccess executeAccess : node.getAttributeNotNull(AttributeKey.ACCESS_STACK)) { - if (executeAccess.isRecord()) { - for (int i = 0; i < pushed; i++) { - printer.getAccessStack().pop(); - } - return out; - } - - printer.getAccessStack().push(executeAccess); - pushed++; - } - } - - final Executable exec = node.getEitherAttribute(AttributeKey.EXECUTABLE, AttributeKey.DEFAULT_EXECUTABLE); - EXEC_NOT_NULL: - if (exec != null) { - final List> pathToUse = printer.getAccessStack(); - - if (exec instanceof DefaultExecutable defaultExec) { - if (handledDefaults.contains(defaultExec)) { - break EXEC_NOT_NULL; - } - handledDefaults.add(defaultExec); - } - - if (!pathToUse.isEmpty()) { - out.add(new AccessEntry(pathToUse)); - } - } - - for (CommandNode child : node.children()) { - out.addAll(collectAccessEntries(child)); - } - - for (int i = 0; i < pushed; i++) { - printer.getAccessStack().pop(); - } - - return out; - } - - private int printInstanceFields(CommandNode node) throws IOException { - int printed = 0; - for (AccessEntry collectAccessEntry : collectAccessEntries(node)) { - printed += printAccessInstance(collectAccessEntry); - } - return printed; - } - - private int printInjectedFields(CommandNode node) throws IOException { - int printed = 0; - for (AccessEntry collectAccessEntry : collectAccessEntries(node)) { - printed += printInjectedField(collectAccessEntry); - } - return printed; - } - - public String getParameterName(SourceParameter parameter) { - return parameter.getName(); - } - - private int printAccessInstance(AccessEntry entry) throws IOException { - if (printer.getPrintedInstances().contains(entry.getAccessName())) { - return 0; - } - - if (entry.isTopLevel()) { - printer.println("final %s%s instance = new %s%s(%s);", - entry.getTypeName(), getCtorTypeParameters(), - entry.getTypeName(), hasCtorTypeParameters() ? "<>" : "", - getCtorParameters() - ); - printer.getPrintedInstances().add("instance"); - return 1; - } - - int printed = 1; - if (entry.getCurrentAccess() instanceof FieldAccess fieldAccess) { - printed += printAccessInstance(entry.getParent()); - - final SourceField fieldElement = fieldAccess.getElement(); - if (fieldElement.isInitialized()) { - printer.println("final {} {} = {}.{};", - entry.getTypeName(), entry.getAccessName(), - entry.getParent().getAccessName(), fieldAccess.getElement().getName() - ); - } else { - printer.println("final {} {} = new {}();", - entry.getTypeName(), entry.getAccessName(), - entry.getTypeName() - ); - } - - printer.getPrintedInstances().add(entry.getAccessName()); - return printed; - } - - if (entry.getCurrentAccess() instanceof InstanceAccess instanceAccess) { - final SourceClass classElement = instanceAccess.getElement(); - if (classElement.isTopLevel() || classElement.getModifiers().contains(Modifier.STATIC)) { - printer.println("final {} {} = new {}();", - entry.getTypeName(), entry.getAccessName(), - entry.getTypeName() - ); - } else { - printed += printAccessInstance(entry.getParent()); - printer.println("final {} {} = {}.new {}();", - entry.getTypeName(), entry.getAccessName(), - entry.getParent().getAccessName(), classElement.getName() - ); - } - - printer.getPrintedInstances().add(entry.getAccessName()); - return printed; - } - - throw new IllegalStateException("Unknown access type: " + entry.getCurrentAccess()); - } - - private int printInjectedField(AccessEntry entry) throws IOException { - if (printer.getPrintedInstances().contains(entry.getAccessName())) { - return 0; - } - - if (entry.needsToBeInitialized()) { - printer.println("private @Inject {} {};", - entry.getTypeName(), entry.getAccessName() - ); - printer.getPrintedInstances().add(entry.getAccessName()); - return 1; - } - return 0; - } - - private boolean hasCtorTypeParameters() { - return !printer.getCommandInformation().sourceClass().getTypeAnnotations().isEmpty(); - } - - private String getCtorTypeParameters() { - return hasCtorTypeParameters() ? - '<' + String.join(", ", printer.getCommandInformation().sourceClass().getTypeAnnotations().stream() - .map(SourceTypeAnnotation::getName) - .toList()) + '>' : - ""; - } - - private String getCtorParameters() { - return String.join(", ", printer.getCommandInformation().constructor() instanceof SourceConstructor ctor ? - ctor.getParameters().stream() - .map(this::getParameterName) - .toList() : - Collections.emptyList() - ); - } - - protected record AccessEntry(List> access) { - protected AccessEntry(List> access) { - if (access.isEmpty()) { - throw new IllegalStateException("Access stack cannot be empty."); - } - this.access = List.copyOf(access); - } - - public boolean isTopLevel() { - return access.size() == 1; - } - - public String getAccessName() { - if (isTopLevel()) { - return "instance"; - } - - return Utils.getInstanceName(access); - } - - public AccessEntry getParent() { - return new AccessEntry(access.subList(0, access.size() - 1)); - } - - private ExecuteAccess getCurrentAccess() { - return access.getLast(); - } - - public String getTypeName() { - return getCurrentAccess().getSourceName(); - } - - public boolean needsToBeInitialized() { - final boolean alreadyInitialized = getCurrentAccess() instanceof FieldAccess field && field.getElement().isInitialized(); - return !alreadyInitialized; - } - } -} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonTreePrinter.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonTreePrinter.java deleted file mode 100644 index 65bb9d43..00000000 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonTreePrinter.java +++ /dev/null @@ -1,367 +0,0 @@ -/* - * StrokkCommands - A super simple annotation based zero-shade Paper command API library. - * Copyright (C) 2025 Strokkur24 - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see . - */ -package net.strokkur.commands.internal.printer; - -import net.strokkur.commands.internal.abstraction.SourceClass; -import net.strokkur.commands.internal.abstraction.SourceMethod; -import net.strokkur.commands.internal.abstraction.SourceParameter; -import net.strokkur.commands.internal.abstraction.SourceType; -import net.strokkur.commands.internal.abstraction.SourceVariable; -import net.strokkur.commands.internal.arguments.CommandArgument; -import net.strokkur.commands.internal.arguments.LiteralCommandArgument; -import net.strokkur.commands.internal.arguments.MultiLiteralCommandArgument; -import net.strokkur.commands.internal.arguments.RequiredCommandArgument; -import net.strokkur.commands.internal.intermediate.attributes.Attributable; -import net.strokkur.commands.internal.intermediate.attributes.AttributeKey; -import net.strokkur.commands.internal.intermediate.executable.DefaultExecutable; -import net.strokkur.commands.internal.intermediate.executable.Executable; -import net.strokkur.commands.internal.intermediate.executable.ParameterType; -import net.strokkur.commands.internal.intermediate.executable.Parameterizable; -import net.strokkur.commands.internal.intermediate.executable.SourceParameterType; -import net.strokkur.commands.internal.intermediate.registrable.ExecutorWrapperProvider; -import net.strokkur.commands.internal.intermediate.registrable.RequirementProvider; -import net.strokkur.commands.internal.intermediate.registrable.SuggestionProvider; -import net.strokkur.commands.internal.intermediate.tree.CommandNode; -import net.strokkur.commands.internal.util.IOExceptionIgnoringConsumer; -import net.strokkur.commands.internal.util.Utils; -import org.jspecify.annotations.Nullable; - -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Stack; - -public abstract class CommonTreePrinter { - protected final CommonCommandTreePrinter printer; - private final Stack multiLiteralStack = new Stack<>(); - - private final Map stackAtDefaultExecutes = new HashMap<>(); - - private int multiLiteralStackPosition = 0; - - public CommonTreePrinter(CommonCommandTreePrinter printer) { - this.printer = printer; - } - - public final String nextLiteral() { - return this.multiLiteralStack.elementAt(this.multiLiteralStackPosition++); - } - - public final void pushLiteral(String literal) { - this.multiLiteralStack.push(literal); - } - - public final void popLiteral() { - this.multiLiteralStack.pop(); - } - - public final void popLiteralPosition() { - this.multiLiteralStackPosition--; - } - - protected abstract void prefixPrintExecutableInner(CommandNode node, Executable executable) throws IOException; - - protected abstract String handleParameter(SourceVariable parameter) throws IOException; - - public void printNode(CommandNode node) throws IOException { - printNode(node, false); - } - - private void printNode(CommandNode root, boolean isNested) throws IOException { - printForArguments(root, initializer -> { - if (isNested) { - printer.println(); - printer.printIndented(".then("); - printer.incrementIndent(); - } - - printer.print(initializer); - - if (root.argument() instanceof RequiredCommandArgument req && req.hasAttribute(AttributeKey.SUGGESTION_PROVIDER)) { - final SuggestionProvider provider = req.getAttributeNotNull(AttributeKey.SUGGESTION_PROVIDER); - printer.println(); - printer.printIndented(".suggests(" + provider.getSuggestionString() + ")"); - } - - final RequirementProvider requirementProvider = root.getAttribute(AttributeKey.REQUIREMENT_PROVIDER); - final String extraRequirement = getExtraRequirements(root); - - if (requirementProvider != null && extraRequirement == null) { - printer.println(); - printer.printIndented(".requires(source -> %s)", requirementProvider.getRequirementString()); - } else if (requirementProvider == null && extraRequirement != null) { - printer.println(); - printer.printIndented(".requires(source -> %s)", extraRequirement); - } else if (requirementProvider != null) { - printer.println(); - printer.printIndented(".requires(source -> %s && %s)", requirementProvider.getRequirementString(), extraRequirement); - } - - final Executable executable = root.getEitherAttribute(AttributeKey.EXECUTABLE, AttributeKey.DEFAULT_EXECUTABLE); - if (executable != null) { - printExecutableInner(root, executable); - } - - for (CommandNode node : root.children()) { - printNode(node, true); - } - - if (isNested) { - printer.decrementIndent(); - printer.println(); - printer.printIndented(")"); - } - }, isNested); - } - - private void printExecutableInner(CommandNode node, Executable executable) throws IOException { - printer.println(); - - final ExecutorWrapperProvider wrapper = node.getAttributeOr(AttributeKey.EXECUTOR_WRAPPER, printer.getExecutorWrapper()); - if (wrapper == null) { - printExecutableInnerNoWrapper(node, executable); - } else { - printExecutableInnerWithWrapper(node, executable, wrapper); - } - } - - private void printExecutableInnerNoWrapper(CommandNode node, Executable executable) throws IOException { - printer.println(".executes(ctx -> {"); - printer.incrementIndent(); - prefixPrintExecutableInner(node, executable); - printExecutableBody(node, executable); - printer.println("return Command.SINGLE_SUCCESS;"); - printer.decrementIndent(); - printer.printIndented("})"); - } - - private void printExecutableInnerWithWrapper(CommandNode node, Executable executable, ExecutorWrapperProvider wrapper) throws IOException { - printer.println(".executes(%s(ctx -> {", getWrapperMethodCall(wrapper)); - printer.incrementIndent(); - prefixPrintExecutableInner(node, executable); - printExecutableBody(node, executable); - printer.println("return Command.SINGLE_SUCCESS;"); - printer.decrementIndent(); - - if (wrapper.wrapperType().withMethod()) { - final List params = executable.executesMethod().getParameters(); - final String parameterTypesString = params.isEmpty() ? "" : ", " + String.join(", ", executable.executesMethod().getParameters().stream() - .map(SourceParameter::getType) - .map(SourceType::getSourceName) - .map(str -> str + ".class") - .toList()); - - printer.printIndented("}, getMethodViaReflection(%s.class, \"%s\"%s)))", - executable.executesMethod().getEnclosed().getSourceName(), - executable.executesMethod().getName(), - parameterTypesString - ); - } else { - printer.printIndented("}))"); - } - } - - private String getWrapperMethodCall(ExecutorWrapperProvider wrapper) { - final SourceMethod method = wrapper.wrapperMethod(); - final String methodName = method.getName(); - - if (wrapper.wrapperMethod().isStaticallyAccessible()) { - // Static method: WrapperClass.wrapperMethod - final SourceClass wrapperClass = wrapper.wrapperMethod().getEnclosed(); - return wrapperClass.getSourceName() + "." + methodName; - } else { - // Instance method: get the appropriate instance variable name - return Utils.getInstanceName(printer.getExecutorWrapperAccessStack()) + "." + methodName; - } - } - - private void printExecutableBody(CommandNode node, Executable executable) throws IOException { - boolean instancePrint = true; - CommandNode recordNode = node; - - do { - final Parameterizable recordArguments = recordNode.getAttribute(AttributeKey.RECORD_ARGUMENTS); - if (recordArguments != null) { - printWithRecord(recordArguments, executable); - instancePrint = false; - break; - } - } while ((recordNode = recordNode.parent()) != null); - - if (instancePrint) { - printWithInstance(executable); - } - } - - private void printWithInstance(Executable executable) throws IOException { - final String instanceName; - if (executable instanceof DefaultExecutable defaultExec) { - instanceName = stackAtDefaultExecutes.computeIfAbsent(defaultExec, unused -> Utils.getInstanceName(printer.getAccessStack())); - } else { - instanceName = Utils.getInstanceName(printer.getAccessStack()); - } - printExecutesMethodCall(executable, instanceName); - } - - private void printExecutesMethodCall(Executable executable, String typeName) throws IOException { - printer.printIndented("{}.{}", typeName, executable.executesMethod().getName()); - printExecutesArguments(executable); - printer.print(";"); - printer.println(); - } - - private void printWithRecord(Parameterizable record, Executable executable) throws IOException { - final String typeName = executable.executesMethod().getEnclosed().getSourceName(); - - if (record.parameterArguments().isEmpty()) { - printer.println("final {} executorClass = new {}();", typeName, typeName); - } else { - printer.println("final {} executorClass = new {}(", typeName, typeName); - printer.incrementIndent(); - printArguments(record.parameterArguments().stream() - .map(CommandArgument.class::cast) - .toList()); - printer.decrementIndent(); - printer.println(");"); - } - - printExecutesMethodCall(executable, "executorClass"); - - for (ParameterType arg : record.parameterArguments()) { - if (arg instanceof MultiLiteralCommandArgument) { - popLiteralPosition(); - } - } - } - - private void printExecutesArguments(Executable executable) throws IOException { - final List parameterArguments = executable.parameterArguments(); - if (parameterArguments.isEmpty()) { - printer.print("()"); - return; - } - - if (parameterArguments.size() == 1) { - printer.print("("); - switch (parameterArguments.getFirst()) { - case CommandArgument argument -> printArgument(argument); - case SourceParameterType(SourceVariable parameter) -> printer.print(handleParameter(parameter)); - } - printer.print(")"); - return; - } - - printer.print("("); - printer.incrementIndent(); - printer.println(); - for (int i = 0, parameterArgumentsSize = parameterArguments.size(); i < parameterArgumentsSize; i++) { - final ParameterType parameterArgument = parameterArguments.get(i); - printer.printIndent(); - - switch (parameterArgument) { - case CommandArgument argument -> printArgument(argument); - case SourceParameterType(SourceVariable parameter) -> printer.print(handleParameter(parameter)); - } - - if (i + 1 < parameterArgumentsSize) { - printer.print(","); - printer.println(); - } - } - - for (ParameterType argument : parameterArguments) { - if (argument instanceof MultiLiteralCommandArgument) { - popLiteralPosition(); - } - } - - printer.decrementIndent(); - printer.println(); - printer.printIndented(")"); - } - - private void printArguments(List arguments) throws IOException { - for (int i = 0, argumentsSize = arguments.size(); i < argumentsSize; i++) { - printer.printIndent(); - printArgument(arguments.get(i)); - - if (i + 1 < argumentsSize) { - printer.print(","); - } - - printer.println(); - } - } - - private void printArgument(CommandArgument argument) throws IOException { - printer.print(switch (argument) { - case RequiredCommandArgument req -> req.argumentType().retriever(); - case MultiLiteralCommandArgument ignored -> '"' + nextLiteral() + '"'; - case LiteralCommandArgument lit -> '"' + lit.literal() + '"'; - default -> throw new IllegalArgumentException("Unknown argument class: " + argument.getClass()); - }); - } - - @Nullable - protected abstract String getExtraRequirements(Attributable node); - - protected abstract String getLiteralMethodString(); - - protected abstract String getArgumentMethodString(); - - public String getCommandNameLiteralOverride(LiteralCommandArgument lit) { - return "NAME"; - } - - private void printForArguments(CommandNode node, IOExceptionIgnoringConsumer initializer, boolean isNested) throws IOException { - if (node.hasAttribute(AttributeKey.ACCESS_STACK)) { - node.getAttributeNotNull(AttributeKey.ACCESS_STACK).forEach(printer.getAccessStack()::push); - } - - final ExecutorWrapperProvider current = printer.getExecutorWrapper(); - - // TODO: this bleeds over if a method has a wrapper declared and a method is present which starts with the same args and merges. - // However, this is a cheap fix for a problem barely anyone will run into anyways and which can be solved with just one - // UnsetExecutorWrapper annotation. Also it is 2:30 AM, cut me some slack. - final ExecutorWrapperProvider executorWrapper = node.getAttribute(AttributeKey.EXECUTOR_WRAPPER); - if (node.getAttributeNotNull(AttributeKey.EXECUTOR_WRAPPER_UNSET)) { - printer.updateExecutorWrapper(null); - } else if (executorWrapper != null) { - printer.updateExecutorWrapper(executorWrapper); - } - - switch (node.argument()) { - case LiteralCommandArgument lit -> initializer.accept("%s(%s)".formatted(getLiteralMethodString(), isNested ? '"' + lit.literal() + '"' : getCommandNameLiteralOverride(lit))); - case RequiredCommandArgument req -> initializer.accept("%s(\"%s\", %s)".formatted(getArgumentMethodString(), req.argumentName(), req.argumentType().initializer())); - case MultiLiteralCommandArgument multi -> { - for (String literal : multi.literals()) { - pushLiteral(literal); - initializer.accept("%s(\"%s\")".formatted(getLiteralMethodString(), literal)); - popLiteral(); - } - } - default -> throw new IllegalArgumentException("Unknown argument class: " + node.argument().getClass()); - } - - if (node.hasAttribute(AttributeKey.ACCESS_STACK)) { - node.getAttributeNotNull(AttributeKey.ACCESS_STACK).forEach(access -> printer.getAccessStack().pop()); - } - printer.updateExecutorWrapper(current); - } -} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/PrintedAccessPath.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/PrintedAccessPath.java new file mode 100644 index 00000000..e83076ca --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/PrintedAccessPath.java @@ -0,0 +1,87 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.printer; + +import net.strokkur.commands.internal.codegen.CodeExpression; +import net.strokkur.commands.internal.codegen.CodeType; +import net.strokkur.commands.internal.codegen.as.AsExpression; +import net.strokkur.commands.internal.codegen.builder.Builders; +import net.strokkur.commands.internal.intermediate.access.ExecuteAccess; +import net.strokkur.commands.internal.intermediate.access.FieldAccess; +import net.strokkur.commands.internal.util.Utils; + +import java.util.List; +import java.util.Objects; + +public record PrintedAccessPath(List> access) { + public PrintedAccessPath(List> access) { + if (access.isEmpty()) { + throw new IllegalStateException("Access stack cannot be empty."); + } + this.access = List.copyOf(access); + } + + public boolean needsCreating() { + return !(access.getLast() instanceof FieldAccess field) || !field.getElement().isInitialized(); + } + + public String name() { + return Utils.getInstanceName(access); + } + + public String elementName() { + return access.getLast().getElement().getName(); + } + + public PrintedAccessPath parent() { + return new PrintedAccessPath(access.subList(0, access.size() - 1)); + } + + public PrintedAccessPath requiredParent() { + if (needsCreating()) { + return this; + } + return parent().requiredParent(); + } + + public AsExpression getVariableAccess() { + if (needsCreating()) { + // The field with this name + return CodeExpression.variable(name()); + } + + return Builders.fieldAccess(elementName()).setSource(parent().getVariableAccess()); + } + + public CodeType type() { + return access.getLast().getAsCodeType(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof final PrintedAccessPath that)) { + return false; + } + return Objects.equals(name(), that.name()); + } + + @Override + public int hashCode() { + return Objects.hashCode(name()); + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/javadoc/AbstractJavadocPrintingVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/javadoc/AbstractJavadocPrintingVisitor.java new file mode 100644 index 00000000..90c5d0d6 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/javadoc/AbstractJavadocPrintingVisitor.java @@ -0,0 +1,41 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.printer.javadoc; + +import net.strokkur.commands.internal.codegen.CodePackage; +import net.strokkur.commands.internal.codegen.CodeType; +import net.strokkur.commands.internal.codegen.visitor.JavadocVisitor; +import org.jetbrains.annotations.Unmodifiable; +import org.jspecify.annotations.Nullable; + +import java.util.SequencedCollection; +import java.util.Set; + +/// @apiNote instances of this class cannot be reused +public abstract class AbstractJavadocPrintingVisitor implements JavadocVisitor { + protected final StringBuilder builder = new StringBuilder(); + protected final @Nullable CodePackage currentPath; + protected final @Nullable @Unmodifiable Set existingImports; + + public AbstractJavadocPrintingVisitor(@Nullable CodePackage currentPath, @Nullable Set existingImports) { + this.currentPath = currentPath; + this.existingImports = existingImports != null ? Set.copyOf(existingImports) : Set.of(); + } + + public abstract SequencedCollection getLines(); +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/javadoc/JavaMarkdownJavadocVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/javadoc/JavaMarkdownJavadocVisitor.java new file mode 100644 index 00000000..cce961b2 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/javadoc/JavaMarkdownJavadocVisitor.java @@ -0,0 +1,93 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.printer.javadoc; + +import net.strokkur.commands.internal.codegen.CodePackage; +import net.strokkur.commands.internal.codegen.CodeType; +import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc; + +import java.util.Arrays; +import java.util.SequencedCollection; +import java.util.Set; + +public class JavaMarkdownJavadocVisitor extends JavaStarJavadocVisitor { + public JavaMarkdownJavadocVisitor() { + super(); + } + + public JavaMarkdownJavadocVisitor(CodePackage currentPath, Set imports) { + super(currentPath, imports); + } + + @Override + public SequencedCollection getLines() { + return Arrays.stream(builder.toString().strip().split("\n")) + .map(str -> str.isBlank() ? "" : " " + str) + .map(str -> "///" + str) + .toList(); + } + + @Override + public void visit(CodeJavadoc.Header value) { + builder.append('\n'); + builder.repeat("#", value.level()).append(' '); + builder.append(value.text()); + builder.append('\n'); + } + + @Override + public void visit(CodeJavadoc.Linebreak value) { + // '

' in legacy JD; Markdown equivalent is to just keep the line empty. + } + + @Override + public void visit(CodeJavadoc.InlineCode value) { + builder.append('`').append(value.code()).append('`'); + } + + @Override + public void visit(CodeJavadoc.CodeBlock value) { + builder.append("```\n"); + builder.append(value.code()); + builder.append("\n```"); + } + + @Override + public void visit(CodeJavadoc.Url value) { + builder.append('[').append(value.text()).append(']'); + builder.append('(').append(value.url()).append(')'); + } + + @Override + public void visit(CodeJavadoc.ClassReference value) { + if (value.name() != null) { + builder.append('[').append(value.name()).append(']'); + } + + builder.append('[').append(getQualifiedTypeName(value.codeClass())).append(']'); + } + + @Override + public void visit(CodeJavadoc.MethodReference value) { + if (value.name() != null) { + builder.append('[').append(value.name()).append(']'); + } + + builder.append('[').append(getMethodRefString(value.method(), value.localMethod())).append(']'); + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/javadoc/JavaStarJavadocVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/javadoc/JavaStarJavadocVisitor.java new file mode 100644 index 00000000..dd87295c --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/javadoc/JavaStarJavadocVisitor.java @@ -0,0 +1,183 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.printer.javadoc; + +import net.strokkur.commands.internal.codegen.CodeClass; +import net.strokkur.commands.internal.codegen.CodeMethod; +import net.strokkur.commands.internal.codegen.CodePackage; +import net.strokkur.commands.internal.codegen.CodeType; +import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.SequencedCollection; +import java.util.Set; +import java.util.stream.Collectors; + +public class JavaStarJavadocVisitor extends AbstractJavadocPrintingVisitor { + public JavaStarJavadocVisitor() { + super(null, null); + } + + public JavaStarJavadocVisitor(CodePackage currentPath, Set imports) { + super(currentPath, imports); + } + + @Override + public SequencedCollection getLines() { + final List out = new ArrayList<>(); + out.add("/**"); + out.addAll(Arrays.stream(builder.toString().strip().split("\n")) + .map(str -> str.isEmpty() ? " *" : " * " + str) + .toList()); + out.add(" */"); + return out; + } + + @Override + public void visit(CodeJavadoc.PlainText value) { + builder.append(value.text()); + } + + @Override + public void visit(CodeJavadoc.Meta value) { + builder + .append('@') + .append(value.descriptor()) + .append(' ') + .append(value.value()); + } + + @Override + public void visit(CodeJavadoc.MethodReferenceMeta value) { + builder + .append('@') + .append(value.descriptor()) + .append(' ') + .append(getMethodRefString(value.codeMethod(), value.localMethod())); + + if (value.text() != null) { + builder.append(' ').append(value.text()); + } + } + + @Override + public void visit(CodeJavadoc.ClassReferenceMeta value) { + builder + .append('@') + .append(value.descriptor()) + .append(' ') + .append(getQualifiedTypeName(value.type())); + + if (value.text() != null) { + builder.append(' ').append(value.text()); + } + } + + @Override + public void visit(CodeJavadoc.Header value) { + builder.append('\n'); + builder.append("'); + builder.append(value.text()); + builder.append("'); + builder.append('\n'); + } + + @Override + public void visit(CodeJavadoc.Newline value) { + builder.append('\n'); + } + + @Override + public void visit(CodeJavadoc.Linebreak value) { + builder.append("

"); + } + + @Override + public void visit(CodeJavadoc.InlineCode value) { + builder.append("{@code ").append(value.code()).append("}"); + } + + @Override + public void visit(CodeJavadoc.CodeBlock value) { + builder.append("

{@code\n")
+        .append(value.code())
+        .append("\n}
"); + } + + @Override + public void visit(CodeJavadoc.Url value) { + builder.append("") + .append(value.text()) + .append(""); + } + + @Override + public void visit(CodeJavadoc.ClassReference value) { + builder.append("{@link ").append(getQualifiedTypeName(value.codeClass())); + if (value.name() != null) { + builder.append(" ").append(value.name()); + } + builder.append("}"); + } + + @Override + public void visit(CodeJavadoc.MethodReference value) { + builder.append("{@link ").append(getMethodRefString(value.method(), value.localMethod())); + if (value.name() != null) { + builder.append(" ").append(value.name()); + } + builder.append("}"); + } + + protected String getQualifiedTypeName(CodeClass type) { + if (type.codePackage().path().equals("java.lang") || Objects.equals(currentPath, type.codePackage())) { + return type.name(); + } + return type.fullyQualifiedName(); + } + + protected String getQualifiedTypeName(CodeType.ClassType type) { + if (CodePackage.isRedundantImport(currentPath, type.codePackage()) + || existingImports != null && existingImports.contains(type) + ) { + return type.name(); + } + return type.fullyQualifiedName(); + } + + public String javadocName(CodeMethod method) { + return method.name() + "(" + method.parameters().stream() + .map(param -> { + if (param.type() instanceof CodeType.ClassType classType) { + return getQualifiedTypeName(classType); + } + return param.type().fullyQualifiedName(); + }) + .collect(Collectors.joining(", ")) + + ")"; + } + + protected String getMethodRefString(CodeMethod method, boolean local) { + return local + ? "#" + javadocName(method) + : getQualifiedTypeName(method.declaredClass()) + "#" + javadocName(method); + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/AbstractSourcePrintingVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/AbstractSourcePrintingVisitor.java new file mode 100644 index 00000000..8cd08af2 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/AbstractSourcePrintingVisitor.java @@ -0,0 +1,194 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.printer.source; + +import net.strokkur.commands.internal.codegen.CodeAnnotation; +import net.strokkur.commands.internal.codegen.CodeExpression; +import net.strokkur.commands.internal.codegen.CodePackage; +import net.strokkur.commands.internal.codegen.InvokesMethod; +import net.strokkur.commands.internal.codegen.Modifiers; +import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitable; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitor; +import net.strokkur.commands.internal.printer.javadoc.AbstractJavadocPrintingVisitor; +import org.jspecify.annotations.Nullable; + +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public abstract class AbstractSourcePrintingVisitor implements CodeVisitor { + protected final Supplier javadocPrintingVisitor; + private final String indentString; + private final String continuationIndentString; + private int indentation = 0; + private int continuationIndent = 0; + + public AbstractSourcePrintingVisitor(Supplier javadocPrintingVisitor, String indentString, String continuationIndentString) { + this.javadocPrintingVisitor = javadocPrintingVisitor; + this.indentString = indentString; + this.continuationIndentString = continuationIndentString; + } + + /// Packages are never printed. + @Override + public final StringBuilder visitPackage(CodePackage codePackage) { + throw new IllegalStateException("This should not be called."); + } + + protected final void appendIndented(Runnable run) { + indentation++; + run.run(); + indentation--; + } + + protected final void appendIndentedContinuation(Runnable run) { + continuationIndent++; + run.run(); + continuationIndent--; + } + + protected final StringBuilder append(Consumer run) { + final StringBuilder builder = new StringBuilder(); + run.accept(builder); + return builder; + } + + protected final void appendIndent(StringBuilder builder) { + builder.repeat(indentString, indentation).repeat(continuationIndentString, continuationIndent); + } + + // Utility methods + + protected void appendNested(StringBuilder builder, CodeVisitable visitable) { + builder.append(visitable.accept(this)); + } + + protected String joining(Collection nested) { + return nested.stream() + .map(visitable -> visitable.accept(this)) + .map(StringBuilder::toString) + .collect(Collectors.joining(", ")); + } + + protected void printJavadocIndented(StringBuilder builder, @Nullable CodeJavadoc javadoc) { + if (javadoc != null) { + final AbstractJavadocPrintingVisitor visitor = javadocPrintingVisitor.get(); + javadoc.accept(visitor); + for (String line : visitor.getLines()) { + appendIndent(builder); + builder.append(line); + builder.append("\n"); + } + } + } + + protected void printAnnotationsIndented(StringBuilder builder, List annotations) { + for (CodeAnnotation annotation : annotations) { + appendIndent(builder); + appendNested(builder, annotation); + builder.append("\n"); + } + } + + protected void printModifiersIndented(StringBuilder builder, Set modifiers) { + appendIndent(builder); + modifiers.stream() + .sorted(Comparator.comparingInt(Modifiers::priority)) + .map(Modifiers::toString) + .forEach(mod -> builder.append(mod).append(' ')); + } + + protected void appendMethodParametersMultiline(StringBuilder builder, List parameters) { + appendIndentedContinuation(() -> { + builder.append("\n"); + for (int i = 0, parametersSize = parameters.size(); i < parametersSize; i++) { + final CodeExpression parameter = parameters.get(i); + appendIndent(builder); + appendNested(builder, parameter); + if (i + 1 < parametersSize) { + builder.append(","); + } + builder.append("\n"); + } + }); + appendIndent(builder); + } + + protected void appendMethodInvocation(StringBuilder builder, InvokesMethod method) { + if (method.isCtor()) { + builder.append("new "); + } else { + final String source; + if (method.instanceSource() != null) { + source = method.instanceSource().accept(this).toString(); + } else if (method.isStatic() && method.type() != null) { + source = method.type().name(); + } else { + source = null; + } + + if (source != null) { + builder.append(source); + appendIndentedContinuation(() -> { + if (method.style().newline()) { + builder.append("\n"); + appendIndent(builder); + } + builder.append("."); + }); + } + } + + builder.append(method.methodName()); + builder.append("("); + if (method.style().multilineParameters()) { + appendMethodParametersMultiline(builder, method.parameters()); + } else { + builder.append(joining(method.parameters())); + } + builder.append(")"); + + appendIndentedContinuation(() -> { + for (InvokesMethod.Chained chained : method.chained()) { + if (chained.style().newline()) { + builder.append("\n"); + appendIndent(builder); + } + builder.append("."); + builder.append(chained.methodName()); + builder.append("("); + if (chained.style().multilineParameters()) { + appendMethodParametersMultiline(builder, chained.parameters()); + } else { + builder.append(joining(chained.parameters())); + } + + if (chained.style().newlineClosingBrace()) { + builder.append("\n"); + appendIndent(builder); + } + builder.append(")"); + } + }); + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/ImportGatheringVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/ImportGatheringVisitor.java new file mode 100644 index 00000000..8701c9b8 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/ImportGatheringVisitor.java @@ -0,0 +1,208 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.printer.source; + +import net.strokkur.commands.internal.codegen.CodeAnnotation; +import net.strokkur.commands.internal.codegen.CodeClass; +import net.strokkur.commands.internal.codegen.CodeExpression; +import net.strokkur.commands.internal.codegen.CodeField; +import net.strokkur.commands.internal.codegen.CodeMethod; +import net.strokkur.commands.internal.codegen.CodePackage; +import net.strokkur.commands.internal.codegen.CodeParameter; +import net.strokkur.commands.internal.codegen.CodeStatement; +import net.strokkur.commands.internal.codegen.CodeType; +import net.strokkur.commands.internal.codegen.InvokesMethod; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitable; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitor; +import org.jspecify.annotations.Nullable; + +import java.util.Collection; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class ImportGatheringVisitor implements CodeVisitor> { + + public Set collectFilteredImports(CodeClass codeClass) { + return codeClass.accept(this).stream() + .filter(gathered -> !CodePackage.isRedundantImport(codeClass.codePackage(), gathered.codePackage())) + .collect(Collectors.toSet()); + } + + private Set collectMethodInvokesImports(InvokesMethod invokes) { + final Set chainedImports = collect(invokes.chained().stream() + .flatMap(chained -> chained.parameters().stream()) + .toList() + ); + + if (invokes.isCtor()) { + return join( + Objects.requireNonNull(invokes.type()).accept(this), + collect(invokes.parameters()), + chainedImports + ); + } + + final Set typeImport = invokes.isStatic() && invokes.type() != null + ? invokes.type().accept(this) + : Set.of(); + + return join( + typeImport, + collect(invokes.parameters()), + chainedImports + ); + } + + @SafeVarargs + private Set join(Set... all) { + return Stream.of(all) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + } + + private Set maybeAccess(@Nullable CodeVisitable visitable) { + if (visitable != null) { + return visitable.accept(this); + } else { + return Set.of(); + } + } + + private Set collect(Collection collection) { + return collection.stream() + .flatMap(visitable -> visitable.accept(this).stream()) + .collect(Collectors.toSet()); + } + + @Override + public Set visitClass(CodeClass codeClass) { + return join( + Set.of(CodeType.ofClass(codeClass)), + collect(codeClass.methods()), + collect(codeClass.fields()), + collect(codeClass.annotations()) + ); + } + + @Override + public Set visitMethod(CodeMethod codeMethod) { + return join( + collect(codeMethod.parameters()), + collect(codeMethod.throwsExceptions()), + codeMethod.returnType().accept(this), + collect(codeMethod.codeBlock().statements()) + ); + } + + @Override + public Set visitPackage(CodePackage codePackage) { + throw new IllegalStateException("This should not be called."); + } + + @Override + public Set visitParameter(CodeParameter codeParameter) { + return codeParameter.type().accept(this); + } + + @Override + public Set visitType(CodeType codeType) { + if (codeType instanceof CodeType.ArrayType array) { + return array.inner().accept(this); + } + + if (codeType instanceof CodeType.ClassType codeClass) { + return join( + codeClass.types() == null ? Set.of() : collect(codeClass.types()), + Set.of(codeClass) + ); + } + + return Set.of(); + } + + @Override + public Set visitAnnotation(CodeAnnotation codeAnnotation) { + return Set.of(codeAnnotation.type()); + } + + @Override + public Set visitField(CodeField codeField) { + return join( + codeField.initialiser() != null ? codeField.initialiser().accept(this) : Set.of(), + collect(codeField.annotations()), + codeField.type().accept(this) + ); + } + + @Override + public Set visitExpression(CodeExpression codeExpression) { + return switch (codeExpression) { + case CodeExpression.MethodInvocation(InvokesMethod invokes) -> collectMethodInvokesImports(invokes); + + case CodeExpression.MethodReference ref -> ref.type().accept(this); + + case CodeExpression.SingleLineLambda lambda -> lambda.lambdaExpression().accept(this); + + case CodeExpression.MultiLineLambda lambda -> collect(lambda.statements()); + + case CodeExpression.Instanceof instStmt -> join( + instStmt.left().accept(this), + instStmt.type().accept(this) + ); + + case CodeExpression.FieldAccess field -> join( + maybeAccess(field.source()), + field.isStatic() ? maybeAccess(field.type()) : Set.of() + ); + + default -> Set.of(); + }; + } + + @Override + public Set visitStatement(CodeStatement codeStatement) { + return switch (codeStatement) { + case CodeStatement.VariableDeclaration variableDeclaration -> { + if (variableDeclaration.assignment() != null) { + yield join( + variableDeclaration.assignment().accept(this), + variableDeclaration.type().accept(this) + ); + } + yield variableDeclaration.type().accept(this); + } + + case CodeStatement.ReturnStatement returnStatement -> returnStatement.returnValue() != null ? + returnStatement.returnValue().accept(this) : + Set.of(); + + case CodeStatement.ThrowStatement throwStatement -> throwStatement.throwExpression().accept(this); + + case CodeStatement.MethodInvocation(InvokesMethod invokes) -> collectMethodInvokesImports(invokes); + + case CodeStatement.If ifStmt -> join( + ifStmt.booleanExpr().accept(this), + collect(ifStmt.ifTrue()) + ); + + case CodeStatement.Blank blank -> Set.of(); + }; + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/JavaSourcePrintingVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/JavaSourcePrintingVisitor.java new file mode 100644 index 00000000..fd22d328 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/JavaSourcePrintingVisitor.java @@ -0,0 +1,309 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.printer.source; + +import net.strokkur.commands.internal.codegen.CodeAnnotation; +import net.strokkur.commands.internal.codegen.CodeClass; +import net.strokkur.commands.internal.codegen.CodeConstructor; +import net.strokkur.commands.internal.codegen.CodeExpression; +import net.strokkur.commands.internal.codegen.CodeField; +import net.strokkur.commands.internal.codegen.CodeMethod; +import net.strokkur.commands.internal.codegen.CodeParameter; +import net.strokkur.commands.internal.codegen.CodeStatement; +import net.strokkur.commands.internal.codegen.CodeType; +import net.strokkur.commands.internal.codegen.InvokesMethod; +import net.strokkur.commands.internal.codegen.Modifiers; +import net.strokkur.commands.internal.printer.javadoc.AbstractJavadocPrintingVisitor; + +import java.util.List; +import java.util.function.Predicate; +import java.util.function.Supplier; + +public class JavaSourcePrintingVisitor extends AbstractSourcePrintingVisitor { + public JavaSourcePrintingVisitor(Supplier javadocPrintingVisitor, String indent, String continuationIndent) { + super(javadocPrintingVisitor, indent, continuationIndent); + } + + @Override + public StringBuilder visitClass(CodeClass codeClass) { + class ClassPrintUtil { + private void printMethods(StringBuilder builder, List methods) { + methods.forEach(method -> { + builder.append("\n"); + appendNested(builder, method); + }); + } + } + + final ClassPrintUtil util = new ClassPrintUtil(); + return append(builder -> { + printJavadocIndented(builder, codeClass.javadoc()); + printAnnotationsIndented(builder, codeClass.annotations()); + printModifiersIndented(builder, codeClass.modifiers()); + builder.append("class "); + builder.append(codeClass.name()); + builder.append(" {\n"); + + appendIndented(() -> { + final List staticFields = codeClass.fields().stream() + .filter(field -> field.modifiers().contains(Modifiers.STATIC)) + .toList(); + staticFields.forEach(field -> appendNested(builder, field)); + + final List instanceFields = codeClass.fields().stream() + .filter(field -> !field.modifiers().contains(Modifiers.STATIC)) + .toList(); + + if (!staticFields.isEmpty() && !instanceFields.isEmpty()) { + builder.append("\n"); + } + instanceFields.forEach(field -> appendNested(builder, field)); + + final List staticMethods = codeClass.methods().stream() + .filter(method -> method.modifiers().contains(Modifiers.STATIC)) + .toList(); + util.printMethods(builder, staticMethods); + + final List constructors = codeClass.methods().stream() + .filter(CodeConstructor.class::isInstance) + .toList(); + util.printMethods(builder, constructors); + + final List instanceMethods = codeClass.methods().stream() + .filter(Predicate.not(method -> method.modifiers().contains(Modifiers.STATIC))) + .filter(Predicate.not(CodeConstructor.class::isInstance)) + .toList(); + util.printMethods(builder, instanceMethods); + }); + + appendIndent(builder); + builder.append("}\n"); + }); + } + + @Override + public StringBuilder visitMethod(CodeMethod codeMethod) { + return append(builder -> { + printJavadocIndented(builder, codeMethod.javadoc()); + printModifiersIndented(builder, codeMethod.modifiers()); + builder.append(codeMethod.returnType().accept(this)); + if (!(codeMethod instanceof CodeConstructor)) { + builder.append(" "); + builder.append(codeMethod.name()); + } + + builder.append("("); + builder.append(joining(codeMethod.parameters())); + builder.append(")"); + + if (!codeMethod.throwsExceptions().isEmpty()) { + builder.append(" throws "); + builder.append(joining(codeMethod.throwsExceptions())); + } + + builder.append(" {\n"); + appendIndented(() -> codeMethod.codeBlock().statements().forEach( + stmt -> appendNested(builder, stmt) + )); + appendIndent(builder); + builder.append("}\n"); + }); + } + + @Override + public StringBuilder visitParameter(CodeParameter codeParameter) { + return append(builder -> { + appendNested(builder, codeParameter.type()); + builder.append(" "); + builder.append(codeParameter.name()); + }); + } + + @Override + public StringBuilder visitType(CodeType codeType) { + final StringBuilder out = new StringBuilder(codeType.name()); + + if (codeType instanceof CodeType.ClassType classType && classType.types() != null) { + out.append("<").append(joining(classType.types())).append(">"); + } + + return out; + } + + @Override + public StringBuilder visitAnnotation(CodeAnnotation codeAnnotation) { + return append(builder -> { + builder.append("@"); + appendNested(builder, codeAnnotation.type()); + }); + } + + @Override + public StringBuilder visitField(CodeField codeField) { + return append(builder -> { + printModifiersIndented(builder, codeField.modifiers()); + if (!codeField.annotations().isEmpty()) { + builder.append(joining(codeField.annotations())); + builder.append(" "); + } + appendNested(builder, codeField.type()); + builder.append(" "); + builder.append(codeField.name()); + if (codeField.initialiser() != null) { + builder.append(" = "); + appendNested(builder, codeField.initialiser()); + } + builder.append(";\n"); + }); + } + + @Override + public StringBuilder visitExpression(CodeExpression codeExpression) { + return append(builder -> { + switch (codeExpression) { + case CodeExpression.MethodInvocation(InvokesMethod invokes) -> { + appendMethodInvocation(builder, invokes); + } + case CodeExpression.Null ignored -> { + builder.append("null"); + } + case CodeExpression.StringLiteral stringLiteral -> { + builder.append('"').append(stringLiteral.value()).append('"'); + } + case CodeExpression.NumberConstant number -> { + builder.append(number); + } + case CodeExpression.Variable variable -> { + builder.append(variable.name()); + } + case CodeExpression.MethodReference ref -> { + builder.append(ref.type().name()).append("::").append(ref.methodName()); + } + case CodeExpression.SingleLineLambda lambda -> { + appendLambdaHead(builder, lambda.lambdaParams()); + appendNested(builder, lambda.lambdaExpression()); + } + case CodeExpression.MultiLineLambda lambda -> { + appendLambdaHead(builder, lambda.lambdaParams()); + builder.append("{\n"); + appendIndented(() -> { + for (CodeStatement statement : lambda.statements()) { + appendNested(builder, statement); + } + }); + appendIndent(builder); + builder.append("}"); + } + case CodeExpression.Instanceof instExpr -> { + if (instExpr.isInverted()) { + builder.append("!("); + } + appendNested(builder, instExpr.left()); + builder.append(" instanceof "); + builder.append(instExpr.type().name()); + if (instExpr.name() != null) { + builder.append(" ").append(instExpr.name()); + } + if (instExpr.isInverted()) { + builder.append(")"); + } + } + case CodeExpression.FieldAccess( + CodeType.ClassType type, CodeExpression source, String fieldName, boolean isStatic + ) -> { + if (source != null) { + appendNested(builder, source); + builder.append("."); + } else if (type != null && isStatic) { + builder.append(type.name()); + builder.append("."); + } + + builder.append(fieldName); + } + default -> throw new IllegalStateException("Invalid expression: " + codeExpression.getClass().getName()); + } + }); + } + + @Override + public StringBuilder visitStatement(CodeStatement codeStatement) { + return append(builder -> { + if (codeStatement instanceof CodeStatement.Blank) { + builder.append("\n"); + return; + } + + appendIndent(builder); + + if (codeStatement instanceof CodeStatement.If ifStmt) { + builder.append("if ("); + appendNested(builder, ifStmt.booleanExpr()); + builder.append(") {\n"); + appendIndented(() -> { + ifStmt.ifTrue().forEach(stmt -> appendNested(builder, stmt)); + }); + appendIndent(builder); + builder.append("}\n"); + return; + } + + switch (codeStatement) { + case CodeStatement.MethodInvocation(InvokesMethod invokes) -> { + appendMethodInvocation(builder, invokes); + } + case CodeStatement.ReturnStatement returnStatement -> { + builder.append("return"); + if (returnStatement.returnValue() != null) { + builder.append(" "); + appendNested(builder, returnStatement.returnValue()); + } + } + case CodeStatement.ThrowStatement throwStatement -> { + builder.append("throw "); + appendNested(builder, throwStatement.throwExpression()); + } + case CodeStatement.VariableDeclaration variableDeclaration -> { + if (variableDeclaration.isFinal()) { + builder.append("final "); + } + appendNested(builder, variableDeclaration.type()); + builder.append(" "); + builder.append(variableDeclaration.name()); + if (variableDeclaration.assignment() != null) { + builder.append(" = "); + appendNested(builder, variableDeclaration.assignment()); + } + } + default -> throw new IllegalStateException("Invalid statement: " + codeStatement.getClass().getName()); + } + builder.append(";\n"); + }); + } + + private void appendLambdaHead(StringBuilder builder, List lambdaParams) { + if (lambdaParams.size() != 1) { + builder.append("("); + builder.append(String.join(", ", lambdaParams)); + builder.append(")"); + } else { + builder.append(lambdaParams.getFirst()); + } + builder.append(" -> "); + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/util/Classes.java b/processor/common/src/main/java/net/strokkur/commands/internal/util/Classes.java index ead1be2e..39933620 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/util/Classes.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/util/Classes.java @@ -17,30 +17,67 @@ */ package net.strokkur.commands.internal.util; -public interface Classes { +import net.strokkur.commands.internal.codegen.CodeType; +import net.strokkur.commands.internal.codegen.as.AsCodeType; + +import java.util.Arrays; + +public enum Classes implements AsCodeType { // Java types - String LIST = "java.util.List"; - String COLLECTIONS = "java.util.Collections"; - String ARRAYS = "java.util.Arrays"; - String LIST_STRING = LIST + ""; - String PREDICATE = "java.util.function.Predicate"; - String COMPLETABLE_FUTURE = "java.util.concurrent.CompletableFuture"; - String METHOD = "java.lang.reflect.Method"; + STRING("java.lang.String"), + BOOLEAN("java.lang.Boolean"), + + LIST("java.util.List"), + COLLECTIONS("java.util.Collections"), + ARRAYS("java.util.Arrays"), + + PREDICATE("java.util.function.Predicate"), + COMPLETABLE_FUTURE("java.util.concurrent.CompletableFuture"), + + METHOD("java.lang.reflect.Method"), + ILLEGAL_ACCESS_EXCEPTION("java.lang.IllegalAccessException"), + + LIST_STRING(LIST, STRING), // Brigadier types - String COMMAND = "com.mojang.brigadier.Command"; - String COMMAND_DISPATCHER = "com.mojang.brigadier.CommandDispatcher"; - String LITERAL_COMMAND_NODE = "com.mojang.brigadier.tree.LiteralCommandNode"; - String LITERAL_ARGUMENT_BUILDER = "com.mojang.brigadier.builder.LiteralArgumentBuilder"; - String SIMPLE_COMMAND_EXCEPTION_TYPE = "com.mojang.brigadier.exceptions.SimpleCommandExceptionType"; - String LITERAL_MESSAGE = "com.mojang.brigadier.LiteralMessage"; - String COMMAND_CONTEXT = "com.mojang.brigadier.context.CommandContext"; - String SUGGESTIONS = "com.mojang.brigadier.suggestion.Suggestions"; - String SUGGESTION_PROVIDER = "com.mojang.brigadier.suggestion.SuggestionProvider"; - String SUGGESTIONS_BUILDER = "com.mojang.brigadier.suggestion.SuggestionsBuilder"; + COMMAND("com.mojang.brigadier.Command"), + COMMAND_DISPATCHER("com.mojang.brigadier.CommandDispatcher"), + LITERAL_COMMAND_NODE("com.mojang.brigadier.tree.LiteralCommandNode"), + LITERAL_ARGUMENT_BUILDER("com.mojang.brigadier.builder.LiteralArgumentBuilder"), + SIMPLE_COMMAND_EXCEPTION_TYPE("com.mojang.brigadier.exceptions.SimpleCommandExceptionType"), + LITERAL_MESSAGE("com.mojang.brigadier.LiteralMessage"), + COMMAND_CONTEXT("com.mojang.brigadier.context.CommandContext"), + SUGGESTIONS("com.mojang.brigadier.suggestion.Suggestions"), + SUGGESTION_PROVIDER("com.mojang.brigadier.suggestion.SuggestionProvider"), + SUGGESTIONS_BUILDER("com.mojang.brigadier.suggestion.SuggestionsBuilder"), + + BOOL_ARGUMENT_TYPE("com.mojang.brigadier.arguments.BoolArgumentType"), + INTEGER_ARGUMENT_TYPE("com.mojang.brigadier.arguments.IntegerArgumentType"), + LONG_ARGUMENT_TYPE("com.mojang.brigadier.arguments.LongArgumentType"), + FLOAT_ARGUMENT_TYPE("com.mojang.brigadier.arguments.FloatArgumentType"), + DOUBLE_ARGUMENT_TYPE("com.mojang.brigadier.arguments.DoubleArgumentType"), + STRING_ARGUMENT_TYPE("com.mojang.brigadier.arguments.StringArgumentType"), // Other - String NULL_MARKED = "org.jspecify.annotations.NullMarked"; - String NULLABLE = "org.jspecify.annotations.Nullable"; - String INJECT = "jakarta.inject.Inject"; + NULL_MARKED("org.jspecify.annotations.NullMarked"), + NULLABLE("org.jspecify.annotations.Nullable"), + INJECT("jakarta.inject.Inject"); + + private final CodeType.ClassType classType; + + Classes(String fqn) { + this.classType = CodeType.ofClass(fqn); + } + + Classes(Classes base, Classes... types) { + this.classType = CodeType.ofClassTyped(base.classType.codeClass(), Arrays.stream(types) + .map(Classes::getAsCodeType) + .toArray(CodeType[]::new) + ); + } + + @Override + public CodeType.ClassType getAsCodeType() { + return classType; + } } diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/util/ConvertableTo.java b/processor/common/src/main/java/net/strokkur/commands/internal/util/ConvertableTo.java new file mode 100644 index 00000000..9b6a5f3e --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/util/ConvertableTo.java @@ -0,0 +1,29 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.util; + +public interface ConvertableTo { + S convert(); + + interface Self> extends ConvertableTo { + @Override + default S convert() { + return (S) this; + } + } +} diff --git a/processor/common/src/test/java/net/strokkur/commands/internal/codegen/CodePackageEqualityTests.java b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/CodePackageEqualityTests.java new file mode 100644 index 00000000..515c5b43 --- /dev/null +++ b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/CodePackageEqualityTests.java @@ -0,0 +1,85 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CodePackageEqualityTests { + + @ParameterizedTest + @CsvSource({ + "this.one.matches, this.one.matches", + "this.also.matches, this.also.matches", + "java.lang, java.lang", + "io.papermc.paper.command, io.papermc.paper.command" + }) + void ensureTwoPackagesEqual(String path1, String path2) { + final CodePackage first = CodePackage.of(path1); + final CodePackage second = CodePackage.of(path2); + assertEquals(first, second); + assertEquals(first.hashCode(), second.hashCode()); + } + + @ParameterizedTest + @CsvSource({ + "this.one.matches, io.papermc.paper.command", + "this.also.matches, this.one.matches", + "java.lang, this.also.matches", + "io.papermc.paper.command, java.lang" + }) + void ensureTwoPackagesDoNotEqual(String path1, String path2) { + final CodePackage first = CodePackage.of(path1); + final CodePackage second = CodePackage.of(path2); + assertNotEquals(first, second); + assertNotEquals(first.hashCode(), second.hashCode()); + } + + @ParameterizedTest + @CsvSource({ + "base.package, base.package", + "base.package, java.lang", + "different.one, different.one", + "different.one, java.lang", + "a.very.long.one.because.why.not, a.very.long.one.because.why.not", + "a.very.long.one.because.why.not, java.lang" + }) + void ensureRedundancyWorks(String path1, String path2) { + final CodePackage first = CodePackage.of(path1); + final CodePackage second = CodePackage.of(path2); + assertTrue(CodePackage.isRedundantImport(first, second)); + } + + @ParameterizedTest + @CsvSource({ + "base.package, base.package.subpath", + "base.package.subpath, base.package", + "hey.now, java.util", + "hey.now, java.lang.subpath", + }) + void ensureRedundancyWorks2(String path1, String path2) { + final CodePackage first = CodePackage.of(path1); + final CodePackage second = CodePackage.of(path2); + assertFalse(CodePackage.isRedundantImport(first, second)); + } +} diff --git a/processor/common/src/test/java/net/strokkur/commands/internal/codegen/CommonJavadocVisitorTests.java b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/CommonJavadocVisitorTests.java new file mode 100644 index 00000000..f7989598 --- /dev/null +++ b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/CommonJavadocVisitorTests.java @@ -0,0 +1,140 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen; + +import net.strokkur.commands.internal.codegen.builder.Builders; +import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc; +import net.strokkur.commands.internal.printer.javadoc.AbstractJavadocPrintingVisitor; + +import java.util.function.Supplier; + +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.author; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.blank; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.classReference; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.codeBlock; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.combine; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.combineLines; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.header; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.inlineCode; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.linebreak; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.methodReference; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.see; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.text; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.throwsMeta; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.url; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.version; +import static org.junit.jupiter.api.Assertions.assertEquals; + +abstract class CommonJavadocVisitorTests { + void checkOutput(String expected, CodeJavadoc javadoc, Supplier visitorSupplier) { + final AbstractJavadocPrintingVisitor visitor = visitorSupplier.get(); + javadoc.accept(visitor); + final String actual = String.join("\n", visitor.getLines()); + assertEquals(expected, actual); + } + + CodeJavadoc classJavadoc() { + return combineLines( + text("A class holding the Brigadier source tree generated from"), + combine(classReference(sourceClass()), text(" using "), url("StrokkCommands", "https://commands.strokkur.net")), + blank(), + author("Strokkur24 - StrokkCommands"), + version("2.0.0"), + see(createMethod(), "creating the LiteralCommandNode", true), + see(registerMethod(), "registering the LiteralCommandNode", true) + ); + } + + CodeJavadoc registerJavadoc() { + return combineLines( + text("Shortcut for registering the command node returned from"), + combine(methodReference(createMethod(), true), text(". This method uses the provided aliases")), + text("and description from the original source file."), + header("Registering the command", 3), + text("This method can safely be called either in your plugin bootstrapper's"), + combine(methodReference(bootstrapMethod()), text(" or your main")), + combine(text("class' "), methodReference(onLoadMethod()), text(" or "), methodReference(onEnableMethod())), + text("methods."), + linebreak(), + text("You need to call it inside of a lifecycle event. General information can be found on the"), + combine(url("PaperMC Lifecycle API docs page", "https://docs.papermc.io/paper/dev/lifecycle/"), text(".")), + linebreak(), + combine(text("The general use case might look like this (example given inside the "), inlineCode("onEnable"), text(" method):")), + codeBlock(""" + this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS.newHandler(event -> { + final Commands commands = event.registrar(); + EntitiesCommandBrigadier.register(commands); + }""") + ); + } + + CodeJavadoc createJd() { + return combineLines( + text("A method for creating a Brigadier command node which denotes the declared command"), + combine(text("in "), classReference(sourceClass()), text(". "), text("You can either retrieve the unregistered node with this method")), + combine(text("or register it directly with "), methodReference(registerMethod(), true), text(".")) + ); + } + + CodeJavadoc ctorJd() { + return combineLines( + text("The constructor is not accessible. There is no need for an instance"), + text("to be created, as no state is stored and all methods are static."), + blank(), + throwsMeta(CodeType.ofClass("java.lang.IllegalAccessException"), "always") + ); + } + + CodeClass sourceClass() { + return CodeClass.simple("com.example.CommandClass"); + } + + CodeClass targetClass() { + return CodeClass.simple("com.example.CommandClassBrigadier"); + } + + CodeMethod createMethod() { + return Builders.method(targetClass(), "create") + .build(); + } + + CodeMethod registerMethod() { + return Builders.method(targetClass(), "register") + .addParameter(CodeType.ofClass(CodeClass.simple("io.papermc.paper.command.brigadier.Commands")), "commands") + .build(); + } + + CodeMethod bootstrapMethod() { + return Builders.method("bootstrap") + .setDeclaringClass(CodeClass.simple("io.papermc.paper.plugin.bootstrap.PluginBootstrap")) + .addParameter(CodeType.ofClass(CodeClass.simple("io.papermc.paper.plugin.bootstrap.BootstrapContext")), "context") + .build(); + } + + CodeMethod onLoadMethod() { + return Builders.method("onLoad") + .setDeclaringClass(CodeClass.simple("org.bukkit.plugin.java.JavaPlugin")) + .build(); + } + + CodeMethod onEnableMethod() { + return Builders.method("onEnable") + .setDeclaringClass(CodeClass.simple("org.bukkit.plugin.java.JavaPlugin")) + .build(); + } +} diff --git a/processor/common/src/test/java/net/strokkur/commands/internal/codegen/FullCommandClassBuilderTests.java b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/FullCommandClassBuilderTests.java new file mode 100644 index 00000000..312405af --- /dev/null +++ b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/FullCommandClassBuilderTests.java @@ -0,0 +1,151 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen; + +import net.strokkur.commands.internal.codegen.builder.Builders; +import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc; +import net.strokkur.commands.internal.printer.javadoc.JavaMarkdownJavadocVisitor; +import net.strokkur.commands.internal.printer.source.ImportGatheringVisitor; +import net.strokkur.commands.internal.printer.source.JavaSourcePrintingVisitor; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +class FullCommandClassBuilderTests { + + @Test + void testFullClassExampleImports() { + final CodeClass exampleClass = constructExampleCommandClass(); + + final ImportGatheringVisitor visitor = new ImportGatheringVisitor(); + final Set collectedImports = exampleClass.accept(visitor); + + final String expected = """ + com.example.ExampleCommandBrigadier + io.papermc.paper.command.brigadier.Commands + java.lang.IllegalAccessException + java.lang.String + org.jspecify.annotations.NullMarked + org.jspecify.annotations.Nullable"""; + final String actual = collectedImports.stream() + .sorted() + .map(CodeType::fullyQualifiedName) + .collect(Collectors.joining("\n")); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testFullClassExampleJavaPrinter() { + final CodeClass exampleClass = constructExampleCommandClass(); + final JavaSourcePrintingVisitor visitor = new JavaSourcePrintingVisitor(JavaMarkdownJavadocVisitor::new, " ", " "); + + // language=java + final String expected = """ + /// A very cool class. + /// + /// @author Strokkur24 + @NullMarked + public final class ExampleCommandBrigadier { + public static final String NAME = "example"; + public static final @Nullable String DESCRIPTION = null; + public static final String ALIASES = "example"; + + /// Registers your command. + public static void register(Commands commands) { + commands.register(create(), DESCRIPTION, ALIASES); + } + + public static void create() { + } + + /// The constructor is inaccessible. + /// + /// @throws IllegalAccessException always + private ExampleCommandBrigadier() throws IllegalAccessException { + throw new IllegalAccessException("This class cannot be instantiated."); + } + } + """; + final String actual = exampleClass.accept(visitor).toString(); + + Assertions.assertEquals(expected, actual); + } + + private CodeClass constructExampleCommandClass() { + final CodeClass commands = CodeClass.simple("io.papermc.paper.command.brigadier.Commands"); + final CodeClass current = CodeClass.simple("com.example.ExampleCommandBrigadier"); + + return Builders.classBuilder(current.fullyQualifiedName()) + .setAnnotations(List.of(CodeAnnotation.NULL_MARKED)) + .setModifiers(Set.of(Modifiers.PUBLIC, Modifiers.FINAL)) + .setJavadoc(CodeJavadoc.combineLines( + CodeJavadoc.text("A very cool class."), + CodeJavadoc.linebreak(), + CodeJavadoc.author("Strokkur24") + )) + .addField(Builders.field("NAME", CodeType.ofClass(CodeClass.STRING)) + .setModifiers(Set.of(Modifiers.PUBLIC, Modifiers.STATIC, Modifiers.FINAL)) + .setInitialiser(CodeExpression.string("example")) + ) + .addField(Builders.field("DESCRIPTION", CodeType.ofClass(CodeClass.STRING)) + .setModifiers(Set.of(Modifiers.PUBLIC, Modifiers.STATIC, Modifiers.FINAL)) + .setInitialiser(CodeExpression.nullExpr()) + .addAnnotation(CodeAnnotation.NULLABLE) + ) + .addField(Builders.field("ALIASES", CodeType.ofClass(CodeClass.STRING)) + .setModifiers(Set.of(Modifiers.PUBLIC, Modifiers.STATIC, Modifiers.FINAL)) + .setInitialiser(CodeExpression.string("example")) + ) + .addMethod(Builders.method(current, "register") + .setJavadoc(CodeJavadoc.text("Registers your command.")) + .setModifiers(Set.of(Modifiers.PUBLIC, Modifiers.STATIC)) + .addParameter(CodeType.ofClass(commands), "commands") + .setMethodStatements(List.of( + Builders.methodInvocation("register") + .addParameter(Builders.methodInvocation("create")) + .addParameter(CodeExpression.variable("DESCRIPTION")) + .addParameter(CodeExpression.variable("ALIASES")) + .setInstanceVariable("commands") + ) + ) + ) + .addMethod(Builders.method(current, "create") + .setModifiers(Set.of(Modifiers.PUBLIC, Modifiers.STATIC)) + ) + .addMethod(Builders.method(current) + .setThrowsExceptions(CodeType.ofClass("java.lang.IllegalAccessException")) + .setModifiers(Set.of(Modifiers.PRIVATE)) + .setJavadoc(CodeJavadoc.combineLines( + CodeJavadoc.text("The constructor is inaccessible."), + CodeJavadoc.blank(), + CodeJavadoc.throwsMeta(CodeType.ofClass("java.lang.IllegalAccessException"), "always") + )) + .setMethodStatements(List.of( + CodeStatement.throwStatement(Builders.ctorInvocation(CodeType.ofClass("java.lang.IllegalAccessException")) + .addParameter(CodeExpression.string("This class cannot be instantiated.")) + ) + )) + .buildConstructor() + ) + .build(); + } +} diff --git a/processor/common/src/test/java/net/strokkur/commands/internal/codegen/ImportGatherTests.java b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/ImportGatherTests.java new file mode 100644 index 00000000..5843e6af --- /dev/null +++ b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/ImportGatherTests.java @@ -0,0 +1,267 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen; + +import net.strokkur.commands.internal.codegen.builder.Builders; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitable; +import net.strokkur.commands.internal.printer.source.ImportGatheringVisitor; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertLinesMatch; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ImportGatherTests { + + private void check(String expectedMultiline, CodeVisitable visitable) { + final Set packages = visitable.accept(new ImportGatheringVisitor()); + final Set imports = packages.stream().map(CodeType::fullyQualifiedName).collect(Collectors.toSet()); + + if (expectedMultiline.isBlank()) { + assertEquals(0, imports.size()); + return; + } + + final Set expected = Arrays.stream(expectedMultiline.strip().split("\n")) + .collect(Collectors.toSet()); + assertLinesMatch(expected.stream().sorted(), imports.stream().sorted()); + } + + @Test + void testPackageVisitThrows() { + assertThrows(IllegalStateException.class, () -> { + CodePackage.of("com.example").accept(new ImportGatheringVisitor()); + }); + } + + @Test + void testGatherExpressionImports() { + // Test static method invocation + check(""" + com.example.Test""", Builders.methodInvocation(Builders.method(CodeClass.simple("com.example.Test"), "execute") + .setModifiers(Set.of(Modifiers.STATIC))) + .getAsExpression() + ); + + // Test static method invocation with parameters + check(""" + com.example.Test + io.library.Value + """, Builders.methodInvocation(Builders.method(CodeClass.simple("com.example.Test"), "execute") + .setModifiers(Set.of(Modifiers.STATIC))) + .addParameter(Builders.ctorInvocation(CodeType.ofClass("io.library.Value"))) + .getAsExpression() + ); + + // Test instance method invocation + check("", Builders.methodInvocation(Builders.method(CodeClass.simple("com.example.Test"), "execute")) + .setInstanceVariable("this") + .getAsExpression() + ); + + // Test instance method invocation with parameters + check(""" + io.library.Value + """, Builders.methodInvocation(Builders.method(CodeClass.simple("com.example.Test"), "execute")) + .addParameter(Builders.ctorInvocation(CodeType.ofClass("io.library.Value"))) + .setInstanceVariable("this") + .getAsExpression() + ); + + // Test method reference + check( + "io.library.Example", + CodeExpression.methodReference(CodeType.ofClass("io.library.Example"), "run") + ); + } + + @Test + void testFields() { + // No initializer + check(""" + java.lang.String + """, Builders.field("field", CodeType.ofClass(CodeClass.STRING)) + .build() + ); + + // With initializer + check(""" + java.lang.String + com.example.TestClass + """, Builders.field("field", CodeType.ofClass(CodeClass.STRING)) + .setInitialiser( + Builders.methodInvocation(Builders.method(CodeClass.simple("com.example.TestClass"), "get") + .setModifiers(Set.of(Modifiers.STATIC)) + ) + ) + .build() + ); + } + + @Test + void testStatements() { + // Variable declaration (no init) + check(""" + java.lang.String + """, CodeStatement.variableDeclaration( + CodeType.ofClass(CodeClass.STRING), + "name", + null + )); + + // Variable declaration (with init) + check(""" + java.lang.String + com.example.TheClass + """, CodeStatement.variableDeclaration( + CodeType.ofClass(CodeClass.STRING), + "name", + Builders.methodInvocation(Builders.method(CodeClass.simple("com.example.TheClass"), "get") + .setModifiers(Set.of(Modifiers.STATIC)) + ) + )); + + // Return statement (no value) + check("", CodeStatement.returnStatement(null)); + // Return statement (with value) + check("", CodeStatement.returnStatement( + CodeExpression.nullExpr() + )); + + // Throw statement + check("java.lang.NullPointerException", CodeStatement.throwStatement( + Builders.ctorInvocation(CodeType.ofClass("java.lang.NullPointerException")) + .addParameter(CodeExpression.string("It was null :(")) + )); + + // Method invocation (instance) + check("", Builders.methodInvocation(Builders.method(CodeClass.simple("io.declared.ThisClass"), "doSomething")) + .addParameter(CodeExpression.string("test")) + .getAsStatement() + ); + // Method invocation (static) + check("io.declared.ThisClass", Builders.methodInvocation(Builders.method(CodeClass.simple("io.declared.ThisClass"), "doSomething") + .setModifiers(Set.of(Modifiers.STATIC))) + .addParameter(CodeExpression.string("test")) + .getAsStatement() + ); + } + + @Test + void testArray() { + check("io.library.TestClass", CodeType.ofArray(CodeType.ofClass("io.library.TestClass"))); + check("java.lang.String", CodeType.STRING_ARRAY); + // String[][] + check("java.lang.String", CodeType.ofArray(CodeType.STRING_ARRAY)); + } + + @Test + void testLambda() { + check("io.library.SomeTest", CodeExpression.lambda(List.of(), Builders.methodInvocation("do") + .setStatic() + .setType(CodeType.ofClass("io.library.SomeTest")) + )); + check(""" + io.library.SomeTest + io.library.AnotherTest + """, CodeExpression.lambda(List.of(), + Builders.methodInvocation("do") + .setStatic() + .setType(CodeType.ofClass("io.library.SomeTest")), + Builders.methodInvocation("anotherOne") + .setStatic() + .setType(CodeType.ofClass("io.library.AnotherTest")) + )); + } + + @Test + void testIfStatement() { + // With instanceof + check("org.bukkit.entity.Player", CodeStatement.ifStmt( + CodeExpression.instanceofExpr( + Builders.methodInvocation("getSource").setInstanceVariable("ctx"), + CodeType.ofClass("org.bukkit.entity.Player"), + "source" + ), + Builders.methodInvocation("sendPlainMessage") + .setInstanceVariable("source") + .addParameter(CodeExpression.string("Hello!")) + )); + + check(""" + java.lang.IllegalStateException + io.library.StaticLibrary + org.bukkit.entity.Player + """, CodeStatement.ifStmt( + CodeExpression.instanceofExpr( + Builders.methodInvocation("getSource") + .setStatic() + .setType(CodeType.ofClass("io.library.StaticLibrary")), + CodeType.ofClass("org.bukkit.entity.Player"), + null + ).invert(), + CodeStatement.throwStatement(Builders.ctorInvocation(CodeType.ofClass("java.lang.IllegalStateException")) + .addParameter(CodeExpression.string("Don't do that.")) + )) + ); + } + + @Test + void testFieldAccess() { + // None of these have any imports + check("", Builders.fieldAccess("value").build()); + check("", Builders.fieldAccess("value").setSource(CodeExpression.variable("inst")).build()); + check("", Builders.fieldAccess("value") + .setSource(CodeExpression.variable("inst")) + .setType(CodeType.ofClass("some.ClassType")) + .build()); + check("", Builders.fieldAccess("value") + .setType(CodeType.ofClass("some.ClassType")) + .build()); + + // These should fetch the same imports as the source expr (including static types) + check("some.ClassType", Builders.fieldAccess("value") + .setStatic(CodeType.ofClass("some.ClassType")) + .build() + ); + check("another.ClassType", Builders.fieldAccess("value") + .setSource(Builders.methodInvocation("fetch") + .setStatic() + .setType(CodeType.ofClass("another.ClassType"))) + .build() + ); + + // They can be nested! + check("yet.Yet", Builders.fieldAccess("value") + .setSource(Builders.fieldAccess("another") + .setStatic(CodeType.ofClass("yet.Yet")) + ) + .build() + ); + } + + @Test + void testBlank() { + check("", CodeStatement.blank()); + } +} diff --git a/processor/common/src/test/java/net/strokkur/commands/internal/codegen/JavaCodeGenTests.java b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/JavaCodeGenTests.java new file mode 100644 index 00000000..d7b2b379 --- /dev/null +++ b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/JavaCodeGenTests.java @@ -0,0 +1,515 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen; + +import net.strokkur.commands.internal.codegen.builder.Builders; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitable; +import net.strokkur.commands.internal.printer.javadoc.JavaMarkdownJavadocVisitor; +import net.strokkur.commands.internal.printer.source.JavaSourcePrintingVisitor; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class JavaCodeGenTests { + private static final CodeClass EXAMPLE_CLASS = Builders.classBuilder("com.example.ExampleClass").build(); + + private void check(String expected, CodeVisitable visitable) { + final JavaSourcePrintingVisitor visitor = new JavaSourcePrintingVisitor(JavaMarkdownJavadocVisitor::new, " ", " "); + final String actual = visitable.accept(visitor).toString(); + assertEquals(expected, actual); + } + + @Test + void testPackageThrows() { + assertThrows(IllegalStateException.class, () -> { + CodePackage.of("com.example").accept(new JavaSourcePrintingVisitor(JavaMarkdownJavadocVisitor::new, " ", " ")); + }); + } + + @Test + void testClass() { + // language=java + final String expected = """ + class ExampleClass { + } + """; + check(expected, EXAMPLE_CLASS); + + // language=java + final String expectedWithFields = """ + class ExampleClass { + String oneField; + int anotherField; + } + """; + check(expectedWithFields, Builders.classBuilder(EXAMPLE_CLASS.fullyQualifiedName()) + .addField(Builders.field("oneField", CodeType.ofClass(CodeClass.STRING))) + .addField(Builders.field("anotherField", CodeType.INT)) + .build() + ); + + // language=java + final String expectedWithMethods = """ + class ExampleClass { + + String oneMethod() { + } + + int anotherMethod() { + } + } + """; + check(expectedWithMethods, Builders.classBuilder(EXAMPLE_CLASS.fullyQualifiedName()) + .addMethod(Builders.method(EXAMPLE_CLASS, "oneMethod") + .setReturnType(CodeType.ofClass(CodeClass.STRING)) + ) + .addMethod(Builders.method(EXAMPLE_CLASS, "anotherMethod") + .setReturnType(CodeType.INT) + ) + .build() + ); + + // language=java + final String expectedCombined = """ + class ExampleClass { + String oneField; + int anotherField; + + static String oneStaticMethod() { + } + + static int anotherStaticMethod() { + } + + ExampleClass() { + } + + String oneInstanceMethod() { + } + + int anotherInstanceMethod() { + } + } + """; + check(expectedCombined, Builders.classBuilder(EXAMPLE_CLASS.fullyQualifiedName()) + .addField(Builders.field("oneField", CodeType.ofClass(CodeClass.STRING))) + .addField(Builders.field("anotherField", CodeType.INT)) + .addMethod(Builders.method(EXAMPLE_CLASS, "oneStaticMethod") + .setModifiers(Set.of(Modifiers.STATIC)) + .setReturnType(CodeType.ofClass(CodeClass.STRING)) + ) + .addMethod(Builders.method(EXAMPLE_CLASS, "anotherStaticMethod") + .setModifiers(Set.of(Modifiers.STATIC)) + .setReturnType(CodeType.INT) + ) + .addMethod(Builders.method(EXAMPLE_CLASS, "oneInstanceMethod") + .setReturnType(CodeType.ofClass(CodeClass.STRING)) + ) + .addMethod(Builders.method(EXAMPLE_CLASS, "anotherInstanceMethod") + .setReturnType(CodeType.INT) + ) + .addMethod(Builders.method(EXAMPLE_CLASS) + .buildConstructor() + ) + .build() + ); + } + + @Test + void testMethod() { + // language=java + final String expected = """ + void method() { + } + """; + check(expected, Builders.method(EXAMPLE_CLASS, "method").build()); + + // language=java + final String expectedWithModifiers = """ + public static void method() { + } + """; + check(expectedWithModifiers, Builders.method(EXAMPLE_CLASS, "method") + .setModifiers(Set.of(Modifiers.PUBLIC, Modifiers.STATIC)) + .build()); + + // language=java + final String expectedWithStatements = """ + void method() { + otherMethod(STATIC_VALUE, "Now"); + } + """; + check(expectedWithStatements, Builders.method(EXAMPLE_CLASS, "method") + .setMethodStatements(List.of( + Builders.methodInvocation(Builders.method(EXAMPLE_CLASS, "otherMethod")) + .addParameter(CodeExpression.variable("STATIC_VALUE")) + .addParameter(CodeExpression.string("Now")) + )).build() + ); + + // language=java + final String expectedWithThrows = """ + void method() throws NullPointerException, SQLException { + throw new SQLException("No database present :("); + } + """; + check(expectedWithThrows, Builders.method(EXAMPLE_CLASS, "method") + .setThrowsExceptions( + CodeType.ofClass("java.lang.NullPointerException"), + CodeType.ofClass("java.sql.SQLException") + ) + .setMethodStatements(List.of( + CodeStatement.throwStatement(Builders.ctorInvocation(CodeType.ofClass("java.sql.SQLException")) + .addParameter(CodeExpression.string("No database present :(")) + ) + )) + .build()); + } + + @Test + void testField() { + // language=java + final String expectedNoInit = """ + String someField; + """; + check(expectedNoInit, Builders.field("someField", CodeType.ofClass(CodeClass.STRING)).build()); + + // language=java + final String expectedWithInit = """ + String someField = "some value"; + """; + check(expectedWithInit, Builders.field("someField", CodeType.ofClass(CodeClass.STRING)) + .setInitialiser(CodeExpression.string("some value")) + .build()); + + // language=java + final String expectedWithModifiers = """ + public static final String SOME_FIELD; + """; + check(expectedWithModifiers, Builders.field("SOME_FIELD", CodeType.ofClass(CodeClass.STRING)) + .setModifiers(Set.of(Modifiers.PUBLIC, Modifiers.STATIC, Modifiers.FINAL)) + .build()); + } + + @Test + void testStatement() { + final @JavaStatements String expectedReturnStmtEmpty = """ + return; + """; + check(expectedReturnStmtEmpty, CodeStatement.returnStatement(null)); + + final @JavaStatements String expectedReturnStmtWithExpr = """ + return value; + """; + check(expectedReturnStmtWithExpr, CodeStatement.returnStatement( + CodeExpression.variable("value") + )); + + final @JavaStatements String expectedVariableDeclaration = """ + String value = "burger"; + """; + check(expectedVariableDeclaration, CodeStatement.variableDeclaration( + CodeType.ofClass(CodeClass.STRING), + "value", + CodeExpression.string("burger") + )); + + final @JavaStatements String expectedVariableDeclarationNoInit = """ + String value; + """; + check(expectedVariableDeclarationNoInit, CodeStatement.variableDeclaration( + CodeType.ofClass(CodeClass.STRING), + "value", + null + )); + } + + @Test + void methodInvocationFormat() { + final @JavaStatements String multiline = """ + String + .getValue() + .stream().toLol() + .toList(); + """; + check(multiline, Builders.methodInvocation("getValue") + .setStatic() + .setType(CodeType.STRING) + .setNewline() + .chain("stream", InvokesMethod.StyleConfig.NEWLINE) + .chain("toLol") + .chain("toList", InvokesMethod.StyleConfig.NEWLINE) + .getAsStatement() + ); + } + + @Test + void testLambda() { + final @JavaStatements String simpleLambdaParam = """ + builder.requires(source -> source.hasPermission("testcommand.use")); + """; + check(simpleLambdaParam, Builders.methodInvocation("requires") + .setInstanceVariable("builder") + .addParameter(CodeExpression.lambda(List.of("source"), Builders.methodInvocation("hasPermission") + .setInstanceVariable("source") + .addParameter(CodeExpression.string("testcommand.use")) + )) + .getAsStatement() + ); + + final @JavaStatements String simpleMultilineLambdaParam = """ + builder.executes(ctx -> { + instance.run(ctx.getSource()); + return; + }); + """; + check(simpleMultilineLambdaParam, Builders.methodInvocation("executes") + .setInstanceVariable("builder") + .addParameter(CodeExpression.lambda(List.of("ctx"), + Builders.methodInvocation("run") + .setInstanceVariable("instance") + .addParameter(Builders.methodInvocation("getSource").setInstanceVariable("ctx")), + CodeStatement.returnStatement(null) + )) + .getAsStatement() + ); + + final @JavaStatements String singleStatementMultilineLambdaParam = """ + launchTask(() -> { + run("Second"); + }, "First"); + """; + check(singleStatementMultilineLambdaParam, Builders.methodInvocation("launchTask") + .addParameter(CodeExpression.lambda(List.of(), + Builders.methodInvocation("run") + .addParameter(CodeExpression.string("Second")) + .getAsStatement() + )) + .addParameter(CodeExpression.string("First")) + .getAsStatement() + ); + + final @JavaStatements String testMultiParam = """ + sortBy((a, b) -> Integer.compare(a, b)); + """; + check(testMultiParam, Builders.methodInvocation("sortBy") + .addParameter(CodeExpression.lambda(List.of("a", "b"), + Builders.methodInvocation("compare") + .setStatic() + .setType(CodeType.ofClass("java.lang.Integer")) + .addParameter(CodeExpression.variable("a")) + .addParameter(CodeExpression.variable("b")) + )) + .getAsStatement() + ); + } + + @Test + void testIfStatement() { + final @JavaStatements String expected = """ + if (ctx.getSource() instanceof Player source) { + source.sendPlainMessage("Hello!"); + } + """; + check(expected, CodeStatement.ifStmt( + CodeExpression.instanceofExpr( + Builders.methodInvocation("getSource").setInstanceVariable("ctx"), + CodeType.ofClass("org.bukkit.entity.Player"), + "source" + ), + Builders.methodInvocation("sendPlainMessage") + .setInstanceVariable("source") + .addParameter(CodeExpression.string("Hello!")) + )); + + final @JavaStatements String expectedInverted = """ + if (!(ctx.getSource() instanceof Player)) { + throw new IllegalStateException("Don't do that."); + } + """; + check(expectedInverted, CodeStatement.ifStmt( + CodeExpression.instanceofExpr( + Builders.methodInvocation("getSource").setInstanceVariable("ctx"), + CodeType.ofClass("org.bukkit.entity.Player"), + null + ).invert(), + CodeStatement.throwStatement(Builders.ctorInvocation(CodeType.ofClass("java.lang.IllegalStateException")) + .addParameter(CodeExpression.string("Don't do that.")) + ) + )); + + final @JavaStatements String expectedWithNestedCtor = """ + if (!(ctx.getSource() instanceof Player)) { + throw new SimpleCommandExceptionType( + new LiteralMessage("This command requires a player sender!") + ).create(); + } + """; + check(expectedWithNestedCtor, CodeStatement.ifStmt( + CodeExpression.instanceofExpr( + Builders.methodInvocation("getSource").setInstanceVariable("ctx"), + CodeType.ofClass("org.bukkit.entity.Player"), + null + ).invert(), + CodeStatement.throwStatement(Builders.ctorInvocation(CodeType.ofClass("brigadier.SimpleCommandExceptionType")) + .setMultilineParameters() + .addParameter(Builders.ctorInvocation(CodeType.ofClass("brigadier.LiteralMessage")) + .addParameter(CodeExpression.string("This command requires a player sender!")) + ) + .chain("create") + ) + )); + } + + @Test + void testFieldAccess() { + check("value", Builders.fieldAccess("value").build()); + check("inst.value", Builders.fieldAccess("value").setSource(CodeExpression.variable("inst")).build()); + check("inst.value", Builders.fieldAccess("value") + .setSource(CodeExpression.variable("inst")) + .setType(CodeType.ofClass("some.ClassType")) + .build()); + check("value", Builders.fieldAccess("value") + .setType(CodeType.ofClass("some.ClassType")) + .build()); + + check("ClassType.value", Builders.fieldAccess("value") + .setStatic(CodeType.ofClass("some.ClassType")) + .build() + ); + check("ClassType.fetch().value", Builders.fieldAccess("value") + .setSource(Builders.methodInvocation("fetch") + .setStatic() + .setType(CodeType.ofClass("another.ClassType"))) + .build() + ); + + check("Yet.another.value", Builders.fieldAccess("value") + .setSource(Builders.fieldAccess("another") + .setStatic(CodeType.ofClass("yet.Yet")) + ) + .build() + ); + } + + @Test + void testFullExecutesMethod() { + final @JavaStatements String expected = """ + builder.executes(ctx -> { + if (!(ctx.getSource() instanceof Player source)) { + throw new SimpleCommandExceptionType( + new LiteralMessage("This command requires a player sender!") + ).create(); + } + + instance.run(source); + return Command.SINGLE_SUCCESS; + }); + """; + check(expected, Builders.methodInvocation("executes") + .setInstanceVariable("builder") + .addParameter(CodeExpression.lambda( + List.of("ctx"), + CodeStatement.ifStmt( + CodeExpression.instanceofExpr( + Builders.methodInvocation("getSource").setInstanceVariable("ctx"), + CodeType.ofClass("bukkit.Player"), + "source" + ).invert(), + CodeStatement.throwStatement( + Builders.ctorInvocation(CodeType.ofClass("brigadier.SimpleCommandExceptionType")) + .setMultilineParameters() + .addParameter(Builders.ctorInvocation(CodeType.ofClass("brigadier.LiteralMessage")) + .addParameter(CodeExpression.string("This command requires a player sender!"))) + .chain("create") + ) + ), + CodeStatement.blank(), + Builders.methodInvocation("run") + .setInstanceVariable("instance") + .addParameter(CodeExpression.variable("source")), + CodeStatement.returnStatement(Builders.fieldAccess("SINGLE_SUCCESS") + .setStatic(CodeType.ofClass("brigadier.Command")) + ) + )) + .getAsStatement() + ); + } + + @Test + void testAdvancedRegisterMethod() { + // language=java + final String expected = """ + class BrigadierTest { + + public void register(ProxyServer server, Object command$plugin) { + final BrigadierCommand command = new BrigadierCommand(create()); + final CommandMeta meta = server.getCommandManager().metaBuilder(command) + .aliases(ALIASES.toArray(String[]::new)) + .plugin(command$plugin) + .build(); + + server.getCommandManager().register(meta, command); + } + } + """; + check(expected, Builders.classBuilder("pkg.BrigadierTest") + .addMethod(Builders.method("register") + .setModifiers(Set.of(Modifiers.PUBLIC)) + .addParameter(CodeType.ofClass("velocity.ProxyServer"), "server") + .addParameter(CodeType.ofClass("java.lang.Object"), "command$plugin") + .setMethodStatements(List.of( + // BrigadierCommand command + CodeStatement.variableDeclarationFinal( + CodeType.ofClass("velocity.BrigadierCommand"), + "command", + Builders.ctorInvocation(CodeType.ofClass("velocity.BrigadierCommand")) + .addParameter(Builders.methodInvocation("create")) + ), + + // CommandMeta meta + CodeStatement.variableDeclarationFinal( + CodeType.ofClass("velocity.CommandMeta"), + "meta", + Builders.methodInvocation("getCommandManager") + .setInstanceVariable("server") + .chain("metaBuilder", CodeExpression.variable("command")) + .chain("aliases", InvokesMethod.StyleConfig.NEWLINE, Builders.methodInvocation("toArray") + .setInstanceVariable("ALIASES") + .addParameter(CodeExpression.methodReference(CodeType.STRING_ARRAY, "new")) + ) + .chain("plugin", InvokesMethod.StyleConfig.NEWLINE, CodeExpression.variable("command$plugin")) + .chain("build", InvokesMethod.StyleConfig.NEWLINE) + ), + + CodeStatement.blank(), + + // register call + Builders.methodInvocation("getCommandManager") + .setInstanceVariable("server") + .chain("register", CodeExpression.variable("meta"), CodeExpression.variable("command")) + )) + ) + .build() + ); + } +} diff --git a/processor/common/src/test/java/net/strokkur/commands/internal/codegen/JavaStatements.java b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/JavaStatements.java new file mode 100644 index 00000000..02a6d172 --- /dev/null +++ b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/JavaStatements.java @@ -0,0 +1,31 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen; + +import org.intellij.lang.annotations.Language; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Language( + value = "JAVA", + prefix = "class Wrapper__ { Object __method() { if (true) {", + suffix = "} return null; } }") +@Retention(RetentionPolicy.SOURCE) +public @interface JavaStatements { +} diff --git a/processor/common/src/test/java/net/strokkur/commands/internal/codegen/JavadocMarkdownVisitorTests.java b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/JavadocMarkdownVisitorTests.java new file mode 100644 index 00000000..0c7f2c69 --- /dev/null +++ b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/JavadocMarkdownVisitorTests.java @@ -0,0 +1,145 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen; + +import net.strokkur.commands.internal.codegen.builder.Builders; +import net.strokkur.commands.internal.printer.javadoc.JavaMarkdownJavadocVisitor; +import org.intellij.lang.annotations.Language; +import org.junit.jupiter.api.Test; + +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.blank; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.classReference; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.combine; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.combineLines; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.methodReference; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.newline; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.see; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.text; + +class JavadocMarkdownVisitorTests extends CommonJavadocVisitorTests { + @Test + void testJavaMarkdownJavadocsClass() { + // language=java + final String expected = """ + /// A class holding the Brigadier source tree generated from + /// [com.example.CommandClass] using [StrokkCommands](https://commands.strokkur.net) + /// + /// @author Strokkur24 - StrokkCommands + /// @version 2.0.0 + /// @see #create() creating the LiteralCommandNode + /// @see #register(io.papermc.paper.command.brigadier.Commands) registering the LiteralCommandNode"""; + checkOutput(expected, classJavadoc(), JavaMarkdownJavadocVisitor::new); + } + + @Test + void testJavaMarkdownJavadocsCreate() { + // language=java + final String expected = """ + /// A method for creating a Brigadier command node which denotes the declared command + /// in [com.example.CommandClass]. You can either retrieve the unregistered node with this method + /// or register it directly with [#register(io.papermc.paper.command.brigadier.Commands)]."""; + checkOutput(expected, createJd(), JavaMarkdownJavadocVisitor::new); + } + + @Test + void testJavaMarkdownJavadocsRegister() { + // language=java + final String expected = """ + /// Shortcut for registering the command node returned from + /// [#create()]. This method uses the provided aliases + /// and description from the original source file. + /// + /// ### Registering the command + /// + /// This method can safely be called either in your plugin bootstrapper's + /// [io.papermc.paper.plugin.bootstrap.PluginBootstrap#bootstrap(io.papermc.paper.plugin.bootstrap.BootstrapContext)] or your main + /// class' [org.bukkit.plugin.java.JavaPlugin#onLoad()] or [org.bukkit.plugin.java.JavaPlugin#onEnable()] + /// methods. + /// + /// You need to call it inside of a lifecycle event. General information can be found on the + /// [PaperMC Lifecycle API docs page](https://docs.papermc.io/paper/dev/lifecycle/). + /// + /// The general use case might look like this (example given inside the `onEnable` method): + /// ``` + /// this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS.newHandler(event -> { + /// final Commands commands = event.registrar(); + /// EntitiesCommandBrigadier.register(commands); + /// } + /// ```"""; + checkOutput(expected, registerJavadoc(), JavaMarkdownJavadocVisitor::new); + } + + @Test + void testJavaMarkdownJavadocsCtor() { + // language=java + final String expected = """ + /// The constructor is not accessible. There is no need for an instance + /// to be created, as no state is stored and all methods are static. + /// + /// @throws IllegalAccessException always"""; + checkOutput(expected, ctorJd(), JavaMarkdownJavadocVisitor::new); + } + + @Test + void testNamedClassReference() { + @Language("JAVA") final String expected = """ + /// This [environment][ProcessEnvironment] does not help me + /// at all."""; + checkOutput(expected, combineLines( + combine(text("This "), classReference(CodeClass.simple("java.lang.ProcessEnvironment"), "environment"), text(" does not help me")), + text("at all.") + ), JavaMarkdownJavadocVisitor::new); + } + + @Test + void testNamedMethodReference() { + @Language("JAVA") final String expected = """ + /// Use the [builder][#builder()] for quick access."""; + checkOutput( + expected, + combine( + text("Use the "), + methodReference(Builders.method(CodeClass.simple("none.None"), "builder").build(), "builder", true), + text(" for quick access.") + ), + JavaMarkdownJavadocVisitor::new + ); + } + + @Test + void testMiscCodeTypes() { + // language=java + final String expected = """ + /// This is text with a + /// new line. + /// + /// @see String#concat(String)"""; + checkOutput( + expected, + combineLines( + combine(text("This is text with a"), newline(), text("new line.")), + blank(), + see(Builders.method(CodeClass.STRING, "concat") + .addParameter(CodeType.ofClass(CodeClass.STRING), "") + .build(), null + ) + ), + JavaMarkdownJavadocVisitor::new + ); + } +} diff --git a/processor/common/src/test/java/net/strokkur/commands/internal/codegen/JavadocStarVisitorTests.java b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/JavadocStarVisitorTests.java new file mode 100644 index 00000000..2a088a84 --- /dev/null +++ b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/JavadocStarVisitorTests.java @@ -0,0 +1,133 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen; + +import net.strokkur.commands.internal.codegen.builder.Builders; +import net.strokkur.commands.internal.printer.javadoc.JavaStarJavadocVisitor; +import org.junit.jupiter.api.Test; + +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.classReference; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.combine; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.combineLines; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.methodReference; +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.text; + +class JavadocStarVisitorTests extends CommonJavadocVisitorTests { + @Test + void testJavaStarJavadocsClass() { + // language=java + final String expected = """ + /** + * A class holding the Brigadier source tree generated from + * {@link com.example.CommandClass} using StrokkCommands + * + * @author Strokkur24 - StrokkCommands + * @version 2.0.0 + * @see #create() creating the LiteralCommandNode + * @see #register(io.papermc.paper.command.brigadier.Commands) registering the LiteralCommandNode + */"""; + checkOutput(expected, classJavadoc(), JavaStarJavadocVisitor::new); + } + + @Test + void testJavaStarJavadocsCreate() { + // language=java + final String expected = """ + /** + * A method for creating a Brigadier command node which denotes the declared command + * in {@link com.example.CommandClass}. You can either retrieve the unregistered node with this method + * or register it directly with {@link #register(io.papermc.paper.command.brigadier.Commands)}. + */"""; + checkOutput(expected, createJd(), JavaStarJavadocVisitor::new); + } + + @Test + void testJavaStarJavadocsRegister() { + // language=java + final String expected = """ + /** + * Shortcut for registering the command node returned from + * {@link #create()}. This method uses the provided aliases + * and description from the original source file. + * + *

Registering the command

+ * + * This method can safely be called either in your plugin bootstrapper's + * {@link io.papermc.paper.plugin.bootstrap.PluginBootstrap#bootstrap(io.papermc.paper.plugin.bootstrap.BootstrapContext)} or your main + * class' {@link org.bukkit.plugin.java.JavaPlugin#onLoad()} or {@link org.bukkit.plugin.java.JavaPlugin#onEnable()} + * methods. + *

+ * You need to call it inside of a lifecycle event. General information can be found on the + * PaperMC Lifecycle API docs page. + *

+ * The general use case might look like this (example given inside the {@code onEnable} method): + *

{@code
+         * this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS.newHandler(event -> {
+         *     final Commands commands = event.registrar();
+         *     EntitiesCommandBrigadier.register(commands);
+         * }
+         * }
+ */"""; + checkOutput(expected, registerJavadoc(), JavaStarJavadocVisitor::new); + } + + @Test + void testJavaStarJavadocsCtor() { + // language=java + final String expected = """ + /** + * The constructor is not accessible. There is no need for an instance + * to be created, as no state is stored and all methods are static. + * + * @throws IllegalAccessException always + */"""; + checkOutput(expected, ctorJd(), JavaStarJavadocVisitor::new); + } + + @Test + void testNamedClassReference() { + // language=java + final String expected = """ + /** + * This {@link ProcessEnvironment environment} does not help me + * at all. + */"""; + checkOutput(expected, combineLines( + combine(text("This "), classReference(CodeClass.simple("java.lang.ProcessEnvironment"), "environment"), text(" does not help me")), + text("at all.") + ), JavaStarJavadocVisitor::new); + } + + @Test + void testNamedMethodReference() { + // language=java + final String expected = """ + /** + * Use the {@link #builder() builder} for quick access. + */"""; + checkOutput( + expected, + combine( + text("Use the "), + methodReference(Builders.method(CodeClass.simple("none.None"), "builder").build(), "builder", true), + text(" for quick access.") + ), + JavaStarJavadocVisitor::new + ); + } +} diff --git a/processor/common/src/test/java/net/strokkur/commands/internal/codegen/builder/MiscBuilderTests.java b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/builder/MiscBuilderTests.java new file mode 100644 index 00000000..1503f19f --- /dev/null +++ b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/builder/MiscBuilderTests.java @@ -0,0 +1,33 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen.builder; + +import net.strokkur.commands.internal.codegen.CodePackage; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class MiscBuilderTests { + + @Test + void ensureClassBuilderOverloadsDoTheSame() { + Assertions.assertEquals( + Builders.classBuilder("com.example.ClassName"), + Builders.classBuilder("ClassName", CodePackage.of("com.example")) + ); + } +} diff --git a/processor/common/src/test/java/net/strokkur/commands/internal/codegen/builder/package-info.java b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/builder/package-info.java new file mode 100644 index 00000000..4e3b98a0 --- /dev/null +++ b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/builder/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package net.strokkur.commands.internal.codegen.builder; + +import org.jspecify.annotations.NullMarked; diff --git a/processor/common/src/test/java/net/strokkur/commands/internal/codegen/integration/FullVelocityIntegrationTest.java b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/integration/FullVelocityIntegrationTest.java new file mode 100644 index 00000000..7561071a --- /dev/null +++ b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/integration/FullVelocityIntegrationTest.java @@ -0,0 +1,360 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.codegen.integration; + +import net.strokkur.commands.internal.codegen.CodeAnnotation; +import net.strokkur.commands.internal.codegen.CodeClass; +import net.strokkur.commands.internal.codegen.CodeExpression; +import net.strokkur.commands.internal.codegen.CodeStatement; +import net.strokkur.commands.internal.codegen.CodeType; +import net.strokkur.commands.internal.codegen.InvokesMethod; +import net.strokkur.commands.internal.codegen.Modifiers; +import net.strokkur.commands.internal.codegen.builder.Builders; +import net.strokkur.commands.internal.codegen.builder.MethodBuilder; +import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc; +import net.strokkur.commands.internal.printer.javadoc.JavaStarJavadocVisitor; +import net.strokkur.commands.internal.printer.source.AbstractSourcePrintingVisitor; +import net.strokkur.commands.internal.printer.source.ImportGatheringVisitor; +import net.strokkur.commands.internal.printer.source.JavaSourcePrintingVisitor; +import org.intellij.lang.annotations.Language; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class FullVelocityIntegrationTest { + private static final CodeType.ClassType COMMAND = CodeType.ofClass("com.mojang.brigadier.Command"); + private static final CodeType.ClassType LITERAL_MESSAGE = CodeType.ofClass("com.mojang.brigadier.LiteralMessage"); + private static final CodeType.ClassType STRING_ARGUMENT_TYPE = CodeType.ofClass("com.mojang.brigadier.arguments.StringArgumentType"); + private static final CodeType.ClassType COMMAND_SOURCE = CodeType.ofClass("com.velocitypowered.api.command.CommandSource"); + private static final CodeType.ClassType LITERAL_ARGUMENT_BUILDER = CodeType.ofClassTyped( + "com.mojang.brigadier.builder.LiteralArgumentBuilder", COMMAND_SOURCE + ); + private static final CodeType.ClassType SIMPLE_COMMAND_EXCEPTION_TYPE = CodeType.ofClass("com.mojang.brigadier.exceptions.SimpleCommandExceptionType"); + private static final CodeType.ClassType BRIGADIER_COMMAND = CodeType.ofClass("com.velocitypowered.api.command.BrigadierCommand"); + private static final CodeType.ClassType COMMAND_META = CodeType.ofClass("com.velocitypowered.api.command.CommandMeta"); + private static final CodeType.ClassType PLAYER = CodeType.ofClass("com.velocitypowered.api.proxy.Player"); + private static final CodeType.ClassType PROXY_SERVER = CodeType.ofClass("com.velocitypowered.api.proxy.ProxyServer"); + private static final CodeType.ClassType INJECT = CodeType.ofClass("jakarta.inject.Inject"); + private static final CodeType.ClassType NULL_MARKED = CodeType.ofClass("org.jspecify.annotations.NullMarked"); + + private static final CodeType.ClassType TEST_COMMAND = CodeType.ofClass("net.strokkur.testplugin.velocity.reference.TestCommand"); + private static final CodeType.ClassType PROXY_INITIALIZE_EVENT = CodeType.ofClass("com.velocitypowered.api.event.proxy.ProxyInitializeEvent"); + + // + private static final Set EXPECTED_IMPORTED_CLASSES = Set.of( + COMMAND, LITERAL_MESSAGE, STRING_ARGUMENT_TYPE, LITERAL_ARGUMENT_BUILDER, + SIMPLE_COMMAND_EXCEPTION_TYPE, BRIGADIER_COMMAND, COMMAND_META, + COMMAND_SOURCE, PLAYER, PROXY_SERVER, INJECT, NULL_MARKED, CodeType.LIST + ); + + @Language("JAVA") + private static final String EXPECTED_CODE = """ + /** + * A class holding the Brigadier source tree generated from + * {@link TestCommand} using StrokkCommands. + * + * @author Strokkur24 - StrokkCommands + * @version 2.1.3 + * @see #create() creating the LiteralArgumentBuilder + * @see #register(ProxyServer, Object) registering the command + */ + @NullMarked + public final class TestCommandBrigadier { + public static final String NAME = "testcommand"; + public static final List ALIASES = List.of("test"); + + private @Inject TestCommand instance; + + /** + * Shortcut for registering the command node returned from + * {@link #create()}. This method uses the provided aliases + * from the original source file. + * + *

Registering the command

+ * + * Commands should only be registered during the {@link com.velocitypowered.api.event.proxy.ProxyInitializeEvent}. + * The example below shows an example of how to do this. For more information, + * refer to The Velocity Command API docs + * + *
{@code
+         * @Subscribe
+         * void onProxyInitialize(final ProxyInitializeEvent event) {
+         *   TestCommandBrigadier.register(this.proxy, this);
+         * }
+         * }
+ */ + public void register(ProxyServer server, Object command$plugin) { + final BrigadierCommand command = new BrigadierCommand(create()); + final CommandMeta meta = server.getCommandManager().metaBuilder(command) + .aliases(ALIASES.toArray(String[]::new)) + .plugin(command$plugin) + .build(); + + server.getCommandManager().register(meta, command); + } + + /** + * A method for creating a Brigadier command node which denotes the declared command + * in {@link TestCommand}. You can either retrieve the unregistered node with this method + * or register it directly with {@link #register(ProxyServer, Object)}. + */ + public LiteralArgumentBuilder create() { + return BrigadierCommand.literalArgumentBuilder(NAME) + .requires(source -> source.hasPermission("testcommand.use")) + .executes(ctx -> { + instance.execute(ctx.getSource()); + return Command.SINGLE_SUCCESS; + }) + .then(BrigadierCommand.literalArgumentBuilder("run") + .executes(ctx -> { + if (!(ctx.getSource() instanceof Player source)) { + throw new SimpleCommandExceptionType( + new LiteralMessage("This command requires a player sender!") + ).create(); + } + + instance.run(source); + return Command.SINGLE_SUCCESS; + }) + .then(BrigadierCommand.requiredArgumentBuilder("target", StringArgumentType.word()) + .executes(ctx -> { + instance.runWithTarget( + ctx.getSource(), + StringArgumentType.getString(ctx, "target") + ); + return Command.SINGLE_SUCCESS; + }) + ) + ); + } + } + """; + //
+ + @Test + void testFullExample() { + final CodeClass builtClass = buildClass(); + + final ImportGatheringVisitor importVisitor = new ImportGatheringVisitor(); + final Set imports = importVisitor.collectFilteredImports(builtClass); + + // Check if imports match + final List sortedImports = imports.stream().map(CodeType::fullyQualifiedName).sorted().toList(); + final List sortedExpectedImports = EXPECTED_IMPORTED_CLASSES.stream() + .map(CodeType::fullyQualifiedName).sorted().toList(); + + assertEquals(sortedExpectedImports.size(), sortedImports.size()); + for (int i = 0, sortedImportsSize = sortedImports.size(); i < sortedImportsSize; i++) { + final String expectedImport = sortedExpectedImports.get(i); + final String actualImport = sortedImports.get(i); + assertEquals(expectedImport, actualImport); + } + + // Check generated class file + final AbstractSourcePrintingVisitor sourceVisitor = new JavaSourcePrintingVisitor( + () -> new JavaStarJavadocVisitor(builtClass.codePackage(), imports), " ", " " + ); + + final StringBuilder result = builtClass.accept(sourceVisitor); + assertEquals(EXPECTED_CODE, result.toString()); + } + + private CodeClass buildClass() { + final MethodBuilder registerMethod = Builders.method("register") + .addParameter(PROXY_SERVER, "server") + .addParameter(CodeType.OBJECT, "command$plugin"); + final MethodBuilder createMethod = Builders.method("create"); + + return Builders.classBuilder(TEST_COMMAND.fullyQualifiedName() + "Brigadier") + .setJavadoc(CodeJavadoc.combineLines( + CodeJavadoc.text("A class holding the Brigadier source tree generated from"), + CodeJavadoc.combine( + CodeJavadoc.classReference(TEST_COMMAND.codeClass()), + CodeJavadoc.text(" using "), + CodeJavadoc.url("StrokkCommands", "https://commands.strokkur.net"), + CodeJavadoc.text(".") + ), + CodeJavadoc.blank(), + CodeJavadoc.author("Strokkur24 - StrokkCommands"), + CodeJavadoc.version("2.1.3"), + CodeJavadoc.see(createMethod, "creating the LiteralArgumentBuilder", true), + CodeJavadoc.see(registerMethod, "registering the command", true))) + .addAnnotations(CodeAnnotation.of(NULL_MARKED)) + .setModifiers(Modifiers.FINAL, Modifiers.PUBLIC) + + // Static fields + .addField(Builders.field("NAME", CodeType.STRING) + .setModifiers(Modifiers.PUBLIC, Modifiers.STATIC, Modifiers.FINAL) + .setInitialiser(CodeExpression.string("testcommand")) + ) + .addField(Builders.field("ALIASES", CodeType.LIST_STRING) + .setModifiers(Modifiers.PUBLIC, Modifiers.STATIC, Modifiers.FINAL) + .setInitialiser(Builders.methodInvocation("of") + .addParameter(CodeExpression.string("test")) + .setStatic(CodeType.LIST) + ) + ) + + // Instance fields + .addField(Builders.field("instance", TEST_COMMAND) + .addAnnotation(CodeAnnotation.of(INJECT)) + .setModifiers(Modifiers.PRIVATE) + ) + + // Register method + .addMethod(registerMethod + .setJavadoc(CodeJavadoc.combineLines( + CodeJavadoc.text("Shortcut for registering the command node returned from"), + CodeJavadoc.combine(CodeJavadoc.methodReference(createMethod, true), CodeJavadoc.text(". This method uses the provided aliases")), + CodeJavadoc.text("from the original source file."), + + CodeJavadoc.header("Registering the command", 3), + + CodeJavadoc.combine( + CodeJavadoc.text("Commands should only be registered during the "), + CodeJavadoc.classReference(PROXY_INITIALIZE_EVENT.codeClass()), + CodeJavadoc.text(".") + ), + CodeJavadoc.text("The example below shows an example of how to do this. For more information,"), + CodeJavadoc.combine(CodeJavadoc.text("refer to "), CodeJavadoc.url("The Velocity Command API docs", "https://docs.papermc.io/velocity/dev/command-api/#registering-a-command")), + + CodeJavadoc.blank(), + + CodeJavadoc.codeBlock(""" + @Subscribe + void onProxyInitialize(final ProxyInitializeEvent event) { + TestCommandBrigadier.register(this.proxy, this); + }""") + )) + .setModifiers(Modifiers.PUBLIC) + + .setMethodStatements( + CodeStatement.variableDeclarationFinal(BRIGADIER_COMMAND, "command", Builders.ctorInvocation(BRIGADIER_COMMAND) + .addParameter(Builders.methodInvocation("create"))), + CodeStatement.variableDeclarationFinal(COMMAND_META, "meta", Builders.methodInvocation("getCommandManager") + .setInstanceVariable("server") + .chain("metaBuilder", CodeExpression.variable("command")) + .chain("aliases", InvokesMethod.StyleConfig.NEWLINE, Builders.methodInvocation("toArray") + .setInstanceVariable("ALIASES") + .addParameter(CodeExpression.methodReference(CodeType.STRING_ARRAY, "new"))) + .chain("plugin", InvokesMethod.StyleConfig.NEWLINE, CodeExpression.variable("command$plugin")) + .chain("build", InvokesMethod.StyleConfig.NEWLINE)), + CodeStatement.blank(), + Builders.methodInvocation("getCommandManager") + .setInstanceVariable("server") + .chain("register", CodeExpression.variable("meta"), CodeExpression.variable("command")) + ) + ) + + // Create method + .addMethod(createMethod + .setModifiers(Modifiers.PUBLIC) + .setReturnType(LITERAL_ARGUMENT_BUILDER) + .setJavadoc(CodeJavadoc.combineLines( + CodeJavadoc.text("A method for creating a Brigadier command node which denotes the declared command"), + CodeJavadoc.combine( + CodeJavadoc.text("in "), + CodeJavadoc.classReference(TEST_COMMAND.codeClass()), + CodeJavadoc.text(". You can either retrieve the unregistered node with this method")), + CodeJavadoc.combine( + CodeJavadoc.text("or register it directly with "), + CodeJavadoc.methodReference(registerMethod, true), + CodeJavadoc.text(".") + ) + )) + + .setMethodStatements(CodeStatement.returnStatement( + Builders.methodInvocation("literalArgumentBuilder") + .setStatic(BRIGADIER_COMMAND) + .addParameter(CodeExpression.variable("NAME")) + + .chain("requires", InvokesMethod.StyleConfig.NEWLINE, CodeExpression.lambda( + List.of("source"), + Builders.methodInvocation("hasPermission") + .setInstanceVariable("source") + .addParameter(CodeExpression.string("testcommand.use")) + )) + + .chain("executes", InvokesMethod.StyleConfig.NEWLINE, CodeExpression.lambda( + List.of("ctx"), + Builders.methodInvocation("execute") + .setInstanceVariable("instance") + .addParameter(Builders.methodInvocation("getSource").setInstanceVariable("ctx")), + CodeStatement.returnStatement(Builders.fieldAccess("SINGLE_SUCCESS") + .setStatic(COMMAND) + ) + )) + + .chain("then", InvokesMethod.StyleConfig.NEWLINE_BOTH, Builders.methodInvocation("literalArgumentBuilder") + .setStatic(BRIGADIER_COMMAND) + .addParameter(CodeExpression.string("run")) + + .chain("executes", InvokesMethod.StyleConfig.NEWLINE, CodeExpression.lambda( + List.of("ctx"), + CodeStatement.ifStmt( + CodeExpression.instanceofExpr( + Builders.methodInvocation("getSource").setInstanceVariable("ctx"), + PLAYER, + "source" + ).invert(), + CodeStatement.throwStatement(Builders.ctorInvocation(SIMPLE_COMMAND_EXCEPTION_TYPE) + .setMultilineParameters() + .addParameter(Builders.ctorInvocation(LITERAL_MESSAGE) + .addParameter(CodeExpression.string("This command requires a player sender!"))) + .chain("create") + ) + ), + CodeStatement.blank(), + Builders.methodInvocation("run") + .setInstanceVariable("instance") + .addParameter(CodeExpression.variable("source")), + CodeStatement.returnStatement(Builders.fieldAccess("SINGLE_SUCCESS") + .setStatic(COMMAND) + ) + )) + + .chain("then", InvokesMethod.StyleConfig.NEWLINE_BOTH, Builders.methodInvocation("requiredArgumentBuilder") + .setStatic(BRIGADIER_COMMAND) + .addParameter(CodeExpression.string("target")) + .addParameter(Builders.methodInvocation("word").setStatic(STRING_ARGUMENT_TYPE)) + .chain("executes", InvokesMethod.StyleConfig.NEWLINE, CodeExpression.lambda( + List.of("ctx"), + Builders.methodInvocation("runWithTarget") + .setMultilineParameters() + .setInstanceVariable("instance") + .addParameter(Builders.methodInvocation("getSource").setInstanceVariable("ctx")) + .addParameter(Builders.methodInvocation("getString") + .setStatic(STRING_ARGUMENT_TYPE) + .addParameter(CodeExpression.variable("ctx")) + .addParameter(CodeExpression.string("target")) + ), + CodeStatement.returnStatement(Builders.fieldAccess("SINGLE_SUCCESS") + .setStatic(COMMAND) + ) + )) + ) + ) + )) + ) + .build(); + } + +} diff --git a/processor/common/src/test/java/net/strokkur/commands/internal/codegen/package-info.java b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/package-info.java new file mode 100644 index 00000000..738b3b29 --- /dev/null +++ b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package net.strokkur.commands.internal.codegen; + +import org.jspecify.annotations.NullMarked; diff --git a/processor/paper/src/main/java/net/strokkur/commands/internal/paper/PaperTreePrinter.java b/processor/paper/src/main/java/net/strokkur/commands/internal/paper/PaperClassBuilder.java similarity index 96% rename from processor/paper/src/main/java/net/strokkur/commands/internal/paper/PaperTreePrinter.java rename to processor/paper/src/main/java/net/strokkur/commands/internal/paper/PaperClassBuilder.java index 57a334cf..b241e0ce 100644 --- a/processor/paper/src/main/java/net/strokkur/commands/internal/paper/PaperTreePrinter.java +++ b/processor/paper/src/main/java/net/strokkur/commands/internal/paper/PaperClassBuilder.java @@ -26,8 +26,8 @@ import net.strokkur.commands.internal.paper.util.ExecutorType; import net.strokkur.commands.internal.paper.util.PaperAttributeKeys; import net.strokkur.commands.internal.paper.util.PaperClasses; +import net.strokkur.commands.internal.printer.CommonClassBuilder; import net.strokkur.commands.internal.printer.CommonCommandTreePrinter; -import net.strokkur.commands.internal.printer.CommonTreePrinter; import net.strokkur.commands.internal.util.Classes; import net.strokkur.commands.paper.Executor; import org.jspecify.annotations.Nullable; @@ -37,8 +37,8 @@ import java.util.List; import java.util.Objects; -final class PaperTreePrinter extends CommonTreePrinter { - PaperTreePrinter(CommonCommandTreePrinter printer) { +final class PaperClassBuilder extends CommonClassBuilder { + PaperClassBuilder(CommonCommandTreePrinter printer) { super(printer); } diff --git a/processor/paper/src/main/java/net/strokkur/commands/internal/paper/PaperCommandTreePrinter.java b/processor/paper/src/main/java/net/strokkur/commands/internal/paper/PaperCommandTreePrinter.java index 1f6f4544..8321b58d 100644 --- a/processor/paper/src/main/java/net/strokkur/commands/internal/paper/PaperCommandTreePrinter.java +++ b/processor/paper/src/main/java/net/strokkur/commands/internal/paper/PaperCommandTreePrinter.java @@ -23,9 +23,9 @@ import net.strokkur.commands.internal.abstraction.SourceVariable; import net.strokkur.commands.internal.intermediate.tree.CommandNode; import net.strokkur.commands.internal.paper.util.PaperCommandInformation; +import net.strokkur.commands.internal.printer.CommonClassBuilder; import net.strokkur.commands.internal.printer.CommonCommandTreePrinter; import net.strokkur.commands.internal.printer.CommonImportPrinter; -import net.strokkur.commands.internal.printer.CommonTreePrinter; import net.strokkur.commands.internal.util.PrintParamsHolder; import org.jspecify.annotations.Nullable; @@ -54,8 +54,8 @@ protected CommonImportPrinter createImportPrinter() { } @Override - protected CommonTreePrinter createTreePrinter() { - return new PaperTreePrinter(this); + protected CommonClassBuilder createTreePrinter() { + return new PaperClassBuilder(this); } @Override @@ -79,7 +79,7 @@ protected PrintParamsHolder getParamsHolder() { @Override protected void printExtraClassStart() throws IOException { - super.printExtraClassStart(); + super.collectFields(); final String description = getCommandInformation().description() == null ? "null" : '"' + getCommandInformation().description() + '"'; final String aliases = getCommandInformation().aliases() == null ? "" : '"' + String.join("\", \"", List.of(getCommandInformation().aliases())) + '"'; println("public static final @Nullable String DESCRIPTION = %s;", description); diff --git a/processor/paper/src/main/java/net/strokkur/commands/internal/paper/PaperStrokkCommandsProcessor.java b/processor/paper/src/main/java/net/strokkur/commands/internal/paper/PaperStrokkCommandsProcessor.java index c76e2dd4..3effcc0e 100644 --- a/processor/paper/src/main/java/net/strokkur/commands/internal/paper/PaperStrokkCommandsProcessor.java +++ b/processor/paper/src/main/java/net/strokkur/commands/internal/paper/PaperStrokkCommandsProcessor.java @@ -63,7 +63,7 @@ protected CommonTreePostProcessor createPostProcessor(MessagerWrapper messager) } @Override - protected CommonCommandTreePrinter createPrinter(CommandNode node, PaperCommandInformation commandInformation) { + protected CommonCommandTreePrinter createBuilder(CommandNode node, PaperCommandInformation commandInformation) { return new PaperCommandTreePrinter(0, null, node, commandInformation, processingEnv, getPlatformUtils()); } diff --git a/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityBrigadierStatementBuilder.java b/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityBrigadierStatementBuilder.java new file mode 100644 index 00000000..af64efb0 --- /dev/null +++ b/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityBrigadierStatementBuilder.java @@ -0,0 +1,100 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.velocity; + +import net.strokkur.commands.internal.abstraction.SourceVariable; +import net.strokkur.commands.internal.codegen.CodeExpression; +import net.strokkur.commands.internal.codegen.CodeStatement; +import net.strokkur.commands.internal.codegen.as.AsExpression; +import net.strokkur.commands.internal.codegen.as.AsStatement; +import net.strokkur.commands.internal.codegen.builder.Builders; +import net.strokkur.commands.internal.codegen.builder.MethodInvocationBuilder; +import net.strokkur.commands.internal.intermediate.executable.DefaultExecutable; +import net.strokkur.commands.internal.intermediate.executable.Executable; +import net.strokkur.commands.internal.printer.CommonBrigadierStatementBuilder; +import net.strokkur.commands.internal.util.Classes; +import net.strokkur.commands.internal.velocity.util.SenderType; +import net.strokkur.commands.internal.velocity.util.VelocityAttributeKeys; +import net.strokkur.commands.internal.velocity.util.VelocityClasses; + +import java.util.List; +import java.util.Objects; + +class VelocityBrigadierStatementBuilder extends CommonBrigadierStatementBuilder { + @Override + protected MethodInvocationBuilder literalBuilder(AsExpression name) { + return Builders.methodInvocation("literalArgumentBuilder").setStatic(VelocityClasses.BRIGADIER_COMMAND) + .addParameter(name); + } + + @Override + protected MethodInvocationBuilder argumentBuilder(AsExpression name, AsExpression argument) { + return Builders.methodInvocation("requiredArgumentBuilder").setStatic(VelocityClasses.BRIGADIER_COMMAND) + .addParameter(name) + .addParameter(argument); + } + + @Override + protected List validationStatements(Executable executable) { + final SenderType type = executable.getAttributeNotNull(VelocityAttributeKeys.SENDER_TYPE); + if (type != SenderType.NORMAL) { + return List.of( + CodeStatement.ifStmt( + CodeExpression.instanceofExpr( + Builders.methodInvocation("getSource").setInstanceVariable("ctx"), + type.getClassType().getAsCodeType(), + "source" + ).invert(), + CodeStatement.throwStatement(Builders.ctorInvocation(Classes.SIMPLE_COMMAND_EXCEPTION_TYPE) + .setMultilineParameters() + .addParameter(Builders.ctorInvocation(Classes.LITERAL_MESSAGE).addParameter(CodeExpression.string( + "This command requires a %s sender!".formatted(type.getClassType().getAsCodeType().name().toLowerCase()) + ))) + .chain("create") + ) + ), + CodeStatement.blank() + ); + } + + return List.of(); + } + + @Override + protected AsExpression getParameterValueExpr(SourceVariable parameter) { + if (parameter.getType().getFullyQualifiedAndTypedName().equalsIgnoreCase(Classes.COMMAND_CONTEXT.getAsCodeType().fullyQualifiedName() + "<" + VelocityClasses.COMMAND_SOURCE.getAsCodeType().fullyQualifiedName() + ">")) { + return CodeExpression.variable("ctx"); + } + + if (parameter.getType().getFullyQualifiedAndTypedName().equalsIgnoreCase(VelocityClasses.COMMAND_SOURCE.getAsCodeType().fullyQualifiedName())) { + return Builders.methodInvocation("getSource").setInstanceVariable("ctx"); + } + + if (parameter.getType().getFullyQualifiedAndTypedName().equalsIgnoreCase(VelocityClasses.PLAYER.getAsCodeType().fullyQualifiedName()) + || parameter.getType().getFullyQualifiedAndTypedName().equalsIgnoreCase(VelocityClasses.CONSOLE_COMMAND_SOURCE.getAsCodeType().fullyQualifiedName())) { + return CodeExpression.variable("source"); + } + + final DefaultExecutable.Type type = DefaultExecutable.Type.getType(parameter); + if (type == DefaultExecutable.Type.LIST || type == DefaultExecutable.Type.ARRAY) { + return Objects.requireNonNull(type.getGetter()); + } + + throw new IllegalStateException("Unknown parameter type: " + parameter.getFullDefinition()); + } +} diff --git a/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityClassBuilder.java b/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityClassBuilder.java new file mode 100644 index 00000000..29362707 --- /dev/null +++ b/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityClassBuilder.java @@ -0,0 +1,151 @@ +/* + * StrokkCommands - A super simple annotation based zero-shade Paper command API library. + * Copyright (C) 2025 Strokkur24 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +package net.strokkur.commands.internal.velocity; + +import net.strokkur.commands.internal.abstraction.SourceConstructor; +import net.strokkur.commands.internal.abstraction.SourceParameter; +import net.strokkur.commands.internal.abstraction.SourceVariable; +import net.strokkur.commands.internal.codegen.CodeExpression; +import net.strokkur.commands.internal.codegen.CodeMethod; +import net.strokkur.commands.internal.codegen.CodePackage; +import net.strokkur.commands.internal.codegen.CodeStatement; +import net.strokkur.commands.internal.codegen.CodeType; +import net.strokkur.commands.internal.codegen.InvokesMethod; +import net.strokkur.commands.internal.codegen.Modifiers; +import net.strokkur.commands.internal.codegen.adapter.CodeTypeAdapter; +import net.strokkur.commands.internal.codegen.builder.Builders; +import net.strokkur.commands.internal.codegen.builder.ClassBuilder; +import net.strokkur.commands.internal.codegen.builder.MethodBuilder; +import net.strokkur.commands.internal.codegen.builder.MethodInvocationBuilder; +import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc; +import net.strokkur.commands.internal.intermediate.tree.CommandNode; +import net.strokkur.commands.internal.printer.CommonClassBuilder; +import net.strokkur.commands.internal.printer.source.AbstractSourcePrintingVisitor; +import net.strokkur.commands.internal.util.ConvertableTo; +import net.strokkur.commands.internal.velocity.util.VelocityClasses; +import net.strokkur.commands.internal.velocity.util.VelocityCommandInformation; + +import java.util.Set; +import java.util.function.BiFunction; + +class VelocityClassBuilder extends CommonClassBuilder { + VelocityClassBuilder( + CommandNode rootNode, + VelocityCommandInformation commandInformation, + BiFunction, AbstractSourcePrintingVisitor> sourceVisitor + ) { + super(rootNode, commandInformation, new VelocityBrigadierStatementBuilder(), sourceVisitor); + } + + @Override + protected MethodBuilder getCreateMethodBuilder() { + return super.getCreateMethodBuilder() + .setReturnType(VelocityClasses.TYPED_LITERAL_COMMAND_NODE.getAsCodeType()); + } + + @Override + protected void populateStaticFields(ClassBuilder builder) { + super.populateStaticFields(builder); + + final MethodInvocationBuilder listOf = Builders.methodInvocation("of").setStatic(CodeType.LIST); + if (commandInformation.aliases() != null) { + for (String alias : commandInformation.aliases()) { + listOf.addParameter(CodeExpression.string(alias)); + } + } + + builder.addField(Builders.field("ALIASES", CodeType.LIST_STRING) + .setModifiers(Modifiers.PUBLIC, Modifiers.STATIC, Modifiers.FINAL) + .setInitialiser(listOf) + ); + } + + @Override + protected MethodBuilder getRegisterMethodBuilder() { + final MethodInvocationBuilder createInvocation = Builders.methodInvocation("create"); + + if (!commandInformation.useInjection()) { + if (commandInformation.constructor() instanceof SourceConstructor sourceCtor) { + for (SourceParameter parameter : sourceCtor.getParameters()) { + final String variableName; + if (isProxyServer(parameter)) { + variableName = "server"; + } else { + variableName = parameter.getName(); + } + + createInvocation.addParameter(CodeExpression.variable(variableName)); + } + } + } + + final MethodBuilder builder = super.getRegisterMethodBuilder() + .addParameter(VelocityClasses.PROXY_SERVER.getAsCodeType(), "server") + .addParameter(CodeType.OBJECT, "command$plugin") + .setMethodStatements( + CodeStatement.variableDeclarationFinal(VelocityClasses.BRIGADIER_COMMAND, "command", Builders.ctorInvocation(VelocityClasses.BRIGADIER_COMMAND) + .addParameter(createInvocation)), + CodeStatement.variableDeclarationFinal(VelocityClasses.COMMAND_META, "meta", Builders.methodInvocation("getCommandManager") + .setInstanceVariable("server") + .chain("metaBuilder", CodeExpression.variable("command")) + .chain("aliases", InvokesMethod.StyleConfig.NEWLINE, Builders.methodInvocation("toArray") + .setInstanceVariable("ALIASES") + .addParameter(CodeExpression.methodReference(CodeType.STRING_ARRAY, "new"))) + .chain("plugin", InvokesMethod.StyleConfig.NEWLINE, CodeExpression.variable("command$plugin")) + .chain("build", InvokesMethod.StyleConfig.NEWLINE)), + CodeStatement.blank(), + Builders.methodInvocation("getCommandManager") + .setInstanceVariable("server") + .chain("register", CodeExpression.variable("meta"), CodeExpression.variable("command")) + ); + + addConstructorParametersTo(builder, f -> !isProxyServer(f)); + return builder; + } + + private boolean isProxyServer(SourceVariable sourceVar) { + return CodeTypeAdapter.from(sourceVar.getType()).equals(VelocityClasses.PROXY_SERVER.getAsCodeType()); + } + + @Override + protected void applyRegisterMethodJavadoc(MethodBuilder registerMethod, ConvertableTo createMethod) { + registerMethod.setJavadoc(CodeJavadoc.combineLines( + CodeJavadoc.text("Shortcut for registering the command node returned from"), + CodeJavadoc.combine(CodeJavadoc.methodReference(createMethod, true), CodeJavadoc.text(". This method uses the provided aliases")), + CodeJavadoc.text("from the original source file."), + + CodeJavadoc.header("Registering the command", 3), + + CodeJavadoc.combine( + CodeJavadoc.text("Commands should only be registered during the "), + CodeJavadoc.classReference(VelocityClasses.PROXY_INITIALIZE_EVENT.getAsCodeType().codeClass()), + CodeJavadoc.text(".") + ), + CodeJavadoc.text("The example below shows an example of how to do this. For more information,"), + CodeJavadoc.combine(CodeJavadoc.text("refer to "), CodeJavadoc.url("The Velocity Command API docs", "https://docs.papermc.io/velocity/dev/command-api/#registering-a-command")), + + CodeJavadoc.blank(), + + CodeJavadoc.codeBlock(""" + @Subscribe + void onProxyInitialize(final ProxyInitializeEvent event) { + %s.register(this.proxy, this); + }""".formatted(selfType.name())) + )); + } +} diff --git a/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityCommandTreePrinter.java b/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityCommandTreePrinter.java deleted file mode 100644 index 13f9a4ae..00000000 --- a/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityCommandTreePrinter.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * StrokkCommands - A super simple annotation based zero-shade Paper command API library. - * Copyright (C) 2025 Strokkur24 - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see . - */ -package net.strokkur.commands.internal.velocity; - -import net.strokkur.commands.internal.PlatformUtils; -import net.strokkur.commands.internal.abstraction.SourceParameter; -import net.strokkur.commands.internal.abstraction.SourceVariable; -import net.strokkur.commands.internal.intermediate.tree.CommandNode; -import net.strokkur.commands.internal.printer.CommonCommandTreePrinter; -import net.strokkur.commands.internal.printer.CommonImportPrinter; -import net.strokkur.commands.internal.printer.CommonInstanceFieldPrinter; -import net.strokkur.commands.internal.printer.CommonTreePrinter; -import net.strokkur.commands.internal.util.PrintParamsHolder; -import net.strokkur.commands.internal.velocity.util.VelocityClasses; -import net.strokkur.commands.internal.velocity.util.VelocityCommandInformation; -import org.jspecify.annotations.Nullable; - -import javax.annotation.processing.ProcessingEnvironment; -import java.io.IOException; -import java.io.Writer; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -final class VelocityCommandTreePrinter extends CommonCommandTreePrinter { - VelocityCommandTreePrinter( - int indent, - @Nullable Writer writer, - CommandNode node, - VelocityCommandInformation commandInformation, - ProcessingEnvironment environment, - PlatformUtils utils - ) { - super(indent, writer, node, commandInformation, environment, utils); - } - - @Override - protected void printExtraClassStart() throws IOException { - super.printExtraClassStart(); - final Optional aliases = Optional.ofNullable(getCommandInformation().aliases()); - if (aliases.isPresent()) { - final String aliasesVarargs = String.join(", ", Arrays.stream(aliases.get()) - .map(alias -> '"' + alias + '"') - .toList()); - println("public static final List ALIASES = List.of(%s);", aliasesVarargs); - } - } - - @Override - protected CommonImportPrinter createImportPrinter() { - return new VelocityImportPrinter(this); - } - - @Override - protected CommonTreePrinter createTreePrinter() { - return new VelocityTreePrinter(this); - } - - @Override - protected CommonInstanceFieldPrinter createInstanceFieldPrinter() { - return new VelocityInstanceFieldPrinter(this); - } - - @Override - protected PrintParamsHolder getParamsHolder() { - if (getCommandInformation().useInjection()) { - return new PrintParamsHolder( - "", "", "", - "ProxyServer, Object", "ProxyServer server, Object command$plugin", - SourceVariable::getName - ); - } - - return new PrintParamsHolder( - SourceParameter.combineJavaDocsParameterString( - List.of("ProxyServer"), - getCommandInformation().constructor(), - p -> !p.getType().getFullyQualifiedName().equals(VelocityClasses.PROXY_SERVER) - ), - SourceParameter.combineMethodParameterString( - List.of("final ProxyServer server"), - getCommandInformation().constructor(), - p -> !p.getType().getFullyQualifiedName().equals(VelocityClasses.PROXY_SERVER) - ), - SourceParameter.combineMethodParameterNameString( - List.of("server"), - getCommandInformation().constructor(), - p -> !p.getType().getFullyQualifiedName().equals(VelocityClasses.PROXY_SERVER) - ), - SourceParameter.combineJavaDocsParameterString( - List.of("ProxyServer", "Object"), - getCommandInformation().constructor(), - p -> !p.getType().getFullyQualifiedName().equals(VelocityClasses.PROXY_SERVER) - ), - SourceParameter.combineMethodParameterString( - List.of("final ProxyServer server", "final Object command$plugin"), - getCommandInformation().constructor(), - p -> !p.getType().getFullyQualifiedName().equals(VelocityClasses.PROXY_SERVER) - ), - p -> p.getType().getFullyQualifiedName().equals(VelocityClasses.PROXY_SERVER) ? "server" : p.getName() - ); - } - - @Override - protected void printRegisterMethod(PrintParamsHolder holder) throws IOException { - printBlock(""" - /** - * Shortcut for registering the command node returned from - * {@link #create(%s)}. This method uses the provided aliases - * from the original source file. - * - *

Registering the command

- *

- * Commands should only be registered during the {@link ProxyInitializeEvent}. - * The example below shows an example of how to do this. For more information, - * refer to The Velocity Command API docs - * - *

{@code
-             * @Subscribe
-             * void onProxyInitialize(final ProxyInitializeEvent event) {
-             *   %s.register(this.proxy, this);
-             * }
-             * }
- */ - public%s void register(%s) { - final BrigadierCommand command = new BrigadierCommand(create(%s)); - final CommandMeta meta = server.getCommandManager().metaBuilder(command)""", - holder.createJdParams(), - getBrigadierClassName(), - getCommandInformation().useInjection() ? "" : " static", - holder.registerParams(), - holder.createParamNames() - ); - - final Optional aliases = Optional.ofNullable(getCommandInformation().aliases()); - if (aliases.isPresent()) { - incrementIndent(); - incrementIndent(); - println(".aliases(ALIASES.toArray(String[]::new))"); - decrementIndent(); - decrementIndent(); - } - - printBlock(""" - .plugin(command$plugin) - .build(); - - server.getCommandManager().register(meta, command); - }"""); - } -} diff --git a/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityImportPrinter.java b/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityImportPrinter.java deleted file mode 100644 index a430c6e4..00000000 --- a/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityImportPrinter.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * StrokkCommands - A super simple annotation based zero-shade Paper command API library. - * Copyright (C) 2025 Strokkur24 - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see . - */ -package net.strokkur.commands.internal.velocity; - -import net.strokkur.commands.internal.intermediate.attributes.AttributeKey; -import net.strokkur.commands.internal.intermediate.executable.Executable; -import net.strokkur.commands.internal.intermediate.tree.CommandNode; -import net.strokkur.commands.internal.printer.CommonCommandTreePrinter; -import net.strokkur.commands.internal.printer.CommonImportPrinter; -import net.strokkur.commands.internal.util.Classes; -import net.strokkur.commands.internal.velocity.util.SenderType; -import net.strokkur.commands.internal.velocity.util.VelocityAttributeKeys; -import net.strokkur.commands.internal.velocity.util.VelocityClasses; - -import java.util.Set; - -final class VelocityImportPrinter extends CommonImportPrinter { - VelocityImportPrinter(CommonCommandTreePrinter printer) { - super(printer); - } - - @Override - public Set standardImports() { - return Set.of( - Classes.COMMAND, - VelocityClasses.LITERAL_ARGUMENT_BUILDER, - VelocityClasses.BRIGADIER_COMMAND, - VelocityClasses.COMMAND_META, - VelocityClasses.COMMAND_SOURCE, - VelocityClasses.PROXY_INITIALIZE_EVENT, - VelocityClasses.PROXY_SERVER, - Classes.NULL_MARKED, - Classes.LIST - ); - } - - @Override - public void gatherAdditionalNodeImports(Set imports, CommandNode node) { - addExecutorTypeImports(imports, node.getAttributeNotNull(VelocityAttributeKeys.SENDER_TYPE)); - final Executable executable = node.getEitherAttribute(AttributeKey.EXECUTABLE, AttributeKey.DEFAULT_EXECUTABLE); - if (executable != null) { - addExecutorTypeImports(imports, executable.getAttributeNotNull(VelocityAttributeKeys.SENDER_TYPE)); - } - } - - private void addExecutorTypeImports(Set imports, SenderType type) { - if (type == SenderType.NORMAL) { - return; - } - - if (type == SenderType.PLAYER) { - imports.add(VelocityClasses.PLAYER); - } else if (type == SenderType.CONSOLE) { - imports.add(VelocityClasses.CONSOLE_COMMAND_SOURCE); - } - - imports.add(Classes.SIMPLE_COMMAND_EXCEPTION_TYPE); - imports.add(Classes.LITERAL_MESSAGE); - } -} diff --git a/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityPlatformUtils.java b/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityPlatformUtils.java index 7ab31a13..8e0504c3 100644 --- a/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityPlatformUtils.java +++ b/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityPlatformUtils.java @@ -20,6 +20,8 @@ import net.strokkur.commands.internal.PlatformUtils; import net.strokkur.commands.internal.abstraction.AnnotationsHolder; import net.strokkur.commands.internal.abstraction.SourceVariable; +import net.strokkur.commands.internal.codegen.CodeType; +import net.strokkur.commands.internal.codegen.adapter.CodeTypeAdapter; import net.strokkur.commands.internal.exceptions.AnnotationException; import net.strokkur.commands.internal.intermediate.executable.Executable; import net.strokkur.commands.internal.intermediate.executable.ParameterType; @@ -43,7 +45,7 @@ public void populateExecutesNode(Executable executable, CommandNode node, List

parameters) throws Annotati if (!(parameter instanceof SourceParameterType(SourceVariable sourceParam))) { continue; } + final CodeType adapted = CodeTypeAdapter.from(sourceParam.getType()); - final SenderType thisType = switch (sourceParam.getType().getFullyQualifiedName()) { - case VelocityClasses.PLAYER -> SenderType.PLAYER; - case VelocityClasses.CONSOLE_COMMAND_SOURCE -> SenderType.CONSOLE; - default -> type; - }; + final SenderType thisType; + if (adapted.equals(VelocityClasses.PLAYER.getAsCodeType())) { + thisType = SenderType.PLAYER; + } else if (adapted.equals(VelocityClasses.CONSOLE_COMMAND_SOURCE.getAsCodeType())) { + thisType = SenderType.CONSOLE; + } else { + thisType = type; + } if (type != SenderType.NORMAL && thisType != type) { throw new AnnotationException("Cannot satisfy both a player and a console source."); diff --git a/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityStrokkCommandsProcessor.java b/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityStrokkCommandsProcessor.java index 2024efc6..3f96f92a 100644 --- a/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityStrokkCommandsProcessor.java +++ b/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityStrokkCommandsProcessor.java @@ -28,7 +28,8 @@ import net.strokkur.commands.internal.arguments.BrigadierArgumentConverter; import net.strokkur.commands.internal.intermediate.CommonTreePostProcessor; import net.strokkur.commands.internal.intermediate.tree.CommandNode; -import net.strokkur.commands.internal.printer.CommonCommandTreePrinter; +import net.strokkur.commands.internal.printer.CommonClassBuilder; +import net.strokkur.commands.internal.printer.source.JavaSourcePrintingVisitor; import net.strokkur.commands.internal.util.MessagerWrapper; import net.strokkur.commands.internal.velocity.util.VelocityCommandInformation; @@ -62,8 +63,10 @@ protected CommonTreePostProcessor createPostProcessor(MessagerWrapper messager) } @Override - protected CommonCommandTreePrinter createPrinter(CommandNode node, VelocityCommandInformation commandInformation) { - return new VelocityCommandTreePrinter(0, null, node, commandInformation, this.processingEnv, getPlatformUtils()); + protected CommonClassBuilder createBuilder(CommandNode node, VelocityCommandInformation commandInformation) { + return new VelocityClassBuilder(node, commandInformation, + (pkg, types) -> new JavaSourcePrintingVisitor(() -> createJavadocVisitor(pkg, types), " ", " ") + ); } @Override diff --git a/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityTreePrinter.java b/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityTreePrinter.java deleted file mode 100644 index 81e211c8..00000000 --- a/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityTreePrinter.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * StrokkCommands - A super simple annotation based zero-shade Paper command API library. - * Copyright (C) 2025 Strokkur24 - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see . - */ -package net.strokkur.commands.internal.velocity; - -import net.strokkur.commands.internal.abstraction.SourceVariable; -import net.strokkur.commands.internal.exceptions.PrinterException; -import net.strokkur.commands.internal.intermediate.attributes.Attributable; -import net.strokkur.commands.internal.intermediate.executable.DefaultExecutable; -import net.strokkur.commands.internal.intermediate.executable.Executable; -import net.strokkur.commands.internal.intermediate.tree.CommandNode; -import net.strokkur.commands.internal.printer.CommonCommandTreePrinter; -import net.strokkur.commands.internal.printer.CommonTreePrinter; -import net.strokkur.commands.internal.util.Classes; -import net.strokkur.commands.internal.velocity.util.SenderType; -import net.strokkur.commands.internal.velocity.util.VelocityAttributeKeys; -import net.strokkur.commands.internal.velocity.util.VelocityClasses; -import org.jspecify.annotations.Nullable; - -import java.io.IOException; -import java.util.List; -import java.util.Locale; -import java.util.Objects; -import java.util.Set; - -final class VelocityTreePrinter extends CommonTreePrinter { - VelocityTreePrinter(CommonCommandTreePrinter printer) { - super(printer); - } - - @Override - public void prefixPrintExecutableInner(CommandNode node, Executable executable) throws IOException { - final SenderType type = executable.getAttributeNotNull(VelocityAttributeKeys.SENDER_TYPE); - if (type != SenderType.NORMAL) { - printer.printBlock(""" - if (!(ctx.getSource() instanceof %s source)) { - throw new SimpleCommandExceptionType( - new LiteralMessage("This command requires a %s sender!") - ).create(); - }""", - List.of(type.getClassName().split("\\.")).getLast(), - type.toString().toLowerCase(Locale.ROOT) - ); - printer.println(); - } - } - - @Override - public String handleParameter(SourceVariable parameter) throws IOException { - if (parameter.getType().getFullyQualifiedAndTypedName().equalsIgnoreCase(Classes.COMMAND_CONTEXT + "<" + VelocityClasses.COMMAND_SOURCE + ">")) { - return "ctx"; - } - - if (parameter.getType().getFullyQualifiedAndTypedName().equalsIgnoreCase(VelocityClasses.COMMAND_SOURCE)) { - return "ctx.getSource()"; - } - - if (parameter.getType().getFullyQualifiedAndTypedName().equalsIgnoreCase(VelocityClasses.PLAYER) - || parameter.getType().getFullyQualifiedAndTypedName().equalsIgnoreCase(VelocityClasses.CONSOLE_COMMAND_SOURCE)) { - return "source"; - } - - final DefaultExecutable.Type type = DefaultExecutable.Type.getType(parameter); - if (type == DefaultExecutable.Type.LIST || type == DefaultExecutable.Type.ARRAY) { - return Objects.requireNonNull(type.getGetter()); - } - - throw new PrinterException("Unknown parameter type: " + parameter.getName()); - } - - @Override - public @Nullable String getExtraRequirements(Attributable node) { - final Set permissions = node.getAttributeNotNull(VelocityAttributeKeys.PERMISSIONS); - final SenderType type = node.getAttributeNotNull(VelocityAttributeKeys.SENDER_TYPE); - - if (type == SenderType.NORMAL) { - if (permissions.isEmpty()) { - return null; - } - - return String.join(" || ", permissions.stream() - .map(perm -> "source.hasPermission(\"" + perm + "\")") - .toList()); - } - - if (permissions.isEmpty()) { - return type.getPredicate(); - } - if (permissions.size() == 1) { - return "%s && source.hasPermission(\"%s\")".formatted( - type.getPredicate(), - permissions.stream().findFirst().get() - ); - } - - return "source -> %s && (%s))".formatted( - type.getPredicate(), - String.join(" || ", permissions.stream() - .map(perm -> "source.hasPermission(\"" + perm + "\")") - .toList()) - ); - } - - @Override - public String getLiteralMethodString() { - return "BrigadierCommand.literalArgumentBuilder"; - } - - @Override - public String getArgumentMethodString() { - return "BrigadierCommand.requiredArgumentBuilder"; - } -} diff --git a/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/util/SenderType.java b/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/util/SenderType.java index 91ec31c8..86ca23ec 100644 --- a/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/util/SenderType.java +++ b/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/util/SenderType.java @@ -17,27 +17,25 @@ */ package net.strokkur.commands.internal.velocity.util; -public enum SenderType { - NORMAL(VelocityClasses.COMMAND_SOURCE), - CONSOLE(VelocityClasses.CONSOLE_COMMAND_SOURCE, "source instanceof ConsoleCommandSource"), - PLAYER(VelocityClasses.PLAYER, "source instanceof Player"); - private final String className; - private final String predicate; +import net.strokkur.commands.internal.codegen.CodeExpression; - SenderType(String className) { - this(className, "true"); - } +public enum SenderType { + NORMAL(VelocityClasses.COMMAND_SOURCE, CodeExpression.bool(true)), + CONSOLE(VelocityClasses.CONSOLE_COMMAND_SOURCE, CodeExpression.instanceofExpr(CodeExpression.variable("source"), VelocityClasses.CONSOLE_COMMAND_SOURCE.getAsCodeType(), null)), + PLAYER(VelocityClasses.PLAYER, CodeExpression.instanceofExpr(CodeExpression.variable("source"), VelocityClasses.PLAYER.getAsCodeType(), null)); + private final VelocityClasses classType; + private final CodeExpression.BooleanExpression predicate; - SenderType(String className, String predicate) { - this.className = className; + SenderType(VelocityClasses classType, CodeExpression.BooleanExpression predicate) { + this.classType = classType; this.predicate = predicate; } - public String getPredicate() { - return predicate; + public VelocityClasses getClassType() { + return classType; } - public String getClassName() { - return className; + public CodeExpression.BooleanExpression getPredicate() { + return predicate; } } diff --git a/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/util/VelocityClasses.java b/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/util/VelocityClasses.java index 770fc153..09e1e002 100644 --- a/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/util/VelocityClasses.java +++ b/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/util/VelocityClasses.java @@ -17,16 +17,41 @@ */ package net.strokkur.commands.internal.velocity.util; +import net.strokkur.commands.internal.codegen.CodeType; +import net.strokkur.commands.internal.codegen.as.AsCodeType; import net.strokkur.commands.internal.util.Classes; -public interface VelocityClasses extends Classes { - String COMMAND_SOURCE = "com.velocitypowered.api.command.CommandSource"; - String PLAYER = "com.velocitypowered.api.proxy.Player"; - String CONSOLE_COMMAND_SOURCE = "com.velocitypowered.api.proxy.ConsoleCommandSource"; +import java.util.Arrays; - String BRIGADIER_COMMAND = "com.velocitypowered.api.command.BrigadierCommand"; - String COMMAND_META = "com.velocitypowered.api.command.CommandMeta"; +public enum VelocityClasses implements AsCodeType { + COMMAND_SOURCE("com.velocitypowered.api.command.CommandSource"), + PLAYER("com.velocitypowered.api.proxy.Player"), + CONSOLE_COMMAND_SOURCE("com.velocitypowered.api.proxy.ConsoleCommandSource"), - String PROXY_INITIALIZE_EVENT = "com.velocitypowered.api.event.proxy.ProxyInitializeEvent"; - String PROXY_SERVER = "com.velocitypowered.api.proxy.ProxyServer"; + BRIGADIER_COMMAND("com.velocitypowered.api.command.BrigadierCommand"), + COMMAND_META("com.velocitypowered.api.command.CommandMeta"), + + PROXY_INITIALIZE_EVENT("com.velocitypowered.api.event.proxy.ProxyInitializeEvent"), + PROXY_SERVER("com.velocitypowered.api.proxy.ProxyServer"), + + TYPED_LITERAL_COMMAND_NODE(Classes.LITERAL_COMMAND_NODE, COMMAND_SOURCE); + + private final CodeType.ClassType classType; + + VelocityClasses(String fqn) { + this.classType = CodeType.ofClass(fqn); + } + + @SafeVarargs + VelocityClasses(AsCodeType base, AsCodeType... types) { + this.classType = CodeType.ofClassTyped(base.getAsCodeType().codeClass(), Arrays.stream(types) + .map(AsCodeType::getAsCodeType) + .toArray(CodeType[]::new) + ); + } + + @Override + public CodeType.ClassType getAsCodeType() { + return classType; + } } diff --git a/test-plugin/velocity/build.gradle.kts b/test-plugin/velocity/build.gradle.kts index 30249ceb..a7205f04 100644 --- a/test-plugin/velocity/build.gradle.kts +++ b/test-plugin/velocity/build.gradle.kts @@ -15,3 +15,7 @@ tasks.runVelocity { velocityVersion(libs.versions.velocity.get()) jvmArgs("-Xmx2G", "-Xms2G") } + +java { + toolchain.languageVersion = JavaLanguageVersion.of(25); +} diff --git a/test-plugin/velocity/src/main/java/net/strokkur/testplugin/velocity/TestPluginVelocity.java b/test-plugin/velocity/src/main/java/net/strokkur/testplugin/velocity/TestPluginVelocity.java index b578132a..59e5123e 100644 --- a/test-plugin/velocity/src/main/java/net/strokkur/testplugin/velocity/TestPluginVelocity.java +++ b/test-plugin/velocity/src/main/java/net/strokkur/testplugin/velocity/TestPluginVelocity.java @@ -24,10 +24,10 @@ import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.proxy.ProxyServer; -import net.strokkur.testplugin.velocity.reference.TestCommandBrigadier; +import jakarta.inject.Inject; import org.slf4j.Logger; -import javax.inject.Inject; +//import net.strokkur.testplugin.velocity.reference.TestCommandBrigadier; @Plugin( id = "strokkcommands-testplugin", @@ -50,7 +50,7 @@ public TestPluginVelocity(ProxyServer proxy, Logger logger) { @Subscribe void onProxyInitialize(ProxyInitializeEvent event) { final Injector injector = Guice.createInjector(new InjectionModule()); - injector.getInstance(TestCommandBrigadier.class).register(this.proxy, this); +// injector.getInstance(TestCommandBrigadier.class).register(this.proxy, this); } private class InjectionModule extends AbstractModule {