From 6f1c0b355f71ab435d3af52fd78b1430ba8f0a5b Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Fri, 8 May 2026 21:49:39 +0200 Subject: [PATCH 01/16] Implement Javadocs code gen --- gradle/libs.versions.toml | 7 + processor/common/build.gradle.kts | 11 + .../java/util/annotations.xml | 11 + .../commands/internal/codegen/CodeClass.java | 42 ++++ .../commands/internal/codegen/CodeMethod.java | 115 ++++++++++ .../internal/codegen/CodePackage.java | 28 +++ .../internal/codegen/CodeParameter.java | 30 +++ .../commands/internal/codegen/CodeType.java | 47 ++++ .../internal/codegen/impl/BasicCodeClass.java | 40 ++++ .../codegen/impl/BasicCodeMethod.java | 34 +++ .../codegen/impl/BasicCodePackage.java | 29 +++ .../codegen/impl/BasicCodeParameter.java | 24 ++ .../internal/codegen/javadoc/CodeJavadoc.java | 214 ++++++++++++++++++ .../codegen/visitor/JavadocVisitor.java | 47 ++++ .../attributes/AttributableHelper.java | 1 + .../AbstractJavadocPrintingVisitor.java | 29 +++ .../visitor/JavaMarkdownJavadocVisitor.java | 82 +++++++ .../visitor/JavaStarJavadocVisitor.java | 141 ++++++++++++ .../visitor/CommonJavadocVisitorTests.java | 143 ++++++++++++ .../visitor/JavadocMarkdownVisitorTests.java | 85 +++++++ .../visitor/JavadocStarVisitorTests.java | 93 ++++++++ 21 files changed, 1253 insertions(+) create mode 100644 processor/common/src/main/external-annotations/java/util/annotations.xml create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeClass.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeMethod.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodePackage.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeParameter.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeType.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeClass.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeMethod.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodePackage.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeParameter.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/javadoc/CodeJavadoc.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/visitor/JavadocVisitor.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/printer/visitor/AbstractJavadocPrintingVisitor.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/printer/visitor/JavaMarkdownJavadocVisitor.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/printer/visitor/JavaStarJavadocVisitor.java create mode 100644 processor/common/src/test/java/net/strokkur/commands/internal/printer/visitor/CommonJavadocVisitorTests.java create mode 100644 processor/common/src/test/java/net/strokkur/commands/internal/printer/visitor/JavadocMarkdownVisitorTests.java create mode 100644 processor/common/src/test/java/net/strokkur/commands/internal/printer/visitor/JavadocStarVisitorTests.java diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 19d3dd76..dfd39e2e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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..71e28f2a 100644 --- a/processor/common/build.gradle.kts +++ b/processor/common/build.gradle.kts @@ -5,6 +5,17 @@ plugins { dependencies { compileOnlyApi(project(":annotations-common")) + + testImplementation(platform(libs.junit.bom)) + testImplementation(libs.junit.jupiter) + testRuntimeOnly(libs.junit.platform) +} + +tasks.test { + useJUnitPlatform() + testLogging { + events("passed", "skipped", "failed") + } } 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/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..31b58134 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeClass.java @@ -0,0 +1,42 @@ +/* + * 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.impl.BasicCodeClass; +import net.strokkur.commands.internal.codegen.impl.BasicCodePackage; +import org.jspecify.annotations.Nullable; + +import java.util.Arrays; + +public interface CodeClass extends CodeType { + + static CodeClass simple(String string) { + final String[] split = string.split("\\."); + return new BasicCodeClass(new BasicCodePackage(Arrays.copyOf(split, split.length - 1)), null, split[split.length - 1]); + } + + CodePackage codePackage(); + + @Nullable CodeClass parentClass(); + + String className(); + + default String fullyQualifiedName() { + return codePackage().path() + "." + className(); + } +} 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..bf453839 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeMethod.java @@ -0,0 +1,115 @@ +/* + * 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.impl.BasicCodeMethod; +import org.jspecify.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public interface CodeMethod { + + static CodeMethod.Builder builder() { + return new Builder(); + } + + static CodeMethod.Builder builder(CodeClass declaringClass, String name) { + return new Builder() + .declaringClass(declaringClass) + .name(name); + } + + /// Example: `methodName` + String name(); + + /// Example: `methodName(String, int)` + default String javadocName() { + return name() + "(" + parameters().stream() + .map(param -> param.type().fullyQualifiedName()) + .collect(Collectors.joining(", ")) + + ")"; + } + + CodeClass declaredClass(); + + CodeType returnType(); + + List parameters(); + + boolean isStatic(); + + class Builder { + private @Nullable CodeClass declaredClass = null; + private @Nullable String name = null; + + private CodeType returnType = CodeType.VOID; + private boolean isStatic = false; + private final List parameters = new ArrayList<>(); + + public Builder declaringClass(CodeClass declaringClass) { + this.declaredClass = declaringClass; + return this; + } + + public Builder returnType(CodeType returnType) { + this.returnType = returnType; + return this; + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder parameters(List parameters) { + this.parameters.clear(); + this.parameters.addAll(parameters); + return this; + } + + public Builder parameter(CodeType type, String name) { + this.parameters.add(CodeParameter.of(type, name)); + return this; + } + + public Builder setStatic() { + this.isStatic = true; + return this; + } + + public Builder setStatic(boolean value) { + this.isStatic = value; + return this; + } + + public CodeMethod build() { + Objects.requireNonNull(this.declaredClass); + Objects.requireNonNull(this.name); + return new BasicCodeMethod( + declaredClass, + returnType, + name, + List.copyOf(parameters), + isStatic + ); + } + } +} 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..edf93674 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodePackage.java @@ -0,0 +1,28 @@ +/* + * 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.impl.BasicCodePackage; + +public interface CodePackage { + String path(); + + static CodePackage of(String packageString) { + return new BasicCodePackage(packageString.split("\\.")); + } +} 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..ea41ec8f --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeParameter.java @@ -0,0 +1,30 @@ +/* + * 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.impl.BasicCodeParameter; + +public interface CodeParameter { + static CodeParameter of(CodeType type, String name) { + return new BasicCodeParameter(type, name); + } + + CodeType type(); + + String name(); +} 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..f1e5e0d5 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeType.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; + +import java.util.Set; + +public interface CodeType { + VoidType VOID = new VoidType(); + + String name(); + + String fullyQualifiedName(); + + Set collectImports(); + + class VoidType implements CodeType { + @Override + public String name() { + return "void"; + } + + @Override + public String fullyQualifiedName() { + return "void"; + } + + @Override + public Set collectImports() { + return Set.of(); + } + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeClass.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeClass.java new file mode 100644 index 00000000..1a46a752 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeClass.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.impl; + +import net.strokkur.commands.internal.codegen.CodeClass; +import net.strokkur.commands.internal.codegen.CodePackage; +import org.jspecify.annotations.Nullable; + +import java.util.Set; + +public record BasicCodeClass( + CodePackage codePackage, + @Nullable CodeClass parentClass, + String className +) implements CodeClass { + @Override + public String name() { + return className; + } + + @Override + public Set collectImports() { + return Set.of(); + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeMethod.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeMethod.java new file mode 100644 index 00000000..4132901a --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeMethod.java @@ -0,0 +1,34 @@ +/* + * 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.impl; + +import net.strokkur.commands.internal.codegen.CodeClass; +import net.strokkur.commands.internal.codegen.CodeMethod; +import net.strokkur.commands.internal.codegen.CodeParameter; +import net.strokkur.commands.internal.codegen.CodeType; + +import java.util.List; + +public record BasicCodeMethod( + CodeClass declaredClass, + CodeType returnType, + String name, + List parameters, + boolean isStatic +) implements CodeMethod { +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodePackage.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodePackage.java new file mode 100644 index 00000000..9cf8754d --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodePackage.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.codegen.impl; + +import net.strokkur.commands.internal.codegen.CodePackage; + +public record BasicCodePackage( + String[] paths +) implements CodePackage { + @Override + public String path() { + return String.join(".", paths); + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeParameter.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeParameter.java new file mode 100644 index 00000000..43794139 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeParameter.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.impl; + +import net.strokkur.commands.internal.codegen.CodeParameter; +import net.strokkur.commands.internal.codegen.CodeType; + +public record BasicCodeParameter(CodeType type, String name) implements CodeParameter { +} 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..00cd2f52 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/javadoc/CodeJavadoc.java @@ -0,0 +1,214 @@ +/* + * 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.visitor.JavadocVisitor; +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(CodeMethod method, String description) { + return see(method, description, false); + } + + static CodeJavadoc see(CodeMethod method, String description, boolean localMethod) { + return new MethodReferenceMeta("see", method, description, localMethod); + } + + static CodeJavadoc throwsMeta(CodeClass 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 emptyLine() { + 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(CodeMethod ref) { + return methodReference(ref, null); + } + + static CodeJavadoc methodReference(CodeMethod ref, @Nullable String description) { + return methodReference(ref, description, false); + } + + static CodeJavadoc methodReference(CodeMethod ref, boolean localMethod) { + return methodReference(ref, null, localMethod); + } + + static CodeJavadoc methodReference(CodeMethod ref, @Nullable String description, boolean localMethod) { + return new MethodReference(ref, 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, CodeClass codeClass, @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/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/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/printer/visitor/AbstractJavadocPrintingVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/visitor/AbstractJavadocPrintingVisitor.java new file mode 100644 index 00000000..b1cf3c35 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/visitor/AbstractJavadocPrintingVisitor.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.printer.visitor; + +import net.strokkur.commands.internal.codegen.visitor.JavadocVisitor; + +import java.util.SequencedCollection; + +/// @apiNote instances of this class cannot be reused +public abstract class AbstractJavadocPrintingVisitor implements JavadocVisitor { + protected final StringBuilder builder = new StringBuilder(); + + public abstract SequencedCollection getLines(); +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/visitor/JavaMarkdownJavadocVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/visitor/JavaMarkdownJavadocVisitor.java new file mode 100644 index 00000000..6516461d --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/visitor/JavaMarkdownJavadocVisitor.java @@ -0,0 +1,82 @@ +/* + * 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.visitor; + +import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc; + +import java.util.Arrays; +import java.util.SequencedCollection; + +public class JavaMarkdownJavadocVisitor extends JavaStarJavadocVisitor { + @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(value.codeClass().fullyQualifiedName()).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/visitor/JavaStarJavadocVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/visitor/JavaStarJavadocVisitor.java new file mode 100644 index 00000000..5ee323a5 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/visitor/JavaStarJavadocVisitor.java @@ -0,0 +1,141 @@ +/* + * 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.visitor; + +import net.strokkur.commands.internal.codegen.CodeMethod; +import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.SequencedCollection; + +public class JavaStarJavadocVisitor extends AbstractJavadocPrintingVisitor { + @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(value.codeClass().fullyQualifiedName()); + + 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(value.codeClass().fullyQualifiedName()); + 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 getMethodRefString(CodeMethod method, boolean local) { + return local + ? "#" + method.javadocName() + : method.declaredClass().fullyQualifiedName() + "#" + method.javadocName(); + } +} diff --git a/processor/common/src/test/java/net/strokkur/commands/internal/printer/visitor/CommonJavadocVisitorTests.java b/processor/common/src/test/java/net/strokkur/commands/internal/printer/visitor/CommonJavadocVisitorTests.java new file mode 100644 index 00000000..df04e5ea --- /dev/null +++ b/processor/common/src/test/java/net/strokkur/commands/internal/printer/visitor/CommonJavadocVisitorTests.java @@ -0,0 +1,143 @@ +/* + * 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.visitor; + +import net.strokkur.commands.internal.codegen.CodeClass; +import net.strokkur.commands.internal.codegen.CodeMethod; +import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc; + +import java.util.function.Supplier; + +import static net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc.author; +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.emptyLine; +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")), + emptyLine(), + 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."), + emptyLine(), + throwsMeta(CodeClass.simple("java.lang.IllegalAccessException"), "always") + ); + } + + CodeClass sourceClass() { + return CodeClass.simple("com.example.CommandClass"); + } + + CodeClass targetClass() { + return CodeClass.simple("com.example.CommandClassBrigadier"); + } + + CodeMethod createMethod() { + return CodeMethod.builder(targetClass(), "create") + .build(); + } + + CodeMethod registerMethod() { + return CodeMethod.builder(targetClass(), "register") + .parameter(CodeClass.simple("io.papermc.paper.command.brigadier.Commands"), "commands") + .build(); + } + + CodeMethod bootstrapMethod() { + return CodeMethod.builder() + .declaringClass(CodeClass.simple("io.papermc.paper.plugin.bootstrap.PluginBootstrap")) + .name("bootstrap") + .parameter(CodeClass.simple("io.papermc.paper.plugin.bootstrap.BootstrapContext"), "context") + .build(); + } + + CodeMethod onLoadMethod() { + return CodeMethod.builder() + .declaringClass(CodeClass.simple("org.bukkit.plugin.java.JavaPlugin")) + .name("onLoad") + .build(); + } + + CodeMethod onEnableMethod() { + return CodeMethod.builder() + .declaringClass(CodeClass.simple("org.bukkit.plugin.java.JavaPlugin")) + .name("onEnable") + .build(); + } +} diff --git a/processor/common/src/test/java/net/strokkur/commands/internal/printer/visitor/JavadocMarkdownVisitorTests.java b/processor/common/src/test/java/net/strokkur/commands/internal/printer/visitor/JavadocMarkdownVisitorTests.java new file mode 100644 index 00000000..ba0d9d0a --- /dev/null +++ b/processor/common/src/test/java/net/strokkur/commands/internal/printer/visitor/JavadocMarkdownVisitorTests.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.printer.visitor; + +import org.junit.jupiter.api.Test; + +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 java.lang.IllegalAccessException always"""; + checkOutput(expected, ctorJd(), JavaMarkdownJavadocVisitor::new); + } +} diff --git a/processor/common/src/test/java/net/strokkur/commands/internal/printer/visitor/JavadocStarVisitorTests.java b/processor/common/src/test/java/net/strokkur/commands/internal/printer/visitor/JavadocStarVisitorTests.java new file mode 100644 index 00000000..ed99b230 --- /dev/null +++ b/processor/common/src/test/java/net/strokkur/commands/internal/printer/visitor/JavadocStarVisitorTests.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.visitor; + +import org.junit.jupiter.api.Test; + +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 java.lang.IllegalAccessException always + */"""; + checkOutput(expected, ctorJd(), JavaStarJavadocVisitor::new); + } +} From 62d20c6a33248b20938dcbbbd1e5f1f21d40c918 Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Sat, 9 May 2026 13:14:42 +0200 Subject: [PATCH 02/16] Implement Java code gen --- .../internal/codegen/CodeAnnotation.java | 15 ++ .../commands/internal/codegen/CodeBlock.java | 9 + .../commands/internal/codegen/CodeClass.java | 71 ++++++- .../internal/codegen/CodeConstructor.java | 20 ++ .../internal/codegen/CodeExpression.java | 93 ++++++++ .../commands/internal/codegen/CodeField.java | 22 ++ .../commands/internal/codegen/CodeMethod.java | 143 +++++++------ .../internal/codegen/CodePackage.java | 22 +- .../internal/codegen/CodeParameter.java | 16 +- .../internal/codegen/CodeStatement.java | 91 ++++++++ .../commands/internal/codegen/CodeType.java | 80 ++++++- .../internal/codegen/InvokesMethod.java | 29 +++ .../commands/internal/codegen/Modifiers.java | 31 +++ .../internal/codegen/builder/Builders.java | 34 +++ .../codegen/builder/ClassBuilder.java | 92 ++++++++ .../codegen/builder/FieldBuilder.java | 61 ++++++ .../codegen/builder/MethodBuilder.java | 106 ++++++++++ .../internal/codegen/impl/BasicCodeClass.java | 40 ---- .../codegen/impl/BasicCodeMethod.java | 34 --- .../codegen/impl/BasicCodePackage.java | 29 --- .../codegen/impl/BasicCodeParameter.java | 24 --- .../internal/codegen/javadoc/CodeJavadoc.java | 5 +- .../codegen/visitor/CodeVisitable.java | 5 + .../internal/codegen/visitor/CodeVisitor.java | 31 +++ .../AbstractCommandTreePrintingVisitor.java | 112 ++++++++++ .../command/ImportGatheringVisitor.java | 138 ++++++++++++ .../JavaCommandTreePrintingVisitor.java | 200 ++++++++++++++++++ .../AbstractJavadocPrintingVisitor.java | 2 +- .../JavaMarkdownJavadocVisitor.java | 2 +- .../JavaStarJavadocVisitor.java | 4 +- .../codegen/FullCommandClassBuilderTests.java | 139 ++++++++++++ .../CommonJavadocVisitorTests.java | 32 +-- .../JavadocMarkdownVisitorTests.java | 2 +- .../JavadocStarVisitorTests.java | 2 +- 34 files changed, 1488 insertions(+), 248 deletions(-) create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeAnnotation.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeBlock.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeConstructor.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeExpression.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeField.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeStatement.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/InvokesMethod.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/Modifiers.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/Builders.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/ClassBuilder.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/FieldBuilder.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/MethodBuilder.java delete mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeClass.java delete mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeMethod.java delete mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodePackage.java delete mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeParameter.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/visitor/CodeVisitable.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/visitor/CodeVisitor.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/printer/command/AbstractCommandTreePrintingVisitor.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaCommandTreePrintingVisitor.java rename processor/common/src/main/java/net/strokkur/commands/internal/printer/{visitor => javadoc}/AbstractJavadocPrintingVisitor.java (95%) rename processor/common/src/main/java/net/strokkur/commands/internal/printer/{visitor => javadoc}/JavaMarkdownJavadocVisitor.java (97%) rename processor/common/src/main/java/net/strokkur/commands/internal/printer/{visitor => javadoc}/JavaStarJavadocVisitor.java (97%) create mode 100644 processor/common/src/test/java/net/strokkur/commands/internal/codegen/FullCommandClassBuilderTests.java rename processor/common/src/test/java/net/strokkur/commands/internal/printer/{visitor => javadoc}/CommonJavadocVisitorTests.java (84%) rename processor/common/src/test/java/net/strokkur/commands/internal/printer/{visitor => javadoc}/JavadocMarkdownVisitorTests.java (98%) rename processor/common/src/test/java/net/strokkur/commands/internal/printer/{visitor => javadoc}/JavadocStarVisitorTests.java (98%) 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..d30f0507 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeAnnotation.java @@ -0,0 +1,15 @@ +package net.strokkur.commands.internal.codegen; + +import net.strokkur.commands.internal.codegen.visitor.CodeVisitable; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitor; + +public record CodeAnnotation(CodeType.ClassType type) implements CodeVisitable { + 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..3e6d92b0 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeBlock.java @@ -0,0 +1,9 @@ +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 index 31b58134..1e41f734 100644 --- 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 @@ -17,26 +17,77 @@ */ package net.strokkur.commands.internal.codegen; -import net.strokkur.commands.internal.codegen.impl.BasicCodeClass; -import net.strokkur.commands.internal.codegen.impl.BasicCodePackage; +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 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 interface CodeClass extends CodeType { +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 { + public static CodeClass STRING = CodeClass.simple("java.lang.String"); - static CodeClass simple(String 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("\\."); - return new BasicCodeClass(new BasicCodePackage(Arrays.copyOf(split, split.length - 1)), null, split[split.length - 1]); + return new CodeClass( + new CodePackage(Arrays.copyOf(split, split.length - 1)), + null, + split[split.length - 1] + ); + } + + @Override + public R accept(CodeVisitor visitor) { + return visitor.visitClass(this); } - CodePackage codePackage(); + public String fullyQualifiedName() { + return codePackage().path() + "." + name(); + } - @Nullable CodeClass parentClass(); + @Override + @UnmodifiableView + public Set modifiers() { + return Collections.unmodifiableSet(modifiers); + } - String className(); + @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); + } - default String fullyQualifiedName() { - return codePackage().path() + "." + className(); + @Override + @UnmodifiableView + public List typeParameters() { + return Collections.unmodifiableList(typeParameters); } } diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeConstructor.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeConstructor.java new file mode 100644 index 00000000..d7a4306b --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeConstructor.java @@ -0,0 +1,20 @@ +package net.strokkur.commands.internal.codegen; + +import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc; +import org.jspecify.annotations.Nullable; + +import java.util.List; +import java.util.Set; + +public class CodeConstructor extends CodeMethod { + public CodeConstructor( + CodeClass declaredClass, + List parameters, + Set modifiers, + @Nullable CodeJavadoc javadoc, + CodeBlock codeBlock, + Set throwsExceptions + ) { + super(declaredClass, CodeType.ofClass(declaredClass), declaredClass.name(), parameters, modifiers, javadoc, codeBlock, 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..682fed51 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeExpression.java @@ -0,0 +1,93 @@ +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.List; + +public sealed interface CodeExpression extends CodeVisitable { + static Null nullExpr() { + return Null.INSTANCE; + } + + static StringLiteral string(String value) { + return new StringLiteral(value); + } + + static MethodInvocation methodCall(CodeMethod method, List parameters) { + return new MethodInvocation(method, parameters, null); + } + + static MethodInvocation methodCall(CodeMethod method, List parameters, String instanceVariable) { + return new MethodInvocation(method, parameters, instanceVariable); + } + + static ConstructorInvocation constructorCall(CodeType.ClassType type, List parameters) { + return new ConstructorInvocation(type, parameters); + } + + static Variable variable(String name) { + return new Variable(name); + } + + @Override + default R accept(CodeVisitor visitor) { + return visitor.visitExpression(this); + } + + final class StringLiteral implements CodeExpression { + private final String value; + + private StringLiteral(String value) { + this.value = value; + } + + public String value() { + return 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; + } + } + + final class MethodInvocation extends InvokesMethod implements CodeExpression { + public MethodInvocation(CodeMethod method, List parameters, @Nullable String instanceVariable) { + super(method, parameters, instanceVariable); + } + } + + final class ConstructorInvocation implements CodeExpression { + private final CodeType.ClassType type; + private final List parameters; + + private ConstructorInvocation(CodeType.ClassType type, List parameters) { + this.type = type; + this.parameters = parameters; + } + + public CodeType.ClassType type() { + return type; + } + + public List parameters() { + return parameters; + } + } +} 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..77657339 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeField.java @@ -0,0 +1,22 @@ +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.List; +import java.util.Set; + +public record CodeField( + String name, + CodeType type, + @Nullable CodeExpression initialiser, + Set modifiers, + List annotations +) implements CodeVisitable { + + @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 index bf453839..704f30df 100644 --- 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 @@ -17,99 +17,104 @@ */ package net.strokkur.commands.internal.codegen; -import net.strokkur.commands.internal.codegen.impl.BasicCodeMethod; +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 org.jspecify.annotations.Nullable; -import java.util.ArrayList; import java.util.List; -import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; -public interface CodeMethod { +public class CodeMethod implements CodeVisitable { + 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 Set throwsExceptions; + + public CodeMethod( + CodeClass declaredClass, + CodeType returnType, + String name, + List parameters + ) { + this.declaredClass = declaredClass; + this.returnType = returnType; + this.name = name; + this.parameters = parameters; + this.modifiers = Set.of(); + this.javadoc = null; + this.codeBlock = new CodeBlock(List.of()); + this.throwsExceptions = Set.of(); + } - static CodeMethod.Builder builder() { - return new Builder(); + public CodeMethod( + CodeClass declaredClass, + CodeType returnType, + String name, + List parameters, + Set modifiers, + @Nullable CodeJavadoc javadoc, + CodeBlock codeBlock, + Set throwsExceptions + ) { + this.declaredClass = declaredClass; + this.returnType = returnType; + this.name = name; + this.parameters = parameters; + this.modifiers = modifiers; + this.javadoc = javadoc; + this.codeBlock = codeBlock; + this.throwsExceptions = throwsExceptions; } - static CodeMethod.Builder builder(CodeClass declaringClass, String name) { - return new Builder() - .declaringClass(declaringClass) - .name(name); + @Override + public R accept(CodeVisitor visitor) { + return visitor.visitMethod(this); } /// Example: `methodName` - String name(); + public String name() { + return name; + } /// Example: `methodName(String, int)` - default String javadocName() { + public String javadocName() { return name() + "(" + parameters().stream() .map(param -> param.type().fullyQualifiedName()) .collect(Collectors.joining(", ")) + ")"; } - CodeClass declaredClass(); - - CodeType returnType(); - - List parameters(); - - boolean isStatic(); - - class Builder { - private @Nullable CodeClass declaredClass = null; - private @Nullable String name = null; - - private CodeType returnType = CodeType.VOID; - private boolean isStatic = false; - private final List parameters = new ArrayList<>(); - - public Builder declaringClass(CodeClass declaringClass) { - this.declaredClass = declaringClass; - return this; - } - - public Builder returnType(CodeType returnType) { - this.returnType = returnType; - return this; - } + public CodeClass declaredClass() { + return declaredClass; + } - public Builder name(String name) { - this.name = name; - return this; - } + public CodeType returnType() { + return returnType; + } - public Builder parameters(List parameters) { - this.parameters.clear(); - this.parameters.addAll(parameters); - return this; - } + public List parameters() { + return parameters; + } - public Builder parameter(CodeType type, String name) { - this.parameters.add(CodeParameter.of(type, name)); - return this; - } + public Set modifiers() { + return modifiers; + } - public Builder setStatic() { - this.isStatic = true; - return this; - } + public @Nullable CodeJavadoc javadoc() { + return javadoc; + } - public Builder setStatic(boolean value) { - this.isStatic = value; - return this; - } + public CodeBlock codeBlock() { + return codeBlock; + } - public CodeMethod build() { - Objects.requireNonNull(this.declaredClass); - Objects.requireNonNull(this.name); - return new BasicCodeMethod( - declaredClass, - returnType, - name, - List.copyOf(parameters), - isStatic - ); - } + public Set throwsExceptions() { + return throwsExceptions; } } 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 index edf93674..6ac55e4f 100644 --- 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 @@ -17,12 +17,26 @@ */ package net.strokkur.commands.internal.codegen; -import net.strokkur.commands.internal.codegen.impl.BasicCodePackage; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitable; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitor; -public interface CodePackage { - String path(); +public class CodePackage implements CodeVisitable { + private final String[] paths; static CodePackage of(String packageString) { - return new BasicCodePackage(packageString.split("\\.")); + 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); } } 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 index ea41ec8f..71fd3f56 100644 --- 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 @@ -17,14 +17,16 @@ */ package net.strokkur.commands.internal.codegen; -import net.strokkur.commands.internal.codegen.impl.BasicCodeParameter; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitable; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitor; -public interface CodeParameter { - static CodeParameter of(CodeType type, String name) { - return new BasicCodeParameter(type, name); +public record CodeParameter(CodeType type, String name) implements CodeVisitable { + public static CodeParameter of(CodeType type, String name) { + return new CodeParameter(type, name); } - CodeType type(); - - String 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..404aef62 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeStatement.java @@ -0,0 +1,91 @@ +package net.strokkur.commands.internal.codegen; + +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; + +public sealed interface CodeStatement extends CodeVisitable { + static VariableDeclaration variableDeclaration(CodeType type, String name, @Nullable CodeExpression assignment) { + return new VariableDeclaration(type, name, assignment); + } + + static ReturnStatement returnStatement(@Nullable CodeExpression returnExpression) { + return new ReturnStatement(returnExpression); + } + + static ThrowStatement throwStatement(CodeExpression throwExpression) { + return new ThrowStatement(throwExpression); + } + + static MethodInvocation methodInvocation(CodeMethod method, List parameters) { + return new MethodInvocation(method, parameters, null); + } + + static MethodInvocation methodInvocation(CodeMethod method, List parameters, @Nullable String instanceVariable) { + return new MethodInvocation(method, parameters, instanceVariable); + } + + @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 VariableDeclaration(CodeType type, String name, @Nullable CodeExpression assignment) { + this.type = type; + this.name = name; + this.assignment = assignment; + } + + public CodeType type() { + return type; + } + + public String name() { + return name; + } + + @Contract(pure = true) + public @Nullable CodeExpression assignment() { + return assignment; + } + } + + 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; + } + } + + final class MethodInvocation extends InvokesMethod implements CodeStatement { + public MethodInvocation(CodeMethod method, List parameters, @Nullable String instanceVariable) { + super(method, parameters, instanceVariable); + } + } +} 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 index f1e5e0d5..721c80a2 100644 --- 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 @@ -17,31 +17,95 @@ */ package net.strokkur.commands.internal.codegen; -import java.util.Set; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitable; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitor; -public interface CodeType { +public interface CodeType extends CodeVisitable { 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"); + + static GenericType generic(String name) { + return new GenericType(name); + } + + static ClassType ofClass(CodeClass codeClass) { + return new ClassType(codeClass); + } + + static ClassType ofClass(String fqn) { + return new ClassType(CodeClass.simple(fqn)); + } String name(); String fullyQualifiedName(); - Set collectImports(); + @Override + default R accept(CodeVisitor visitor) { + return visitor.visitType(this); + } + + abstract class SimpleType implements CodeType { + private final String name; + + private SimpleType(String name) { + this.name = name; + } - class VoidType implements CodeType { @Override public String name() { - return "void"; + return name; } @Override public String fullyQualifiedName() { - return "void"; + 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 GenericType(String name) { + super(name); + } + } + + class ClassType implements CodeType { + private final CodeClass codeClass; + + private ClassType(CodeClass codeClass) { + this.codeClass = codeClass; + } + + public CodeClass codeClass() { + return codeClass; } @Override - public Set collectImports() { - return Set.of(); + public String name() { + return codeClass.name(); + } + + @Override + public String fullyQualifiedName() { + return codeClass.fullyQualifiedName(); } } } 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..ca2bd8e8 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/InvokesMethod.java @@ -0,0 +1,29 @@ +package net.strokkur.commands.internal.codegen; + +import org.jspecify.annotations.Nullable; + +import java.util.List; + +public abstract class InvokesMethod { + private final CodeMethod method; + private final List parameters; + private final @Nullable String instanceVariable; + + public InvokesMethod(CodeMethod method, List parameters, @Nullable String instanceVariable) { + this.method = method; + this.parameters = parameters; + this.instanceVariable = instanceVariable; + } + + public CodeMethod method() { + return method; + } + + public List parameters() { + return parameters; + } + + public @Nullable String instanceVariable() { + return instanceVariable; + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/Modifiers.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/Modifiers.java new file mode 100644 index 00000000..599e2796 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/Modifiers.java @@ -0,0 +1,31 @@ +package net.strokkur.commands.internal.codegen; + +import java.util.Locale; + +public enum Modifiers { + // Visibility + PUBLIC(0), + PRIVATE(1), + + // OOP + ABSTRACT(10), + DEFAULT(10), + + // Misc. + FINAL(5), + STATIC(4); + private final int priority; + + Modifiers(int priority) { + this.priority = priority; + } + + public int priority() { + return priority; + } + + @Override + public String toString() { + return this.name().toLowerCase(Locale.ROOT); + } +} 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..06c97eb8 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/Builders.java @@ -0,0 +1,34 @@ +package net.strokkur.commands.internal.codegen.builder; + +import net.strokkur.commands.internal.codegen.CodeClass; +import net.strokkur.commands.internal.codegen.CodePackage; +import net.strokkur.commands.internal.codegen.CodeType; + +import java.util.Arrays; + +public class Builders { + public static MethodBuilder method() { + return new MethodBuilder(); + } + + public static MethodBuilder method(CodeClass declaringClass, String name) { + return new MethodBuilder() + .setDeclaringClass(declaringClass) + .setName(name); + } + + 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))); + } +} 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..dec36cc7 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/ClassBuilder.java @@ -0,0 +1,92 @@ +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 org.jspecify.annotations.Nullable; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class ClassBuilder { + 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 CodeClass parentClass) { + this.parentClass = parentClass; + return this; + } + + public ClassBuilder setModifiers(Set modifiers) { + this.modifiers = modifiers; + return this; + } + + public ClassBuilder setAnnotations(List annotations) { + this.annotations = 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(CodeMethod method) { + this.methods.add(method); + return this; + } + + public ClassBuilder addMethod(MethodBuilder methodBuilder) { + this.methods.add(methodBuilder.build()); + return this; + } + + public ClassBuilder addField(CodeField field) { + this.fields.add(field); + return this; + } + + public ClassBuilder addField(FieldBuilder fieldBuilder) { + this.fields.add(fieldBuilder.build()); + 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 + ); + } +} 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..3c356489 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/FieldBuilder.java @@ -0,0 +1,61 @@ +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 org.jspecify.annotations.Nullable; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +public class FieldBuilder { + 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(@Nullable CodeExpression initialiser) { + this.initialiser = initialiser; + return this; + } + + 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, EnumSet.copyOf(modifiers), List.copyOf(annotations) + ); + } +} 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..7a578e8a --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/MethodBuilder.java @@ -0,0 +1,106 @@ +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.javadoc.CodeJavadoc; +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 MethodBuilder { + private @Nullable CodeClass declaredClass = null; + private @Nullable String name = null; + + private CodeType returnType = CodeType.VOID; + private List parameters = new ArrayList<>(); + private Set modifiers = new HashSet<>(); + private @Nullable CodeJavadoc javadoc = null; + private CodeBlock codeBlock = new CodeBlock(List.of()); + private Set throwsExceptions = Set.of(); + + MethodBuilder() { + // package-private ctor + } + + public MethodBuilder setDeclaringClass(CodeClass declaringClass) { + this.declaredClass = declaringClass; + return this; + } + + public MethodBuilder setName(String name) { + this.name = name; + 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 setParameters(List parameters) { + this.parameters = parameters; + return this; + } + + public MethodBuilder setModifiers(Set modifiers) { + this.modifiers = modifiers; + return this; + } + + public MethodBuilder setJavadoc(@Nullable CodeJavadoc javadoc) { + this.javadoc = javadoc; + return this; + } + + public MethodBuilder setCodeBlock(List statements) { + this.codeBlock = new CodeBlock(statements); + return this; + } + + public MethodBuilder setThrowsExceptions(Set throwsExceptions) { + this.throwsExceptions = throwsExceptions; + return this; + } + + public CodeMethod build() { + Objects.requireNonNull(this.declaredClass); + Objects.requireNonNull(this.name); + return new CodeMethod( + declaredClass, + returnType, + name, + List.copyOf(parameters), + Set.copyOf(modifiers), + javadoc, + codeBlock, + Set.copyOf(throwsExceptions) + ); + } + + public CodeConstructor buildConstructor() { + Objects.requireNonNull(this.declaredClass); + return new CodeConstructor( + declaredClass, + List.copyOf(parameters), + Set.copyOf(modifiers), + javadoc, + codeBlock, + Set.copyOf(throwsExceptions) + ); + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeClass.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeClass.java deleted file mode 100644 index 1a46a752..00000000 --- a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeClass.java +++ /dev/null @@ -1,40 +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.codegen.impl; - -import net.strokkur.commands.internal.codegen.CodeClass; -import net.strokkur.commands.internal.codegen.CodePackage; -import org.jspecify.annotations.Nullable; - -import java.util.Set; - -public record BasicCodeClass( - CodePackage codePackage, - @Nullable CodeClass parentClass, - String className -) implements CodeClass { - @Override - public String name() { - return className; - } - - @Override - public Set collectImports() { - return Set.of(); - } -} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeMethod.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeMethod.java deleted file mode 100644 index 4132901a..00000000 --- a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeMethod.java +++ /dev/null @@ -1,34 +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.codegen.impl; - -import net.strokkur.commands.internal.codegen.CodeClass; -import net.strokkur.commands.internal.codegen.CodeMethod; -import net.strokkur.commands.internal.codegen.CodeParameter; -import net.strokkur.commands.internal.codegen.CodeType; - -import java.util.List; - -public record BasicCodeMethod( - CodeClass declaredClass, - CodeType returnType, - String name, - List parameters, - boolean isStatic -) implements CodeMethod { -} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodePackage.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodePackage.java deleted file mode 100644 index 9cf8754d..00000000 --- a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodePackage.java +++ /dev/null @@ -1,29 +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.codegen.impl; - -import net.strokkur.commands.internal.codegen.CodePackage; - -public record BasicCodePackage( - String[] paths -) implements CodePackage { - @Override - public String path() { - return String.join(".", paths); - } -} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeParameter.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeParameter.java deleted file mode 100644 index 43794139..00000000 --- a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/impl/BasicCodeParameter.java +++ /dev/null @@ -1,24 +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.codegen.impl; - -import net.strokkur.commands.internal.codegen.CodeParameter; -import net.strokkur.commands.internal.codegen.CodeType; - -public record BasicCodeParameter(CodeType type, String name) implements CodeParameter { -} 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 index 00cd2f52..f21b697c 100644 --- 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 @@ -19,6 +19,7 @@ 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.visitor.JavadocVisitor; import org.jspecify.annotations.Nullable; @@ -60,7 +61,7 @@ static CodeJavadoc see(CodeMethod method, String description, boolean localMetho return new MethodReferenceMeta("see", method, description, localMethod); } - static CodeJavadoc throwsMeta(CodeClass exception, @Nullable String description) { + static CodeJavadoc throwsMeta(CodeType.ClassType exception, @Nullable String description) { return new ClassReferenceMeta("throws", exception, description); } @@ -142,7 +143,7 @@ public void accept(JavadocVisitor visitor) { } } - record ClassReferenceMeta(String descriptor, CodeClass codeClass, @Nullable String text) implements CodeJavadoc { + record ClassReferenceMeta(String descriptor, CodeType.ClassType type, @Nullable String text) 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..284e216c --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/visitor/CodeVisitable.java @@ -0,0 +1,5 @@ +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..9657ebe6 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/visitor/CodeVisitor.java @@ -0,0 +1,31 @@ +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/printer/command/AbstractCommandTreePrintingVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/AbstractCommandTreePrintingVisitor.java new file mode 100644 index 00000000..16360108 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/AbstractCommandTreePrintingVisitor.java @@ -0,0 +1,112 @@ +package net.strokkur.commands.internal.printer.command; + +import net.strokkur.commands.internal.codegen.CodeAnnotation; +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 AbstractCommandTreePrintingVisitor implements CodeVisitor { + protected final Supplier javadocPrintingVisitor; + private final String indentString; + private int indentation = 0; + + public AbstractCommandTreePrintingVisitor(Supplier javadocPrintingVisitor, String indentString) { + this.javadocPrintingVisitor = javadocPrintingVisitor; + this.indentString = indentString; + } + + protected final void appendIndented(Runnable run) { + incrementIndent(); + run.run(); + decrementIndent(); + } + + protected final StringBuilder appendIndented(Consumer run) { + final StringBuilder builder = new StringBuilder(); + incrementIndent(); + run.accept(builder); + decrementIndent(); + return builder; + } + + 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); + } + + protected final void incrementIndent() { + indentation++; + } + + protected final void decrementIndent() { + indentation--; + } + + // 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 appendMethodInvocation(StringBuilder builder, InvokesMethod method) { + if (method.instanceVariable() != null) { + builder.append(method.instanceVariable()).append("."); + } + builder.append(method.method().name()); + builder.append("("); + builder.append(joining(method.parameters())); + builder.append(")"); + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java new file mode 100644 index 00000000..6be05832 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java @@ -0,0 +1,138 @@ +package net.strokkur.commands.internal.printer.command; + +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.visitor.CodeVisitable; +import net.strokkur.commands.internal.codegen.visitor.CodeVisitor; + +import java.util.Collection; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class ImportGatheringVisitor implements CodeVisitor> { + + @SafeVarargs + private Set join(Set... all) { + return Stream.of(all) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + } + + 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(codeClass.fullyQualifiedName()), + 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) + ); + } + + @Override + public Set visitPackage(CodePackage codePackage) { + return Set.of(); + } + + @Override + public Set visitParameter(CodeParameter codeParameter) { + return codeParameter.type().accept(this); + } + + @Override + public Set visitType(CodeType codeType) { + return codeType instanceof CodeType.ClassType codeClass ? + Set.of(codeClass.fullyQualifiedName()) : + Set.of(); + } + + @Override + public Set visitAnnotation(CodeAnnotation codeAnnotation) { + return Set.of(codeAnnotation.type().fullyQualifiedName()); + } + + @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 methodInvocation -> { + if (methodInvocation.instanceVariable() == null) { + yield join( + methodInvocation.method().declaredClass().accept(this), + collect(methodInvocation.parameters()) + ); + } + yield collect(methodInvocation.parameters()); + } + + case CodeExpression.ConstructorInvocation ctorInvocation -> join( + ctorInvocation.type().accept(this), + collect(ctorInvocation.parameters()) + ); + 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 methodInvocation -> { + if (methodInvocation.instanceVariable() == null) { + yield join( + methodInvocation.method().declaredClass().accept(this), + collect(methodInvocation.parameters()) + ); + } + yield collect(methodInvocation.parameters()); + } + + default -> Set.of(); + }; + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaCommandTreePrintingVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaCommandTreePrintingVisitor.java new file mode 100644 index 00000000..4f0e9d2e --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaCommandTreePrintingVisitor.java @@ -0,0 +1,200 @@ +package net.strokkur.commands.internal.printer.command; + +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.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.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 JavaCommandTreePrintingVisitor extends AbstractCommandTreePrintingVisitor { + public JavaCommandTreePrintingVisitor(Supplier javadocPrintingVisitor, String indentString) { + super(javadocPrintingVisitor, indentString); + } + + @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(() -> { + codeClass.fields().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 visitPackage(CodePackage codePackage) { + return new StringBuilder(); + } + + @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) { + return new StringBuilder(codeType.name()); + } + + @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()); + 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.ConstructorInvocation constructorInvocation -> { + builder.append("new "); + appendNested(builder, constructorInvocation.type()); + builder.append("("); + builder.append(joining(constructorInvocation.parameters())); + builder.append(")"); + } + case CodeExpression.MethodInvocation methodInvocation -> { + appendMethodInvocation(builder, methodInvocation); + } + case CodeExpression.Null ignored -> { + builder.append("null"); + } + case CodeExpression.StringLiteral stringLiteral -> { + builder.append('"').append(stringLiteral.value()).append('"'); + } + case CodeExpression.Variable variable -> { + builder.append(variable.name()); + } + } + }); + } + + @Override + public StringBuilder visitStatement(CodeStatement codeStatement) { + return append(builder -> { + appendIndent(builder); + switch (codeStatement) { + case CodeStatement.MethodInvocation methodInvocation -> { + appendMethodInvocation(builder, methodInvocation); + } + 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 -> { + appendNested(builder, variableDeclaration.type()); + builder.append(" "); + builder.append(variableDeclaration.name()); + if (variableDeclaration.assignment() != null) { + builder.append(" = "); + appendNested(builder, variableDeclaration.assignment()); + } + } + } + builder.append(";\n"); + }); + } +} diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/visitor/AbstractJavadocPrintingVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/javadoc/AbstractJavadocPrintingVisitor.java similarity index 95% rename from processor/common/src/main/java/net/strokkur/commands/internal/printer/visitor/AbstractJavadocPrintingVisitor.java rename to processor/common/src/main/java/net/strokkur/commands/internal/printer/javadoc/AbstractJavadocPrintingVisitor.java index b1cf3c35..930ef274 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/visitor/AbstractJavadocPrintingVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/javadoc/AbstractJavadocPrintingVisitor.java @@ -15,7 +15,7 @@ * 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.visitor; +package net.strokkur.commands.internal.printer.javadoc; import net.strokkur.commands.internal.codegen.visitor.JavadocVisitor; diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/visitor/JavaMarkdownJavadocVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/javadoc/JavaMarkdownJavadocVisitor.java similarity index 97% rename from processor/common/src/main/java/net/strokkur/commands/internal/printer/visitor/JavaMarkdownJavadocVisitor.java rename to processor/common/src/main/java/net/strokkur/commands/internal/printer/javadoc/JavaMarkdownJavadocVisitor.java index 6516461d..e1e8e155 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/visitor/JavaMarkdownJavadocVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/javadoc/JavaMarkdownJavadocVisitor.java @@ -15,7 +15,7 @@ * 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.visitor; +package net.strokkur.commands.internal.printer.javadoc; import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc; diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/visitor/JavaStarJavadocVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/javadoc/JavaStarJavadocVisitor.java similarity index 97% rename from processor/common/src/main/java/net/strokkur/commands/internal/printer/visitor/JavaStarJavadocVisitor.java rename to processor/common/src/main/java/net/strokkur/commands/internal/printer/javadoc/JavaStarJavadocVisitor.java index 5ee323a5..23d719e0 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/visitor/JavaStarJavadocVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/javadoc/JavaStarJavadocVisitor.java @@ -15,7 +15,7 @@ * 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.visitor; +package net.strokkur.commands.internal.printer.javadoc; import net.strokkur.commands.internal.codegen.CodeMethod; import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc; @@ -70,7 +70,7 @@ public void visit(CodeJavadoc.ClassReferenceMeta value) { .append('@') .append(value.descriptor()) .append(' ') - .append(value.codeClass().fullyQualifiedName()); + .append(value.type().fullyQualifiedName()); if (value.text() != null) { builder.append(' ').append(value.text()); 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..b5061fe6 --- /dev/null +++ b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/FullCommandClassBuilderTests.java @@ -0,0 +1,139 @@ +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.command.ImportGatheringVisitor; +import net.strokkur.commands.internal.printer.command.JavaCommandTreePrintingVisitor; +import net.strokkur.commands.internal.printer.javadoc.JavaMarkdownJavadocVisitor; +import net.strokkur.commands.internal.util.Classes; +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() + .collect(Collectors.joining("\n")); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testFullClassExampleJavaPrinter() { + final CodeClass exampleClass = constructExampleCommandClass(); + final JavaCommandTreePrintingVisitor visitor = new JavaCommandTreePrintingVisitor(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 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 java.lang.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.of(CodeType.ofClass(CodeClass.simple(Classes.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.of(CodeType.ofClass(CodeClass.simple(Classes.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") + .setCodeBlock(List.of( + CodeStatement.methodInvocation( + Builders.method(commands, "register").build(), + List.of( + CodeExpression.methodCall(Builders.method(current, "create").build(), List.of()), + CodeExpression.variable("DESCRIPTION"), + CodeExpression.variable("ALIASES") + ), + "commands" + ) + )) + ) + .addMethod(Builders.method(current, "create") + .setModifiers(Set.of(Modifiers.PUBLIC, Modifiers.STATIC)) + ) + .addMethod(Builders.method() + .setDeclaringClass(current) + .setThrowsExceptions(Set.of(CodeType.ofClass("java.lang.IllegalAccessException"))) + .setModifiers(Set.of(Modifiers.PRIVATE)) + .setJavadoc(CodeJavadoc.combineLines( + CodeJavadoc.text("The constructor is inaccessible."), + CodeJavadoc.emptyLine(), + CodeJavadoc.throwsMeta(CodeType.ofClass("java.lang.IllegalAccessException"), "always") + )) + .setCodeBlock(List.of( + CodeStatement.throwStatement(CodeExpression.constructorCall( + CodeType.ofClass("java.lang.IllegalAccessException"), + List.of(CodeExpression.string("This class cannot be instantiated.")) + )) + )) + .buildConstructor() + ) + .build(); + } +} diff --git a/processor/common/src/test/java/net/strokkur/commands/internal/printer/visitor/CommonJavadocVisitorTests.java b/processor/common/src/test/java/net/strokkur/commands/internal/printer/javadoc/CommonJavadocVisitorTests.java similarity index 84% rename from processor/common/src/test/java/net/strokkur/commands/internal/printer/visitor/CommonJavadocVisitorTests.java rename to processor/common/src/test/java/net/strokkur/commands/internal/printer/javadoc/CommonJavadocVisitorTests.java index df04e5ea..eae6e5a7 100644 --- a/processor/common/src/test/java/net/strokkur/commands/internal/printer/visitor/CommonJavadocVisitorTests.java +++ b/processor/common/src/test/java/net/strokkur/commands/internal/printer/javadoc/CommonJavadocVisitorTests.java @@ -15,10 +15,12 @@ * 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.visitor; +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.CodeType; +import net.strokkur.commands.internal.codegen.builder.Builders; import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc; import java.util.function.Supplier; @@ -96,7 +98,7 @@ CodeJavadoc ctorJd() { 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."), emptyLine(), - throwsMeta(CodeClass.simple("java.lang.IllegalAccessException"), "always") + throwsMeta(CodeType.ofClass("java.lang.IllegalAccessException"), "always") ); } @@ -109,35 +111,35 @@ CodeClass targetClass() { } CodeMethod createMethod() { - return CodeMethod.builder(targetClass(), "create") + return Builders.method(targetClass(), "create") .build(); } CodeMethod registerMethod() { - return CodeMethod.builder(targetClass(), "register") - .parameter(CodeClass.simple("io.papermc.paper.command.brigadier.Commands"), "commands") + return Builders.method(targetClass(), "register") + .addParameter(CodeType.ofClass(CodeClass.simple("io.papermc.paper.command.brigadier.Commands")), "commands") .build(); } CodeMethod bootstrapMethod() { - return CodeMethod.builder() - .declaringClass(CodeClass.simple("io.papermc.paper.plugin.bootstrap.PluginBootstrap")) - .name("bootstrap") - .parameter(CodeClass.simple("io.papermc.paper.plugin.bootstrap.BootstrapContext"), "context") + return Builders.method() + .setDeclaringClass(CodeClass.simple("io.papermc.paper.plugin.bootstrap.PluginBootstrap")) + .setName("bootstrap") + .addParameter(CodeType.ofClass(CodeClass.simple("io.papermc.paper.plugin.bootstrap.BootstrapContext")), "context") .build(); } CodeMethod onLoadMethod() { - return CodeMethod.builder() - .declaringClass(CodeClass.simple("org.bukkit.plugin.java.JavaPlugin")) - .name("onLoad") + return Builders.method() + .setDeclaringClass(CodeClass.simple("org.bukkit.plugin.java.JavaPlugin")) + .setName("onLoad") .build(); } CodeMethod onEnableMethod() { - return CodeMethod.builder() - .declaringClass(CodeClass.simple("org.bukkit.plugin.java.JavaPlugin")) - .name("onEnable") + return Builders.method() + .setDeclaringClass(CodeClass.simple("org.bukkit.plugin.java.JavaPlugin")) + .setName("onEnable") .build(); } } diff --git a/processor/common/src/test/java/net/strokkur/commands/internal/printer/visitor/JavadocMarkdownVisitorTests.java b/processor/common/src/test/java/net/strokkur/commands/internal/printer/javadoc/JavadocMarkdownVisitorTests.java similarity index 98% rename from processor/common/src/test/java/net/strokkur/commands/internal/printer/visitor/JavadocMarkdownVisitorTests.java rename to processor/common/src/test/java/net/strokkur/commands/internal/printer/javadoc/JavadocMarkdownVisitorTests.java index ba0d9d0a..79a77a23 100644 --- a/processor/common/src/test/java/net/strokkur/commands/internal/printer/visitor/JavadocMarkdownVisitorTests.java +++ b/processor/common/src/test/java/net/strokkur/commands/internal/printer/javadoc/JavadocMarkdownVisitorTests.java @@ -15,7 +15,7 @@ * 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.visitor; +package net.strokkur.commands.internal.printer.javadoc; import org.junit.jupiter.api.Test; diff --git a/processor/common/src/test/java/net/strokkur/commands/internal/printer/visitor/JavadocStarVisitorTests.java b/processor/common/src/test/java/net/strokkur/commands/internal/printer/javadoc/JavadocStarVisitorTests.java similarity index 98% rename from processor/common/src/test/java/net/strokkur/commands/internal/printer/visitor/JavadocStarVisitorTests.java rename to processor/common/src/test/java/net/strokkur/commands/internal/printer/javadoc/JavadocStarVisitorTests.java index ed99b230..b8ab76fd 100644 --- a/processor/common/src/test/java/net/strokkur/commands/internal/printer/visitor/JavadocStarVisitorTests.java +++ b/processor/common/src/test/java/net/strokkur/commands/internal/printer/javadoc/JavadocStarVisitorTests.java @@ -15,7 +15,7 @@ * 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.visitor; +package net.strokkur.commands.internal.printer.javadoc; import org.junit.jupiter.api.Test; From 8f3cff0d55871f450f48a9bda0501da3dc2b9913 Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Sat, 9 May 2026 13:16:04 +0200 Subject: [PATCH 03/16] Spotless apply and checkstyle --- .../internal/codegen/CodeAnnotation.java | 17 ++++++++++++++++ .../commands/internal/codegen/CodeBlock.java | 17 ++++++++++++++++ .../internal/codegen/CodeConstructor.java | 17 ++++++++++++++++ .../internal/codegen/CodeExpression.java | 17 ++++++++++++++++ .../commands/internal/codegen/CodeField.java | 17 ++++++++++++++++ .../internal/codegen/CodeStatement.java | 17 ++++++++++++++++ .../internal/codegen/InvokesMethod.java | 17 ++++++++++++++++ .../commands/internal/codegen/Modifiers.java | 17 ++++++++++++++++ .../internal/codegen/builder/Builders.java | 20 +++++++++++++++++++ .../codegen/builder/ClassBuilder.java | 17 ++++++++++++++++ .../codegen/builder/FieldBuilder.java | 17 ++++++++++++++++ .../codegen/builder/MethodBuilder.java | 17 ++++++++++++++++ .../codegen/visitor/CodeVisitable.java | 17 ++++++++++++++++ .../internal/codegen/visitor/CodeVisitor.java | 17 ++++++++++++++++ .../AbstractCommandTreePrintingVisitor.java | 17 ++++++++++++++++ .../command/ImportGatheringVisitor.java | 17 ++++++++++++++++ .../JavaCommandTreePrintingVisitor.java | 19 +++++++++++++++++- .../codegen/FullCommandClassBuilderTests.java | 17 ++++++++++++++++ 18 files changed, 310 insertions(+), 1 deletion(-) 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 index d30f0507..4ad33d39 100644 --- 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 @@ -1,3 +1,20 @@ +/* + * 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; 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 index 3e6d92b0..3e3128ce 100644 --- 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 @@ -1,3 +1,20 @@ +/* + * 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; diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeConstructor.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeConstructor.java index d7a4306b..a46675c2 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeConstructor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeConstructor.java @@ -1,3 +1,20 @@ +/* + * 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; 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 index 682fed51..aec37625 100644 --- 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 @@ -1,3 +1,20 @@ +/* + * 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; 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 index 77657339..919c87ae 100644 --- 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 @@ -1,3 +1,20 @@ +/* + * 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; 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 index 404aef62..3a9e26a0 100644 --- 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 @@ -1,3 +1,20 @@ +/* + * 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; 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 index ca2bd8e8..70aa933d 100644 --- 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 @@ -1,3 +1,20 @@ +/* + * 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.jspecify.annotations.Nullable; diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/Modifiers.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/Modifiers.java index 599e2796..6a45762f 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/Modifiers.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/Modifiers.java @@ -1,3 +1,20 @@ +/* + * 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.Locale; 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 index 06c97eb8..b8b582e0 100644 --- 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 @@ -1,3 +1,20 @@ +/* + * 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; @@ -31,4 +48,7 @@ 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))); } + + 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 index dec36cc7..0c690621 100644 --- 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 @@ -1,3 +1,20 @@ +/* + * 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; 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 index 3c356489..1cb104f9 100644 --- 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 @@ -1,3 +1,20 @@ +/* + * 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; 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 index 7a578e8a..63b06c42 100644 --- 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 @@ -1,3 +1,20 @@ +/* + * 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; 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 index 284e216c..243164f0 100644 --- 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 @@ -1,3 +1,20 @@ +/* + * 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 { 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 index 9657ebe6..009a1b76 100644 --- 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 @@ -1,3 +1,20 @@ +/* + * 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; diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/AbstractCommandTreePrintingVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/AbstractCommandTreePrintingVisitor.java index 16360108..467c240f 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/AbstractCommandTreePrintingVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/AbstractCommandTreePrintingVisitor.java @@ -1,3 +1,20 @@ +/* + * 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.command; import net.strokkur.commands.internal.codegen.CodeAnnotation; diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java index 6be05832..ee1c2e75 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java @@ -1,3 +1,20 @@ +/* + * 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.command; import net.strokkur.commands.internal.codegen.CodeAnnotation; diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaCommandTreePrintingVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaCommandTreePrintingVisitor.java index 4f0e9d2e..c41f025e 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaCommandTreePrintingVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaCommandTreePrintingVisitor.java @@ -1,3 +1,20 @@ +/* + * 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.command; import net.strokkur.commands.internal.codegen.CodeAnnotation; @@ -32,8 +49,8 @@ private void printMethods(StringBuilder builder, List methods) { }); } } - final ClassPrintUtil util = new ClassPrintUtil(); + final ClassPrintUtil util = new ClassPrintUtil(); return append(builder -> { printJavadocIndented(builder, codeClass.javadoc()); printAnnotationsIndented(builder, codeClass.annotations()); 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 index b5061fe6..8ae9754d 100644 --- 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 @@ -1,3 +1,20 @@ +/* + * 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; From ff6d607123efaba3d7885776037e9319f24098f8 Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Sat, 9 May 2026 19:22:54 +0200 Subject: [PATCH 04/16] Extend codegen tests --- processor/common/build.gradle.kts | 23 +- .../internal/codegen/CodeConstructor.java | 2 +- .../commands/internal/codegen/CodeMethod.java | 22 +- .../internal/codegen/CodePackage.java | 18 +- .../commands/internal/codegen/CodeType.java | 4 - .../codegen/builder/ClassBuilder.java | 14 ++ .../codegen/builder/FieldBuilder.java | 3 +- .../codegen/builder/MethodBuilder.java | 8 +- .../internal/codegen/javadoc/CodeJavadoc.java | 6 +- ...ava => AbstractSourcePrintingVisitor.java} | 19 +- .../command/ImportGatheringVisitor.java | 10 +- ...or.java => JavaSourcePrintingVisitor.java} | 12 +- .../CommonJavadocVisitorTests.java | 12 +- .../codegen/FullCommandClassBuilderTests.java | 8 +- .../internal/codegen/ImportGatherTests.java | 161 +++++++++++++ .../internal/codegen/JavaCodeGenTests.java | 224 ++++++++++++++++++ .../internal/codegen/JavaStatements.java | 14 ++ .../JavadocMarkdownVisitorTests.java | 62 ++++- .../JavadocStarVisitorTests.java | 42 +++- .../codegen/builder/MiscBuilderTests.java | 16 ++ 20 files changed, 606 insertions(+), 74 deletions(-) rename processor/common/src/main/java/net/strokkur/commands/internal/printer/command/{AbstractCommandTreePrintingVisitor.java => AbstractSourcePrintingVisitor.java} (89%) rename processor/common/src/main/java/net/strokkur/commands/internal/printer/command/{JavaCommandTreePrintingVisitor.java => JavaSourcePrintingVisitor.java} (94%) rename processor/common/src/test/java/net/strokkur/commands/internal/{printer/javadoc => codegen}/CommonJavadocVisitorTests.java (95%) create mode 100644 processor/common/src/test/java/net/strokkur/commands/internal/codegen/ImportGatherTests.java create mode 100644 processor/common/src/test/java/net/strokkur/commands/internal/codegen/JavaCodeGenTests.java create mode 100644 processor/common/src/test/java/net/strokkur/commands/internal/codegen/JavaStatements.java rename processor/common/src/test/java/net/strokkur/commands/internal/{printer/javadoc => codegen}/JavadocMarkdownVisitorTests.java (61%) rename processor/common/src/test/java/net/strokkur/commands/internal/{printer/javadoc => codegen}/JavadocStarVisitorTests.java (71%) create mode 100644 processor/common/src/test/java/net/strokkur/commands/internal/codegen/builder/MiscBuilderTests.java diff --git a/processor/common/build.gradle.kts b/processor/common/build.gradle.kts index 71e28f2a..e808c323 100644 --- a/processor/common/build.gradle.kts +++ b/processor/common/build.gradle.kts @@ -1,5 +1,6 @@ plugins { alias(libs.plugins.blossom) + id("jacoco") id("commands-publish") } @@ -11,10 +12,24 @@ dependencies { testRuntimeOnly(libs.junit.platform) } -tasks.test { - useJUnitPlatform() - testLogging { - events("passed", "skipped", "failed") +jacoco { + toolVersion = "0.8.14" +} + +tasks { + test { + useJUnitPlatform() + testLogging { + events("passed", "skipped", "failed") + } + } + + jacocoTestReport { + dependsOn(test) + reports { + xml.required = true + html.required = true + } } } diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeConstructor.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeConstructor.java index a46675c2..84511ae2 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeConstructor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeConstructor.java @@ -30,7 +30,7 @@ public CodeConstructor( Set modifiers, @Nullable CodeJavadoc javadoc, CodeBlock codeBlock, - Set throwsExceptions + List throwsExceptions ) { super(declaredClass, CodeType.ofClass(declaredClass), declaredClass.name(), parameters, modifiers, javadoc, codeBlock, throwsExceptions); } 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 index 704f30df..46a67d6c 100644 --- 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 @@ -34,23 +34,7 @@ public class CodeMethod implements CodeVisitable { private final Set modifiers; private final @Nullable CodeJavadoc javadoc; private final CodeBlock codeBlock; - private final Set throwsExceptions; - - public CodeMethod( - CodeClass declaredClass, - CodeType returnType, - String name, - List parameters - ) { - this.declaredClass = declaredClass; - this.returnType = returnType; - this.name = name; - this.parameters = parameters; - this.modifiers = Set.of(); - this.javadoc = null; - this.codeBlock = new CodeBlock(List.of()); - this.throwsExceptions = Set.of(); - } + private final List throwsExceptions; public CodeMethod( CodeClass declaredClass, @@ -60,7 +44,7 @@ public CodeMethod( Set modifiers, @Nullable CodeJavadoc javadoc, CodeBlock codeBlock, - Set throwsExceptions + List throwsExceptions ) { this.declaredClass = declaredClass; this.returnType = returnType; @@ -114,7 +98,7 @@ public CodeBlock codeBlock() { return codeBlock; } - public Set throwsExceptions() { + public List throwsExceptions() { return throwsExceptions; } } 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 index 6ac55e4f..6a88093d 100644 --- 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 @@ -20,10 +20,13 @@ import net.strokkur.commands.internal.codegen.visitor.CodeVisitable; import net.strokkur.commands.internal.codegen.visitor.CodeVisitor; +import java.util.Arrays; +import java.util.Objects; + public class CodePackage implements CodeVisitable { private final String[] paths; - static CodePackage of(String packageString) { + public static CodePackage of(String packageString) { return new CodePackage(packageString.split("\\.")); } @@ -39,4 +42,17 @@ public String path() { public R accept(CodeVisitor visitor) { return visitor.visitPackage(this); } + + @Override + public boolean equals(Object o) { + if (!(o instanceof final CodePackage that)) { + return false; + } + return Objects.deepEquals(paths, that.paths); + } + + @Override + public int hashCode() { + return Arrays.hashCode(paths); + } } 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 index 721c80a2..f0a7cf32 100644 --- 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 @@ -94,10 +94,6 @@ private ClassType(CodeClass codeClass) { this.codeClass = codeClass; } - public CodeClass codeClass() { - return codeClass; - } - @Override public String name() { return codeClass.name(); 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 index 0c690621..60aa0128 100644 --- 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 @@ -30,6 +30,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; public class ClassBuilder { @@ -106,4 +107,17 @@ public CodeClass build() { typeParameters ); } + + @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/FieldBuilder.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/FieldBuilder.java index 1cb104f9..0669638c 100644 --- 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 @@ -25,7 +25,6 @@ import org.jspecify.annotations.Nullable; import java.util.ArrayList; -import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -72,7 +71,7 @@ public CodeField build() { Objects.requireNonNull(name); Objects.requireNonNull(type); return new CodeField( - name, type, initialiser, EnumSet.copyOf(modifiers), List.copyOf(annotations) + name, type, initialiser, Set.copyOf(modifiers), List.copyOf(annotations) ); } } 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 index 63b06c42..c8c812ae 100644 --- 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 @@ -43,7 +43,7 @@ public class MethodBuilder { private Set modifiers = new HashSet<>(); private @Nullable CodeJavadoc javadoc = null; private CodeBlock codeBlock = new CodeBlock(List.of()); - private Set throwsExceptions = Set.of(); + private List throwsExceptions = List.of(); MethodBuilder() { // package-private ctor @@ -89,7 +89,7 @@ public MethodBuilder setCodeBlock(List statements) { return this; } - public MethodBuilder setThrowsExceptions(Set throwsExceptions) { + public MethodBuilder setThrowsExceptions(List throwsExceptions) { this.throwsExceptions = throwsExceptions; return this; } @@ -105,7 +105,7 @@ public CodeMethod build() { Set.copyOf(modifiers), javadoc, codeBlock, - Set.copyOf(throwsExceptions) + List.copyOf(throwsExceptions) ); } @@ -117,7 +117,7 @@ public CodeConstructor buildConstructor() { Set.copyOf(modifiers), javadoc, codeBlock, - Set.copyOf(throwsExceptions) + List.copyOf(throwsExceptions) ); } } 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 index f21b697c..fe61a37c 100644 --- 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 @@ -53,11 +53,11 @@ static CodeJavadoc version(String version) { return new Meta("version", version); } - static CodeJavadoc see(CodeMethod method, String description) { + static CodeJavadoc see(CodeMethod method, @Nullable String description) { return see(method, description, false); } - static CodeJavadoc see(CodeMethod method, String description, boolean localMethod) { + static CodeJavadoc see(CodeMethod method, @Nullable String description, boolean localMethod) { return new MethodReferenceMeta("see", method, description, localMethod); } @@ -70,7 +70,7 @@ static CodeJavadoc newline() { } /// Intended to be used inside [#combineLines(CodeJavadoc...)] for a true blank line. - static CodeJavadoc emptyLine() { + static CodeJavadoc blank() { return visitor -> { // noop }; diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/AbstractCommandTreePrintingVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/AbstractSourcePrintingVisitor.java similarity index 89% rename from processor/common/src/main/java/net/strokkur/commands/internal/printer/command/AbstractCommandTreePrintingVisitor.java rename to processor/common/src/main/java/net/strokkur/commands/internal/printer/command/AbstractSourcePrintingVisitor.java index 467c240f..a0c97ec6 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/AbstractCommandTreePrintingVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/AbstractSourcePrintingVisitor.java @@ -18,6 +18,7 @@ package net.strokkur.commands.internal.printer.command; import net.strokkur.commands.internal.codegen.CodeAnnotation; +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; @@ -34,28 +35,26 @@ import java.util.function.Supplier; import java.util.stream.Collectors; -public abstract class AbstractCommandTreePrintingVisitor implements CodeVisitor { +public abstract class AbstractSourcePrintingVisitor implements CodeVisitor { protected final Supplier javadocPrintingVisitor; private final String indentString; private int indentation = 0; - public AbstractCommandTreePrintingVisitor(Supplier javadocPrintingVisitor, String indentString) { + public AbstractSourcePrintingVisitor(Supplier javadocPrintingVisitor, String indentString) { this.javadocPrintingVisitor = javadocPrintingVisitor; this.indentString = indentString; } - protected final void appendIndented(Runnable run) { - incrementIndent(); - run.run(); - decrementIndent(); + /// Packages are never printed. + @Override + public final StringBuilder visitPackage(CodePackage codePackage) { + throw new IllegalStateException("This should not be called."); } - protected final StringBuilder appendIndented(Consumer run) { - final StringBuilder builder = new StringBuilder(); + protected final void appendIndented(Runnable run) { incrementIndent(); - run.accept(builder); + run.run(); decrementIndent(); - return builder; } protected final StringBuilder append(Consumer run) { diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java index ee1c2e75..7bcc137b 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java @@ -26,6 +26,7 @@ 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.visitor.CodeVisitable; import net.strokkur.commands.internal.codegen.visitor.CodeVisitor; @@ -70,7 +71,7 @@ public Set visitMethod(CodeMethod codeMethod) { @Override public Set visitPackage(CodePackage codePackage) { - return Set.of(); + throw new IllegalStateException("This should not be called."); } @Override @@ -103,7 +104,7 @@ public Set visitField(CodeField codeField) { public Set visitExpression(CodeExpression codeExpression) { return switch (codeExpression) { case CodeExpression.MethodInvocation methodInvocation -> { - if (methodInvocation.instanceVariable() == null) { + if (methodInvocation.method().modifiers().contains(Modifiers.STATIC)) { yield join( methodInvocation.method().declaredClass().accept(this), collect(methodInvocation.parameters()) @@ -116,6 +117,7 @@ yield join( ctorInvocation.type().accept(this), collect(ctorInvocation.parameters()) ); + default -> Set.of(); }; } @@ -140,7 +142,7 @@ yield join( case CodeStatement.ThrowStatement throwStatement -> throwStatement.throwExpression().accept(this); case CodeStatement.MethodInvocation methodInvocation -> { - if (methodInvocation.instanceVariable() == null) { + if (methodInvocation.method().modifiers().contains(Modifiers.STATIC)) { yield join( methodInvocation.method().declaredClass().accept(this), collect(methodInvocation.parameters()) @@ -148,8 +150,6 @@ yield join( } yield collect(methodInvocation.parameters()); } - - default -> Set.of(); }; } } diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaCommandTreePrintingVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java similarity index 94% rename from processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaCommandTreePrintingVisitor.java rename to processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java index c41f025e..135a8f00 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaCommandTreePrintingVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java @@ -23,7 +23,6 @@ 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; @@ -34,8 +33,8 @@ import java.util.function.Predicate; import java.util.function.Supplier; -public class JavaCommandTreePrintingVisitor extends AbstractCommandTreePrintingVisitor { - public JavaCommandTreePrintingVisitor(Supplier javadocPrintingVisitor, String indentString) { +public class JavaSourcePrintingVisitor extends AbstractSourcePrintingVisitor { + public JavaSourcePrintingVisitor(Supplier javadocPrintingVisitor, String indentString) { super(javadocPrintingVisitor, indentString); } @@ -113,11 +112,6 @@ public StringBuilder visitMethod(CodeMethod codeMethod) { }); } - @Override - public StringBuilder visitPackage(CodePackage codePackage) { - return new StringBuilder(); - } - @Override public StringBuilder visitParameter(CodeParameter codeParameter) { return append(builder -> { @@ -178,6 +172,7 @@ public StringBuilder visitExpression(CodeExpression codeExpression) { case CodeExpression.Variable variable -> { builder.append(variable.name()); } + default -> throw new IllegalStateException("Invalid expression: " + codeExpression.getClass().getName()); } }); } @@ -210,6 +205,7 @@ public StringBuilder visitStatement(CodeStatement codeStatement) { appendNested(builder, variableDeclaration.assignment()); } } + default -> throw new IllegalStateException("Invalid statement: " + codeStatement.getClass().getName()); } builder.append(";\n"); }); diff --git a/processor/common/src/test/java/net/strokkur/commands/internal/printer/javadoc/CommonJavadocVisitorTests.java b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/CommonJavadocVisitorTests.java similarity index 95% rename from processor/common/src/test/java/net/strokkur/commands/internal/printer/javadoc/CommonJavadocVisitorTests.java rename to processor/common/src/test/java/net/strokkur/commands/internal/codegen/CommonJavadocVisitorTests.java index eae6e5a7..e3bc2b5a 100644 --- a/processor/common/src/test/java/net/strokkur/commands/internal/printer/javadoc/CommonJavadocVisitorTests.java +++ b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/CommonJavadocVisitorTests.java @@ -15,22 +15,20 @@ * 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; +package net.strokkur.commands.internal.codegen; -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.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.emptyLine; 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; @@ -54,7 +52,7 @@ 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")), - emptyLine(), + blank(), author("Strokkur24 - StrokkCommands"), version("2.0.0"), see(createMethod(), "creating the LiteralCommandNode", true), @@ -97,7 +95,7 @@ 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."), - emptyLine(), + blank(), throwsMeta(CodeType.ofClass("java.lang.IllegalAccessException"), "always") ); } 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 index 8ae9754d..007bd960 100644 --- 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 @@ -20,7 +20,7 @@ import net.strokkur.commands.internal.codegen.builder.Builders; import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc; import net.strokkur.commands.internal.printer.command.ImportGatheringVisitor; -import net.strokkur.commands.internal.printer.command.JavaCommandTreePrintingVisitor; +import net.strokkur.commands.internal.printer.command.JavaSourcePrintingVisitor; import net.strokkur.commands.internal.printer.javadoc.JavaMarkdownJavadocVisitor; import net.strokkur.commands.internal.util.Classes; import org.junit.jupiter.api.Assertions; @@ -56,7 +56,7 @@ void testFullClassExampleImports() { @Test void testFullClassExampleJavaPrinter() { final CodeClass exampleClass = constructExampleCommandClass(); - final JavaCommandTreePrintingVisitor visitor = new JavaCommandTreePrintingVisitor(JavaMarkdownJavadocVisitor::new, " "); + final JavaSourcePrintingVisitor visitor = new JavaSourcePrintingVisitor(JavaMarkdownJavadocVisitor::new, " "); // language=java final String expected = """ @@ -136,11 +136,11 @@ private CodeClass constructExampleCommandClass() { ) .addMethod(Builders.method() .setDeclaringClass(current) - .setThrowsExceptions(Set.of(CodeType.ofClass("java.lang.IllegalAccessException"))) + .setThrowsExceptions(List.of(CodeType.ofClass("java.lang.IllegalAccessException"))) .setModifiers(Set.of(Modifiers.PRIVATE)) .setJavadoc(CodeJavadoc.combineLines( CodeJavadoc.text("The constructor is inaccessible."), - CodeJavadoc.emptyLine(), + CodeJavadoc.blank(), CodeJavadoc.throwsMeta(CodeType.ofClass("java.lang.IllegalAccessException"), "always") )) .setCodeBlock(List.of( 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..de2839c4 --- /dev/null +++ b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/ImportGatherTests.java @@ -0,0 +1,161 @@ +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.command.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 imports = visitable.accept(new ImportGatheringVisitor()); + + 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""", CodeExpression.methodCall( + Builders.method(CodeClass.simple("com.example.Test"), "execute") + .setModifiers(Set.of(Modifiers.STATIC)) + .build(), + List.of() + )); + + // Test static method invocation with parameters + check(""" + com.example.Test + io.library.Value + """, CodeExpression.methodCall( + Builders.method(CodeClass.simple("com.example.Test"), "execute") + .setModifiers(Set.of(Modifiers.STATIC)) + .build(), + List.of(CodeExpression.constructorCall(CodeType.ofClass("io.library.Value"), List.of())) + )); + + // Test instance method invocation + check("", CodeExpression.methodCall( + Builders.method(CodeClass.simple("com.example.Test"), "execute") + .build(), + List.of(), + "this" + )); + + // Test instance method invocation with parameters + check(""" + io.library.Value + """, CodeExpression.methodCall( + Builders.method(CodeClass.simple("com.example.Test"), "execute").build(), + List.of(CodeExpression.constructorCall(CodeType.ofClass("io.library.Value"), List.of())), + "this" + )); + } + + @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(CodeExpression.methodCall( + Builders.method(CodeClass.simple("com.example.TestClass"), "get") + .setModifiers(Set.of(Modifiers.STATIC)) + .build(), + List.of() + )) + .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", + CodeExpression.methodCall( + Builders.method(CodeClass.simple("com.example.TheClass"), "get") + .setModifiers(Set.of(Modifiers.STATIC)) + .build(), + List.of() + ) + )); + + // 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( + CodeExpression.constructorCall( + CodeType.ofClass("java.lang.NullPointerException"), + List.of(CodeExpression.string("It was null :(")) + ) + )); + + // Method invocation (instance) + check("", CodeStatement.methodInvocation( + Builders.method(CodeClass.simple("io.declared.ThisClass"), "doSomething").build(), + List.of( + CodeExpression.string("test") + ) + )); + // Method invocation (static) + check("io.declared.ThisClass", CodeStatement.methodInvocation( + Builders.method(CodeClass.simple("io.declared.ThisClass"), "doSomething") + .setModifiers(Set.of(Modifiers.STATIC)) + .build(), + List.of( + CodeExpression.string("test") + ) + )); + } +} 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..8e3f6d1b --- /dev/null +++ b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/JavaCodeGenTests.java @@ -0,0 +1,224 @@ +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.command.JavaSourcePrintingVisitor; +import net.strokkur.commands.internal.printer.javadoc.JavaMarkdownJavadocVisitor; +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; +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(@Language("JAVA") 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() { + final @Language("JAVA") String expected = """ + class ExampleClass { + } + """; + check(expected, EXAMPLE_CLASS); + + final @Language("JAVA") 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() + ); + + final @Language("JAVA") 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() + ); + + final @Language("JAVA") 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() + .setDeclaringClass(EXAMPLE_CLASS) + .buildConstructor() + ) + .build() + ); + } + + @Test + void testMethod() { + final @Language("JAVA") String expected = """ + void method() { + } + """; + check(expected, Builders.method(EXAMPLE_CLASS, "method").build()); + + final @Language("JAVA") String expectedWithModifiers = """ + public static void method() { + } + """; + check(expectedWithModifiers, Builders.method(EXAMPLE_CLASS, "method") + .setModifiers(Set.of(Modifiers.PUBLIC, Modifiers.STATIC)) + .build()); + + final @Language("JAVA") String expectedWithStatements = """ + void method() { + otherMethod(STATIC_VALUE, "Now"); + } + """; + check(expectedWithStatements, Builders.method(EXAMPLE_CLASS, "method") + .setCodeBlock(List.of( + CodeStatement.methodInvocation(Builders.method(EXAMPLE_CLASS, "otherMethod").build(), + List.of( + CodeExpression.variable("STATIC_VALUE"), + CodeExpression.string("Now") + ) + ) + )) + .build()); + + final @Language("JAVA") String expectedWithThrows = """ + void method() throws NullPointerException, SQLException { + throw new SQLException("No database present :("); + } + """; + check(expectedWithThrows, Builders.method(EXAMPLE_CLASS, "method") + .setThrowsExceptions(List.of( + CodeType.ofClass("java.lang.NullPointerException"), + CodeType.ofClass("java.sql.SQLException") + )) + .setCodeBlock(List.of( + CodeStatement.throwStatement(CodeExpression.constructorCall( + CodeType.ofClass("java.sql.SQLException"), List.of( + CodeExpression.string("No database present :(") + ) + )) + )) + .build()); + } + + @Test + void testField() { + final @Language("JAVA") String expectedNoInit = """ + String someField; + """; + check(expectedNoInit, Builders.field("someField", CodeType.ofClass(CodeClass.STRING)).build()); + + final @Language("JAVA") String expectedWithInit = """ + String someField = "some value"; + """; + check(expectedWithInit, Builders.field("someField", CodeType.ofClass(CodeClass.STRING)) + .setInitialiser(CodeExpression.string("some value")) + .build()); + + final @Language("JAVA") 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 + )); + } +} 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..8628f907 --- /dev/null +++ b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/JavaStatements.java @@ -0,0 +1,14 @@ +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/printer/javadoc/JavadocMarkdownVisitorTests.java b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/JavadocMarkdownVisitorTests.java similarity index 61% rename from processor/common/src/test/java/net/strokkur/commands/internal/printer/javadoc/JavadocMarkdownVisitorTests.java rename to processor/common/src/test/java/net/strokkur/commands/internal/codegen/JavadocMarkdownVisitorTests.java index 79a77a23..a62ce59e 100644 --- a/processor/common/src/test/java/net/strokkur/commands/internal/printer/javadoc/JavadocMarkdownVisitorTests.java +++ b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/JavadocMarkdownVisitorTests.java @@ -15,10 +15,22 @@ * 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; +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() { @@ -82,4 +94,52 @@ void testJavaMarkdownJavadocsCtor() { /// @throws java.lang.IllegalAccessException always"""; checkOutput(expected, ctorJd(), JavaMarkdownJavadocVisitor::new); } + + @Test + void testNamedClassReference() { + @Language("JAVA") final String expected = """ + /// This [environment][java.lang.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 java.lang.String#concat(java.lang.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/printer/javadoc/JavadocStarVisitorTests.java b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/JavadocStarVisitorTests.java similarity index 71% rename from processor/common/src/test/java/net/strokkur/commands/internal/printer/javadoc/JavadocStarVisitorTests.java rename to processor/common/src/test/java/net/strokkur/commands/internal/codegen/JavadocStarVisitorTests.java index b8ab76fd..fb6dbd7c 100644 --- a/processor/common/src/test/java/net/strokkur/commands/internal/printer/javadoc/JavadocStarVisitorTests.java +++ b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/JavadocStarVisitorTests.java @@ -15,10 +15,18 @@ * 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; +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() { @@ -90,4 +98,36 @@ void testJavaStarJavadocsCtor() { */"""; checkOutput(expected, ctorJd(), JavaStarJavadocVisitor::new); } + + @Test + void testNamedClassReference() { + // language=java + final String expected = """ + /** + * This {@link java.lang.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..e71dc31c --- /dev/null +++ b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/builder/MiscBuilderTests.java @@ -0,0 +1,16 @@ +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")) + ); + } +} From 750b2b4a564bff19aa5d2789fc5bdc65febd8129 Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Sat, 9 May 2026 19:30:00 +0200 Subject: [PATCH 05/16] Spotless apply --- .../internal/codegen/ImportGatherTests.java | 17 +++++++++++++++++ .../internal/codegen/JavaCodeGenTests.java | 17 +++++++++++++++++ .../internal/codegen/JavaStatements.java | 17 +++++++++++++++++ .../codegen/builder/MiscBuilderTests.java | 17 +++++++++++++++++ 4 files changed, 68 insertions(+) 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 index de2839c4..ca3fe44c 100644 --- 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 @@ -1,3 +1,20 @@ +/* + * 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; 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 index 8e3f6d1b..831ef67f 100644 --- 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 @@ -1,3 +1,20 @@ +/* + * 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; 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 index 8628f907..02a6d172 100644 --- 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 @@ -1,3 +1,20 @@ +/* + * 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; 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 index e71dc31c..1503f19f 100644 --- 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 @@ -1,3 +1,20 @@ +/* + * 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; From 22f4aa770a4377e90b05a59ab8b67c19e643d684 Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Sun, 10 May 2026 14:34:11 +0200 Subject: [PATCH 06/16] Add method chaining and method references --- .../commands/internal/codegen/CodeClass.java | 3 +- .../internal/codegen/CodeExpression.java | 51 ++++--- .../commands/internal/codegen/CodeField.java | 3 +- .../commands/internal/codegen/CodeMethod.java | 3 +- .../internal/codegen/CodeStatement.java | 43 ++++-- .../commands/internal/codegen/CodeType.java | 29 ++++ .../internal/codegen/InvokesMethod.java | 35 ++--- .../internal/codegen/as/AsExpression.java | 24 ++++ .../internal/codegen/as/AsStatement.java | 24 ++++ .../internal/codegen/builder/Builders.java | 31 ++++- .../codegen/builder/ClassBuilder.java | 31 ++--- .../codegen/builder/FieldBuilder.java | 13 +- .../codegen/builder/MethodBuilder.java | 32 +++-- .../builder/MethodInvocationBuilder.java | 100 ++++++++++++++ .../AbstractSourcePrintingVisitor.java | 35 ++++- .../command/ImportGatheringVisitor.java | 40 +++--- .../command/JavaSourcePrintingVisitor.java | 20 ++- .../commands/internal/util/ConvertableTo.java | 29 ++++ .../codegen/CommonJavadocVisitorTests.java | 9 +- .../codegen/FullCommandClassBuilderTests.java | 22 ++- .../internal/codegen/ImportGatherTests.java | 94 ++++++------- .../internal/codegen/JavaCodeGenTests.java | 130 ++++++++++++++---- 22 files changed, 595 insertions(+), 206 deletions(-) create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/as/AsExpression.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/as/AsStatement.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/MethodInvocationBuilder.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/util/ConvertableTo.java 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 index 1e41f734..a58e5496 100644 --- 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 @@ -20,6 +20,7 @@ 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; @@ -32,7 +33,7 @@ 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 { + List typeParameters) implements CodeVisitable, ConvertableTo.Self { public static CodeClass STRING = CodeClass.simple("java.lang.String"); private CodeClass( 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 index aec37625..dce804dc 100644 --- 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 @@ -17,13 +17,13 @@ */ package net.strokkur.commands.internal.codegen; +import net.strokkur.commands.internal.codegen.as.AsExpression; import net.strokkur.commands.internal.codegen.visitor.CodeVisitable; import net.strokkur.commands.internal.codegen.visitor.CodeVisitor; -import org.jspecify.annotations.Nullable; import java.util.List; -public sealed interface CodeExpression extends CodeVisitable { +public sealed interface CodeExpression extends CodeVisitable, AsExpression { static Null nullExpr() { return Null.INSTANCE; } @@ -32,27 +32,31 @@ static StringLiteral string(String value) { return new StringLiteral(value); } - static MethodInvocation methodCall(CodeMethod method, List parameters) { - return new MethodInvocation(method, parameters, null); - } - - static MethodInvocation methodCall(CodeMethod method, List parameters, String instanceVariable) { - return new MethodInvocation(method, parameters, instanceVariable); - } - - static ConstructorInvocation constructorCall(CodeType.ClassType type, List parameters) { - return new ConstructorInvocation(type, parameters); + static ConstructorInvocation constructorCall(CodeType.ClassType type, List parameters) { + return new ConstructorInvocation(type, parameters.stream() + .map(AsExpression::getAsExpression) + .toList() + ); } static Variable variable(String name) { return new Variable(name); } + static MethodReference methodReference(CodeType type, String methodName) { + return new MethodReference(type, methodName); + } + @Override default R accept(CodeVisitor visitor) { return visitor.visitExpression(this); } + @Override + default CodeExpression getAsExpression() { + return this; + } + final class StringLiteral implements CodeExpression { private final String value; @@ -84,10 +88,7 @@ public String name() { } } - final class MethodInvocation extends InvokesMethod implements CodeExpression { - public MethodInvocation(CodeMethod method, List parameters, @Nullable String instanceVariable) { - super(method, parameters, instanceVariable); - } + record MethodInvocation(InvokesMethod invokes) implements CodeExpression { } final class ConstructorInvocation implements CodeExpression { @@ -107,4 +108,22 @@ public List parameters() { return parameters; } } + + 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; + } + } } 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 index 919c87ae..a05a2ba7 100644 --- 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 @@ -19,6 +19,7 @@ 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; @@ -30,7 +31,7 @@ public record CodeField( @Nullable CodeExpression initialiser, Set modifiers, List annotations -) implements CodeVisitable { +) implements CodeVisitable, ConvertableTo.Self { @Override public R accept(CodeVisitor visitor) { 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 index 46a67d6c..8eb5c488 100644 --- 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 @@ -20,13 +20,14 @@ 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; import java.util.stream.Collectors; -public class CodeMethod implements CodeVisitable { +public class CodeMethod implements CodeVisitable, ConvertableTo.Self { private final CodeClass declaredClass; private final CodeType returnType; private final String name; 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 index 3a9e26a0..0bb97452 100644 --- 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 @@ -17,32 +17,37 @@ */ package net.strokkur.commands.internal.codegen; +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.List; +public sealed interface CodeStatement extends CodeVisitable, AsStatement { + static VariableDeclaration variableDeclaration(CodeType type, String name, @Nullable AsExpression assignment) { + return new VariableDeclaration(type, name, assignment == null ? null : assignment.getAsExpression(), false); + } -public sealed interface CodeStatement extends CodeVisitable { - static VariableDeclaration variableDeclaration(CodeType type, String name, @Nullable CodeExpression assignment) { - return new VariableDeclaration(type, name, assignment); + static VariableDeclaration variableDeclarationFinal(CodeType type, String name, @Nullable AsExpression assignment) { + return new VariableDeclaration(type, name, assignment == null ? null : assignment.getAsExpression(), true); } - static ReturnStatement returnStatement(@Nullable CodeExpression returnExpression) { - return new ReturnStatement(returnExpression); + static ReturnStatement returnStatement(@Nullable AsExpression returnExpression) { + return new ReturnStatement(returnExpression == null ? null : returnExpression.getAsExpression()); } static ThrowStatement throwStatement(CodeExpression throwExpression) { return new ThrowStatement(throwExpression); } - static MethodInvocation methodInvocation(CodeMethod method, List parameters) { - return new MethodInvocation(method, parameters, null); + static Blank blank() { + return Blank.INSTANCE; } - static MethodInvocation methodInvocation(CodeMethod method, List parameters, @Nullable String instanceVariable) { - return new MethodInvocation(method, parameters, instanceVariable); + @Override + default CodeStatement getAsStatement() { + return this; } @Override @@ -54,11 +59,13 @@ 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) { + 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() { @@ -73,6 +80,10 @@ public String name() { public @Nullable CodeExpression assignment() { return assignment; } + + public boolean isFinal() { + return isFinal; + } } final class ReturnStatement implements CodeStatement { @@ -100,9 +111,13 @@ public CodeExpression throwExpression() { } } - final class MethodInvocation extends InvokesMethod implements CodeStatement { - public MethodInvocation(CodeMethod method, List parameters, @Nullable String instanceVariable) { - super(method, parameters, instanceVariable); + record MethodInvocation(InvokesMethod invokes) implements CodeStatement { + } + + 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 index f0a7cf32..27e55632 100644 --- 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 @@ -30,6 +30,9 @@ public interface CodeType extends CodeVisitable { PrimitiveType FLOAT = new PrimitiveType("float"); PrimitiveType DOUBLE = new PrimitiveType("double"); + ClassType STRING = CodeType.ofClass(CodeClass.STRING); + ArrayType STRING_ARRAY = CodeType.ofArray(CodeType.STRING); + static GenericType generic(String name) { return new GenericType(name); } @@ -42,6 +45,10 @@ static ClassType ofClass(String fqn) { return new ClassType(CodeClass.simple(fqn)); } + static ArrayType ofArray(CodeType inner) { + return new ArrayType(inner); + } + String name(); String fullyQualifiedName(); @@ -104,4 +111,26 @@ public String fullyQualifiedName() { return 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 index 70aa933d..ae9863a1 100644 --- 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 @@ -17,30 +17,25 @@ */ package net.strokkur.commands.internal.codegen; +import net.strokkur.commands.internal.util.ConvertableTo; import org.jspecify.annotations.Nullable; import java.util.List; -public abstract class InvokesMethod { - private final CodeMethod method; - private final List parameters; - private final @Nullable String instanceVariable; +public record InvokesMethod( + String methodName, + CodeType.@Nullable ClassType type, + List parameters, + @Nullable String instanceVariable, + boolean newline, + boolean isStatic, + List chained +) implements ConvertableTo.Self { - public InvokesMethod(CodeMethod method, List parameters, @Nullable String instanceVariable) { - this.method = method; - this.parameters = parameters; - this.instanceVariable = instanceVariable; - } - - public CodeMethod method() { - return method; - } - - public List parameters() { - return parameters; - } - - public @Nullable String instanceVariable() { - return instanceVariable; + public record Chained( + String methodName, + List parameters, + boolean newline + ) { } } 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/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 index b8b582e0..9bb94f7d 100644 --- 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 @@ -18,20 +18,27 @@ 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.util.ConvertableTo; import java.util.Arrays; public class Builders { - public static MethodBuilder method() { - return new MethodBuilder(); + 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() - .setDeclaringClass(declaringClass) - .setName(name); + return new MethodBuilder(name) + .setDeclaringClass(declaringClass); } public static FieldBuilder field(String name, CodeType type) { @@ -49,6 +56,20 @@ public static ClassBuilder classBuilder(String fqn) { 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; + } + 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 index 60aa0128..bd5d3c9b 100644 --- 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 @@ -25,15 +25,17 @@ 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 { +public class ClassBuilder implements ConvertableTo { private final CodePackage codePackage; private @Nullable CodeClass parentClass = null; private final String name; @@ -49,8 +51,8 @@ public class ClassBuilder { this.name = name; } - public ClassBuilder setParentClass(@Nullable CodeClass parentClass) { - this.parentClass = parentClass; + public ClassBuilder setParentClass(@Nullable ConvertableTo parentClass) { + this.parentClass = Optional.ofNullable(parentClass).map(ConvertableTo::convert).orElse(null); return this; } @@ -74,23 +76,13 @@ public ClassBuilder setTypeParameters(List typeParameters) { return this; } - public ClassBuilder addMethod(CodeMethod method) { - this.methods.add(method); + public ClassBuilder addMethod(ConvertableTo method) { + this.methods.add(method.convert()); return this; } - public ClassBuilder addMethod(MethodBuilder methodBuilder) { - this.methods.add(methodBuilder.build()); - return this; - } - - public ClassBuilder addField(CodeField field) { - this.fields.add(field); - return this; - } - - public ClassBuilder addField(FieldBuilder fieldBuilder) { - this.fields.add(fieldBuilder.build()); + public ClassBuilder addField(ConvertableTo field) { + this.fields.add(field.convert()); return this; } @@ -108,6 +100,11 @@ public CodeClass build() { ); } + @Override + public CodeClass convert() { + return build(); + } + @Override public boolean equals(Object o) { if (!(o instanceof final ClassBuilder that)) { 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 index 0669638c..9dd5310f 100644 --- 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 @@ -22,6 +22,8 @@ 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; @@ -30,7 +32,7 @@ import java.util.Objects; import java.util.Set; -public class FieldBuilder { +public class FieldBuilder implements ConvertableTo { private @Nullable String name = null; private @Nullable CodeType type = null; private @Nullable CodeExpression initialiser; @@ -51,8 +53,8 @@ public FieldBuilder setType(CodeType type) { return this; } - public FieldBuilder setInitialiser(@Nullable CodeExpression initialiser) { - this.initialiser = initialiser; + public FieldBuilder setInitialiser(AsExpression initialiser) { + this.initialiser = initialiser.getAsExpression(); return this; } @@ -74,4 +76,9 @@ public CodeField build() { 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 index c8c812ae..6829c34d 100644 --- 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 @@ -22,21 +22,23 @@ 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.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.HashSet; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.Set; -public class MethodBuilder { +public class MethodBuilder implements ConvertableTo { private @Nullable CodeClass declaredClass = null; - private @Nullable String name = null; + private final String name; private CodeType returnType = CodeType.VOID; private List parameters = new ArrayList<>(); @@ -45,8 +47,8 @@ public class MethodBuilder { private CodeBlock codeBlock = new CodeBlock(List.of()); private List throwsExceptions = List.of(); - MethodBuilder() { - // package-private ctor + MethodBuilder(String name) { + this.name = name; } public MethodBuilder setDeclaringClass(CodeClass declaringClass) { @@ -54,11 +56,6 @@ public MethodBuilder setDeclaringClass(CodeClass declaringClass) { return this; } - public MethodBuilder setName(String name) { - this.name = name; - return this; - } - public MethodBuilder addParameter(CodeType type, String name) { this.parameters.add(CodeParameter.of(type, name)); return this; @@ -84,8 +81,11 @@ public MethodBuilder setJavadoc(@Nullable CodeJavadoc javadoc) { return this; } - public MethodBuilder setCodeBlock(List statements) { - this.codeBlock = new CodeBlock(statements); + public MethodBuilder setCodeBlock(List statements) { + this.codeBlock = new CodeBlock(statements.stream() + .map(AsStatement::getAsStatement) + .toList() + ); return this; } @@ -95,10 +95,9 @@ public MethodBuilder setThrowsExceptions(List throwsExceptio } public CodeMethod build() { - Objects.requireNonNull(this.declaredClass); Objects.requireNonNull(this.name); return new CodeMethod( - declaredClass, + Optional.ofNullable(declaredClass).orElse(CodeClass.simple("no.class.provided.in.MethodBuilder")), returnType, name, List.copyOf(parameters), @@ -120,4 +119,9 @@ public CodeConstructor buildConstructor() { 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..f6b21b22 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/MethodInvocationBuilder.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.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.AsExpression; +import net.strokkur.commands.internal.codegen.as.AsStatement; +import net.strokkur.commands.internal.util.ConvertableTo; +import org.jspecify.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class MethodInvocationBuilder implements ConvertableTo, AsExpression, AsStatement { + private final String methodName; + private CodeType.@Nullable ClassType type = null; + private final List parameters = new ArrayList<>(); + private @Nullable String instanceVariable = null; + private boolean newline = false; + private boolean isStatic = 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 setInstanceVariable(@Nullable String instanceVariable) { + this.instanceVariable = instanceVariable; + return this; + } + + public MethodInvocationBuilder setNewline() { + this.newline = true; + return this; + } + + public MethodInvocationBuilder setStatic() { + this.isStatic = true; + return this; + } + + public MethodInvocationBuilder chain(String methodName, boolean newline, AsExpression... parameters) { + this.chained.add(new InvokesMethod.Chained( + methodName, + Arrays.stream(parameters) + .map(AsExpression::getAsExpression) + .toList(), + newline + )); + return this; + } + + public InvokesMethod build() { + return new InvokesMethod(methodName, type, List.copyOf(parameters), instanceVariable, newline, isStatic, List.copyOf(chained)); + } + + @Override + public InvokesMethod convert() { + return build(); + } + + @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/printer/command/AbstractSourcePrintingVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/AbstractSourcePrintingVisitor.java index a0c97ec6..f64ad73a 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/AbstractSourcePrintingVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/AbstractSourcePrintingVisitor.java @@ -117,12 +117,43 @@ protected void printModifiersIndented(StringBuilder builder, Set modi } protected void appendMethodInvocation(StringBuilder builder, InvokesMethod method) { + final String source; if (method.instanceVariable() != null) { - builder.append(method.instanceVariable()).append("."); + source = method.instanceVariable(); + } else if (method.isStatic() && method.type() != null) { + source = method.type().name(); + } else { + source = null; } - builder.append(method.method().name()); + + if (source != null) { + builder.append(source); + incrementIndent(); + if (method.newline()) { + builder.append("\n"); + appendIndent(builder); + } + builder.append("."); + decrementIndent(); + } + + builder.append(method.methodName()); builder.append("("); builder.append(joining(method.parameters())); builder.append(")"); + + incrementIndent(); + for (InvokesMethod.Chained chained : method.chained()) { + if (chained.newline()) { + builder.append("\n"); + appendIndent(builder); + } + builder.append("."); + builder.append(chained.methodName()); + builder.append("("); + builder.append(joining(chained.parameters())); + builder.append(")"); + } + decrementIndent(); } } diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java index 7bcc137b..e21a51fc 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java @@ -26,7 +26,7 @@ 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.InvokesMethod; import net.strokkur.commands.internal.codegen.visitor.CodeVisitable; import net.strokkur.commands.internal.codegen.visitor.CodeVisitor; @@ -37,6 +37,16 @@ public class ImportGatheringVisitor implements CodeVisitor> { + private Set collectMethodInvokesImports(InvokesMethod invokes) { + if (invokes.isStatic() && invokes.type() != null) { + return join( + invokes.type().accept(this), + collect(invokes.parameters()) + ); + } + return collect(invokes.parameters()); + } + @SafeVarargs private Set join(Set... all) { return Stream.of(all) @@ -81,6 +91,10 @@ public Set visitParameter(CodeParameter codeParameter) { @Override public Set visitType(CodeType codeType) { + if (codeType instanceof CodeType.ArrayType array) { + return array.inner().accept(this); + } + return codeType instanceof CodeType.ClassType codeClass ? Set.of(codeClass.fullyQualifiedName()) : Set.of(); @@ -103,21 +117,15 @@ public Set visitField(CodeField codeField) { @Override public Set visitExpression(CodeExpression codeExpression) { return switch (codeExpression) { - case CodeExpression.MethodInvocation methodInvocation -> { - if (methodInvocation.method().modifiers().contains(Modifiers.STATIC)) { - yield join( - methodInvocation.method().declaredClass().accept(this), - collect(methodInvocation.parameters()) - ); - } - yield collect(methodInvocation.parameters()); - } + case CodeExpression.MethodInvocation(InvokesMethod invokes) -> collectMethodInvokesImports(invokes); case CodeExpression.ConstructorInvocation ctorInvocation -> join( ctorInvocation.type().accept(this), collect(ctorInvocation.parameters()) ); + case CodeExpression.MethodReference ref -> ref.type().accept(this); + default -> Set.of(); }; } @@ -141,15 +149,9 @@ yield join( case CodeStatement.ThrowStatement throwStatement -> throwStatement.throwExpression().accept(this); - case CodeStatement.MethodInvocation methodInvocation -> { - if (methodInvocation.method().modifiers().contains(Modifiers.STATIC)) { - yield join( - methodInvocation.method().declaredClass().accept(this), - collect(methodInvocation.parameters()) - ); - } - yield collect(methodInvocation.parameters()); - } + case CodeStatement.MethodInvocation(InvokesMethod invokes) -> collectMethodInvokesImports(invokes); + + case CodeStatement.Blank blank -> Set.of(); }; } } diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java index 135a8f00..1dc61d92 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java @@ -26,6 +26,7 @@ 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; @@ -160,8 +161,8 @@ public StringBuilder visitExpression(CodeExpression codeExpression) { builder.append(joining(constructorInvocation.parameters())); builder.append(")"); } - case CodeExpression.MethodInvocation methodInvocation -> { - appendMethodInvocation(builder, methodInvocation); + case CodeExpression.MethodInvocation(InvokesMethod invokes) -> { + appendMethodInvocation(builder, invokes); } case CodeExpression.Null ignored -> { builder.append("null"); @@ -172,6 +173,9 @@ public StringBuilder visitExpression(CodeExpression codeExpression) { case CodeExpression.Variable variable -> { builder.append(variable.name()); } + case CodeExpression.MethodReference ref -> { + builder.append(ref.type().name()).append("::").append(ref.methodName()); + } default -> throw new IllegalStateException("Invalid expression: " + codeExpression.getClass().getName()); } }); @@ -180,10 +184,15 @@ public StringBuilder visitExpression(CodeExpression codeExpression) { @Override public StringBuilder visitStatement(CodeStatement codeStatement) { return append(builder -> { + if (codeStatement instanceof CodeStatement.Blank) { + builder.append("\n"); + return; + } + appendIndent(builder); switch (codeStatement) { - case CodeStatement.MethodInvocation methodInvocation -> { - appendMethodInvocation(builder, methodInvocation); + case CodeStatement.MethodInvocation(InvokesMethod invokes) -> { + appendMethodInvocation(builder, invokes); } case CodeStatement.ReturnStatement returnStatement -> { builder.append("return"); @@ -197,6 +206,9 @@ public StringBuilder visitStatement(CodeStatement codeStatement) { appendNested(builder, throwStatement.throwExpression()); } case CodeStatement.VariableDeclaration variableDeclaration -> { + if (variableDeclaration.isFinal()) { + builder.append("final "); + } appendNested(builder, variableDeclaration.type()); builder.append(" "); builder.append(variableDeclaration.name()); 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/CommonJavadocVisitorTests.java b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/CommonJavadocVisitorTests.java index e3bc2b5a..f7989598 100644 --- 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 @@ -120,24 +120,21 @@ CodeMethod registerMethod() { } CodeMethod bootstrapMethod() { - return Builders.method() + return Builders.method("bootstrap") .setDeclaringClass(CodeClass.simple("io.papermc.paper.plugin.bootstrap.PluginBootstrap")) - .setName("bootstrap") .addParameter(CodeType.ofClass(CodeClass.simple("io.papermc.paper.plugin.bootstrap.BootstrapContext")), "context") .build(); } CodeMethod onLoadMethod() { - return Builders.method() + return Builders.method("onLoad") .setDeclaringClass(CodeClass.simple("org.bukkit.plugin.java.JavaPlugin")) - .setName("onLoad") .build(); } CodeMethod onEnableMethod() { - return Builders.method() + return Builders.method("onEnable") .setDeclaringClass(CodeClass.simple("org.bukkit.plugin.java.JavaPlugin")) - .setName("onEnable") .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 index 007bd960..c7f4802c 100644 --- 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 @@ -95,7 +95,7 @@ private CodeClass constructExampleCommandClass() { final CodeClass current = CodeClass.simple("com.example.ExampleCommandBrigadier"); return Builders.classBuilder(current.fullyQualifiedName()) - .setAnnotations(List.of(CodeAnnotation.of(CodeType.ofClass(CodeClass.simple(Classes.NULL_MARKED))))) + .setAnnotations(List.of(CodeAnnotation.of(CodeType.ofClass(Classes.NULL_MARKED)))) .setModifiers(Set.of(Modifiers.PUBLIC, Modifiers.FINAL)) .setJavadoc(CodeJavadoc.combineLines( CodeJavadoc.text("A very cool class."), @@ -109,7 +109,7 @@ private CodeClass constructExampleCommandClass() { .addField(Builders.field("DESCRIPTION", CodeType.ofClass(CodeClass.STRING)) .setModifiers(Set.of(Modifiers.PUBLIC, Modifiers.STATIC, Modifiers.FINAL)) .setInitialiser(CodeExpression.nullExpr()) - .addAnnotation(CodeAnnotation.of(CodeType.ofClass(CodeClass.simple(Classes.NULLABLE)))) + .addAnnotation(CodeAnnotation.of(CodeType.ofClass(Classes.NULLABLE))) ) .addField(Builders.field("ALIASES", CodeType.ofClass(CodeClass.STRING)) .setModifiers(Set.of(Modifiers.PUBLIC, Modifiers.STATIC, Modifiers.FINAL)) @@ -120,22 +120,18 @@ private CodeClass constructExampleCommandClass() { .setModifiers(Set.of(Modifiers.PUBLIC, Modifiers.STATIC)) .addParameter(CodeType.ofClass(commands), "commands") .setCodeBlock(List.of( - CodeStatement.methodInvocation( - Builders.method(commands, "register").build(), - List.of( - CodeExpression.methodCall(Builders.method(current, "create").build(), List.of()), - CodeExpression.variable("DESCRIPTION"), - CodeExpression.variable("ALIASES") - ), - "commands" + 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() - .setDeclaringClass(current) + .addMethod(Builders.method(current) .setThrowsExceptions(List.of(CodeType.ofClass("java.lang.IllegalAccessException"))) .setModifiers(Set.of(Modifiers.PRIVATE)) .setJavadoc(CodeJavadoc.combineLines( 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 index ca3fe44c..b1371cc6 100644 --- 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 @@ -57,40 +57,41 @@ void testPackageVisitThrows() { void testGatherExpressionImports() { // Test static method invocation check(""" - com.example.Test""", CodeExpression.methodCall( - Builders.method(CodeClass.simple("com.example.Test"), "execute") - .setModifiers(Set.of(Modifiers.STATIC)) - .build(), - List.of() - )); + 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 - """, CodeExpression.methodCall( - Builders.method(CodeClass.simple("com.example.Test"), "execute") - .setModifiers(Set.of(Modifiers.STATIC)) - .build(), - List.of(CodeExpression.constructorCall(CodeType.ofClass("io.library.Value"), List.of())) - )); + """, Builders.methodInvocation(Builders.method(CodeClass.simple("com.example.Test"), "execute") + .setModifiers(Set.of(Modifiers.STATIC))) + .addParameter(CodeExpression.constructorCall(CodeType.ofClass("io.library.Value"), List.of())) + .getAsExpression() + ); // Test instance method invocation - check("", CodeExpression.methodCall( - Builders.method(CodeClass.simple("com.example.Test"), "execute") - .build(), - List.of(), - "this" - )); + check("", Builders.methodInvocation(Builders.method(CodeClass.simple("com.example.Test"), "execute")) + .setInstanceVariable("this") + .getAsExpression() + ); // Test instance method invocation with parameters check(""" io.library.Value - """, CodeExpression.methodCall( - Builders.method(CodeClass.simple("com.example.Test"), "execute").build(), - List.of(CodeExpression.constructorCall(CodeType.ofClass("io.library.Value"), List.of())), - "this" - )); + """, Builders.methodInvocation(Builders.method(CodeClass.simple("com.example.Test"), "execute")) + .addParameter(CodeExpression.constructorCall(CodeType.ofClass("io.library.Value"), List.of())) + .setInstanceVariable("this") + .getAsExpression() + ); + + // Test method reference + check( + "io.library.Example", + CodeExpression.methodReference(CodeType.ofClass("io.library.Example"), "run") + ); } @Test @@ -107,12 +108,11 @@ void testFields() { java.lang.String com.example.TestClass """, Builders.field("field", CodeType.ofClass(CodeClass.STRING)) - .setInitialiser(CodeExpression.methodCall( - Builders.method(CodeClass.simple("com.example.TestClass"), "get") + .setInitialiser( + Builders.methodInvocation(Builders.method(CodeClass.simple("com.example.TestClass"), "get") .setModifiers(Set.of(Modifiers.STATIC)) - .build(), - List.of() - )) + ) + ) .build() ); } @@ -135,11 +135,8 @@ void testStatements() { """, CodeStatement.variableDeclaration( CodeType.ofClass(CodeClass.STRING), "name", - CodeExpression.methodCall( - Builders.method(CodeClass.simple("com.example.TheClass"), "get") - .setModifiers(Set.of(Modifiers.STATIC)) - .build(), - List.of() + Builders.methodInvocation(Builders.method(CodeClass.simple("com.example.TheClass"), "get") + .setModifiers(Set.of(Modifiers.STATIC)) ) )); @@ -159,20 +156,23 @@ void testStatements() { )); // Method invocation (instance) - check("", CodeStatement.methodInvocation( - Builders.method(CodeClass.simple("io.declared.ThisClass"), "doSomething").build(), - List.of( - CodeExpression.string("test") - ) - )); + check("", Builders.methodInvocation(Builders.method(CodeClass.simple("io.declared.ThisClass"), "doSomething")) + .addParameter(CodeExpression.string("test")) + .getAsStatement() + ); // Method invocation (static) - check("io.declared.ThisClass", CodeStatement.methodInvocation( - Builders.method(CodeClass.simple("io.declared.ThisClass"), "doSomething") - .setModifiers(Set.of(Modifiers.STATIC)) - .build(), - List.of( - CodeExpression.string("test") - ) - )); + 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)); } } 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 index 831ef67f..37a417c5 100644 --- 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 @@ -21,7 +21,6 @@ import net.strokkur.commands.internal.codegen.visitor.CodeVisitable; import net.strokkur.commands.internal.printer.command.JavaSourcePrintingVisitor; import net.strokkur.commands.internal.printer.javadoc.JavaMarkdownJavadocVisitor; -import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.Test; import java.util.List; @@ -33,7 +32,7 @@ class JavaCodeGenTests { private static final CodeClass EXAMPLE_CLASS = Builders.classBuilder("com.example.ExampleClass").build(); - private void check(@Language("JAVA") String expected, CodeVisitable visitable) { + private void check(String expected, CodeVisitable visitable) { final JavaSourcePrintingVisitor visitor = new JavaSourcePrintingVisitor(JavaMarkdownJavadocVisitor::new, " "); final String actual = visitable.accept(visitor).toString(); assertEquals(expected, actual); @@ -48,13 +47,15 @@ void testPackageThrows() { @Test void testClass() { - final @Language("JAVA") String expected = """ + // language=java + final String expected = """ class ExampleClass { } """; check(expected, EXAMPLE_CLASS); - final @Language("JAVA") String expectedWithFields = """ + // language=java + final String expectedWithFields = """ class ExampleClass { String oneField; int anotherField; @@ -66,7 +67,8 @@ class ExampleClass { .build() ); - final @Language("JAVA") String expectedWithMethods = """ + // language=java + final String expectedWithMethods = """ class ExampleClass { String oneMethod() { @@ -86,7 +88,8 @@ int anotherMethod() { .build() ); - final @Language("JAVA") String expectedCombined = """ + // language=java + final String expectedCombined = """ class ExampleClass { String oneField; int anotherField; @@ -124,8 +127,7 @@ int anotherInstanceMethod() { .addMethod(Builders.method(EXAMPLE_CLASS, "anotherInstanceMethod") .setReturnType(CodeType.INT) ) - .addMethod(Builders.method() - .setDeclaringClass(EXAMPLE_CLASS) + .addMethod(Builders.method(EXAMPLE_CLASS) .buildConstructor() ) .build() @@ -134,13 +136,15 @@ int anotherInstanceMethod() { @Test void testMethod() { - final @Language("JAVA") String expected = """ + // language=java + final String expected = """ void method() { } """; check(expected, Builders.method(EXAMPLE_CLASS, "method").build()); - final @Language("JAVA") String expectedWithModifiers = """ + // language=java + final String expectedWithModifiers = """ public static void method() { } """; @@ -148,23 +152,22 @@ public static void method() { .setModifiers(Set.of(Modifiers.PUBLIC, Modifiers.STATIC)) .build()); - final @Language("JAVA") String expectedWithStatements = """ + // language=java + final String expectedWithStatements = """ void method() { otherMethod(STATIC_VALUE, "Now"); } """; check(expectedWithStatements, Builders.method(EXAMPLE_CLASS, "method") .setCodeBlock(List.of( - CodeStatement.methodInvocation(Builders.method(EXAMPLE_CLASS, "otherMethod").build(), - List.of( - CodeExpression.variable("STATIC_VALUE"), - CodeExpression.string("Now") - ) - ) - )) - .build()); + Builders.methodInvocation(Builders.method(EXAMPLE_CLASS, "otherMethod")) + .addParameter(CodeExpression.variable("STATIC_VALUE")) + .addParameter(CodeExpression.string("Now")) + )).build() + ); - final @Language("JAVA") String expectedWithThrows = """ + // language=java + final String expectedWithThrows = """ void method() throws NullPointerException, SQLException { throw new SQLException("No database present :("); } @@ -186,19 +189,22 @@ void method() throws NullPointerException, SQLException { @Test void testField() { - final @Language("JAVA") String expectedNoInit = """ + // language=java + final String expectedNoInit = """ String someField; """; check(expectedNoInit, Builders.field("someField", CodeType.ofClass(CodeClass.STRING)).build()); - final @Language("JAVA") String expectedWithInit = """ + // language=java + final String expectedWithInit = """ String someField = "some value"; """; check(expectedWithInit, Builders.field("someField", CodeType.ofClass(CodeClass.STRING)) .setInitialiser(CodeExpression.string("some value")) .build()); - final @Language("JAVA") String expectedWithModifiers = """ + // language=java + final String expectedWithModifiers = """ public static final String SOME_FIELD; """; check(expectedWithModifiers, Builders.field("SOME_FIELD", CodeType.ofClass(CodeClass.STRING)) @@ -238,4 +244,82 @@ void testStatement() { 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", true) + .chain("toLol", false) + .chain("toList", true) + .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") + .setCodeBlock(List.of( + // BrigadierCommand command + CodeStatement.variableDeclarationFinal( + CodeType.ofClass("velocity.BrigadierCommand"), + "command", + CodeExpression.constructorCall(CodeType.ofClass("velocity.BrigadierCommand"), List.of( + Builders.methodInvocation("create") + )) + ), + + // CommandMeta meta + CodeStatement.variableDeclarationFinal( + CodeType.ofClass("velocity.CommandMeta"), + "meta", + Builders.methodInvocation("getCommandManager") + .setInstanceVariable("server") + .chain("metaBuilder", false, CodeExpression.variable("command")) + .chain("aliases", true, Builders.methodInvocation("toArray") + .setInstanceVariable("ALIASES") + .addParameter(CodeExpression.methodReference(CodeType.STRING_ARRAY, "new")) + ) + .chain("plugin", true, CodeExpression.variable("command$plugin")) + .chain("build", true) + ), + + CodeStatement.blank(), + + // register call + Builders.methodInvocation("getCommandManager") + .setInstanceVariable("server") + .chain("register", false, CodeExpression.variable("meta"), CodeExpression.variable("command")) + )) + ) + .build() + ); + } } From ddef4e19be9733971f376e898cea63e1de699eb0 Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Sun, 10 May 2026 15:04:11 +0200 Subject: [PATCH 07/16] Add support for lambdas --- .../internal/codegen/CodeExpression.java | 56 +++++++++++++++++ .../command/ImportGatheringVisitor.java | 4 ++ .../command/JavaSourcePrintingVisitor.java | 26 ++++++++ .../internal/codegen/ImportGatherTests.java | 24 ++++++++ .../internal/codegen/JavaCodeGenTests.java | 61 +++++++++++++++++++ 5 files changed, 171 insertions(+) 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 index dce804dc..9f55a2e3 100644 --- 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 @@ -18,9 +18,13 @@ package net.strokkur.commands.internal.codegen; 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.jetbrains.annotations.Unmodifiable; +import java.util.Arrays; import java.util.List; public sealed interface CodeExpression extends CodeVisitable, AsExpression { @@ -47,6 +51,19 @@ 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() + ); + } + @Override default R accept(CodeVisitor visitor) { return visitor.visitExpression(this); @@ -126,4 +143,43 @@ 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); + } + } } diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java index e21a51fc..cd9b85f2 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java @@ -126,6 +126,10 @@ public Set visitExpression(CodeExpression codeExpression) { case CodeExpression.MethodReference ref -> ref.type().accept(this); + case CodeExpression.SingleLineLambda lambda -> lambda.lambdaExpression().accept(this); + + case CodeExpression.MultiLineLambda lambda -> collect(lambda.statements()); + default -> Set.of(); }; } diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java index 1dc61d92..3e3c02a0 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java @@ -176,6 +176,21 @@ public StringBuilder visitExpression(CodeExpression codeExpression) { 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("}"); + } default -> throw new IllegalStateException("Invalid expression: " + codeExpression.getClass().getName()); } }); @@ -222,4 +237,15 @@ public StringBuilder visitStatement(CodeStatement codeStatement) { 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/test/java/net/strokkur/commands/internal/codegen/ImportGatherTests.java b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/ImportGatherTests.java index b1371cc6..cdf17107 100644 --- 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 @@ -175,4 +175,28 @@ void testArray() { // 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 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 index 37a417c5..4a47eb68 100644 --- 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 @@ -264,6 +264,67 @@ void methodInvocationFormat() { ); } + @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 testAdvancedRegisterMethod() { // language=java From d20307e6f048335a3ceef83044a29114d5ac1ead Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Sun, 10 May 2026 15:46:07 +0200 Subject: [PATCH 08/16] Add if-statement and instanceof expression --- .../internal/codegen/CodeExpression.java | 66 ++++++++++++++++++- .../internal/codegen/CodeStatement.java | 31 +++++++++ .../codegen/as/AsBooleanExpression.java | 24 +++++++ .../command/ImportGatheringVisitor.java | 10 +++ .../command/JavaSourcePrintingVisitor.java | 26 ++++++++ .../codegen/FullCommandClassBuilderTests.java | 2 +- .../internal/codegen/ImportGatherTests.java | 39 ++++++++++- .../internal/codegen/JavaCodeGenTests.java | 46 +++++++++++-- 8 files changed, 233 insertions(+), 11 deletions(-) create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/as/AsBooleanExpression.java 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 index 9f55a2e3..d2bf3947 100644 --- 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 @@ -17,12 +17,14 @@ */ 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 org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Unmodifiable; +import org.jspecify.annotations.Nullable; import java.util.Arrays; import java.util.List; @@ -36,8 +38,8 @@ static StringLiteral string(String value) { return new StringLiteral(value); } - static ConstructorInvocation constructorCall(CodeType.ClassType type, List parameters) { - return new ConstructorInvocation(type, parameters.stream() + static ConstructorInvocation constructorCall(CodeType.ClassType type, AsExpression... parameters) { + return new ConstructorInvocation(type, Arrays.stream(parameters) .map(AsExpression::getAsExpression) .toList() ); @@ -64,6 +66,10 @@ static MultiLineLambda lambda(List parameters, AsStatement... statements ); } + 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); @@ -74,6 +80,27 @@ default CodeExpression getAsExpression() { return this; } + sealed abstract class BooleanExpression> + implements CodeExpression, AsBooleanExpression + permits Instanceof { + 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; @@ -182,4 +209,39 @@ private MultiLineLambda(List lambdaParams, List statement 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() + ); + } + } } 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 index 0bb97452..ddba1fcb 100644 --- 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 @@ -17,6 +17,7 @@ */ 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; @@ -24,6 +25,9 @@ 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(CodeType type, String name, @Nullable AsExpression assignment) { return new VariableDeclaration(type, name, assignment == null ? null : assignment.getAsExpression(), false); @@ -45,6 +49,15 @@ 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; @@ -114,6 +127,24 @@ public CodeExpression 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(); 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/printer/command/ImportGatheringVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java index cd9b85f2..9191c389 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java @@ -130,6 +130,11 @@ public Set visitExpression(CodeExpression codeExpression) { case CodeExpression.MultiLineLambda lambda -> collect(lambda.statements()); + case CodeExpression.Instanceof instStmt -> join( + instStmt.left().accept(this), + instStmt.type().accept(this) + ); + default -> Set.of(); }; } @@ -155,6 +160,11 @@ yield join( 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/command/JavaSourcePrintingVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java index 3e3c02a0..9e55d0b4 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java @@ -191,6 +191,20 @@ public StringBuilder visitExpression(CodeExpression codeExpression) { 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(")"); + } + } default -> throw new IllegalStateException("Invalid expression: " + codeExpression.getClass().getName()); } }); @@ -205,6 +219,18 @@ public StringBuilder visitStatement(CodeStatement codeStatement) { } 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)); + }); + builder.append("}\n"); + return; + } + switch (codeStatement) { case CodeStatement.MethodInvocation(InvokesMethod invokes) -> { appendMethodInvocation(builder, invokes); 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 index c7f4802c..b18f3296 100644 --- 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 @@ -142,7 +142,7 @@ private CodeClass constructExampleCommandClass() { .setCodeBlock(List.of( CodeStatement.throwStatement(CodeExpression.constructorCall( CodeType.ofClass("java.lang.IllegalAccessException"), - List.of(CodeExpression.string("This class cannot be instantiated.")) + CodeExpression.string("This class cannot be instantiated.") )) )) .buildConstructor() 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 index cdf17107..8780a058 100644 --- 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 @@ -68,7 +68,7 @@ void testGatherExpressionImports() { io.library.Value """, Builders.methodInvocation(Builders.method(CodeClass.simple("com.example.Test"), "execute") .setModifiers(Set.of(Modifiers.STATIC))) - .addParameter(CodeExpression.constructorCall(CodeType.ofClass("io.library.Value"), List.of())) + .addParameter(CodeExpression.constructorCall(CodeType.ofClass("io.library.Value"))) .getAsExpression() ); @@ -82,7 +82,7 @@ void testGatherExpressionImports() { check(""" io.library.Value """, Builders.methodInvocation(Builders.method(CodeClass.simple("com.example.Test"), "execute")) - .addParameter(CodeExpression.constructorCall(CodeType.ofClass("io.library.Value"), List.of())) + .addParameter(CodeExpression.constructorCall(CodeType.ofClass("io.library.Value"))) .setInstanceVariable("this") .getAsExpression() ); @@ -151,7 +151,7 @@ void testStatements() { check("java.lang.NullPointerException", CodeStatement.throwStatement( CodeExpression.constructorCall( CodeType.ofClass("java.lang.NullPointerException"), - List.of(CodeExpression.string("It was null :(")) + CodeExpression.string("It was null :(") ) )); @@ -195,6 +195,39 @@ void testLambda() { )); } + @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(CodeExpression.constructorCall( + CodeType.ofClass("java.lang.IllegalStateException"), + CodeExpression.string("Don't do that.") + )) + )); + } + @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 index 4a47eb68..581694ff 100644 --- 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 @@ -179,9 +179,8 @@ void method() throws NullPointerException, SQLException { )) .setCodeBlock(List.of( CodeStatement.throwStatement(CodeExpression.constructorCall( - CodeType.ofClass("java.sql.SQLException"), List.of( - CodeExpression.string("No database present :(") - ) + CodeType.ofClass("java.sql.SQLException"), + CodeExpression.string("No database present :(") )) )) .build()); @@ -325,6 +324,42 @@ void testLambda() { ); } + @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(CodeExpression.constructorCall( + CodeType.ofClass("java.lang.IllegalStateException"), + CodeExpression.string("Don't do that.") + )) + )); + } + @Test void testAdvancedRegisterMethod() { // language=java @@ -352,9 +387,10 @@ public void register(ProxyServer server, Object command$plugin) { CodeStatement.variableDeclarationFinal( CodeType.ofClass("velocity.BrigadierCommand"), "command", - CodeExpression.constructorCall(CodeType.ofClass("velocity.BrigadierCommand"), List.of( + CodeExpression.constructorCall( + CodeType.ofClass("velocity.BrigadierCommand"), Builders.methodInvocation("create") - )) + ) ), // CommandMeta meta From e8c1b78c1cd11d766de6e721ab87a1fe710581d2 Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Sun, 10 May 2026 18:29:51 +0200 Subject: [PATCH 09/16] Extend constructor and general code method options --- .../internal/codegen/CodeExpression.java | 25 -------- .../internal/codegen/CodeStatement.java | 4 +- .../internal/codegen/InvokesMethod.java | 3 + .../internal/codegen/builder/Builders.java | 6 ++ .../codegen/builder/MethodBuilder.java | 5 -- .../builder/MethodInvocationBuilder.java | 31 ++++++--- .../AbstractSourcePrintingVisitor.java | 63 ++++++++++++++----- .../command/ImportGatheringVisitor.java | 10 +-- .../command/JavaSourcePrintingVisitor.java | 7 --- .../codegen/FullCommandClassBuilderTests.java | 7 +-- .../internal/codegen/ImportGatherTests.java | 17 +++-- .../internal/codegen/JavaCodeGenTests.java | 46 +++++++++----- 12 files changed, 127 insertions(+), 97 deletions(-) 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 index d2bf3947..eadac53a 100644 --- 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 @@ -38,13 +38,6 @@ static StringLiteral string(String value) { return new StringLiteral(value); } - static ConstructorInvocation constructorCall(CodeType.ClassType type, AsExpression... parameters) { - return new ConstructorInvocation(type, Arrays.stream(parameters) - .map(AsExpression::getAsExpression) - .toList() - ); - } - static Variable variable(String name) { return new Variable(name); } @@ -135,24 +128,6 @@ public String name() { record MethodInvocation(InvokesMethod invokes) implements CodeExpression { } - final class ConstructorInvocation implements CodeExpression { - private final CodeType.ClassType type; - private final List parameters; - - private ConstructorInvocation(CodeType.ClassType type, List parameters) { - this.type = type; - this.parameters = parameters; - } - - public CodeType.ClassType type() { - return type; - } - - public List parameters() { - return parameters; - } - } - final class MethodReference implements CodeExpression { private final CodeType type; private final String methodName; 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 index ddba1fcb..407cdc91 100644 --- 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 @@ -41,8 +41,8 @@ static ReturnStatement returnStatement(@Nullable AsExpression returnExpression) return new ReturnStatement(returnExpression == null ? null : returnExpression.getAsExpression()); } - static ThrowStatement throwStatement(CodeExpression throwExpression) { - return new ThrowStatement(throwExpression); + static ThrowStatement throwStatement(AsExpression throwExpression) { + return new ThrowStatement(throwExpression.getAsExpression()); } static Blank blank() { 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 index ae9863a1..fc412e08 100644 --- 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 @@ -29,12 +29,15 @@ public record InvokesMethod( @Nullable String instanceVariable, boolean newline, boolean isStatic, + boolean isCtor, + boolean multilineParameters, List chained ) implements ConvertableTo.Self { public record Chained( String methodName, List parameters, + boolean multilineParameters, boolean newline ) { } 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 index 9bb94f7d..38d8b7db 100644 --- 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 @@ -70,6 +70,12 @@ public static MethodInvocationBuilder methodInvocation(ConvertableTo return builder; } + public static MethodInvocationBuilder ctorInvocation(CodeType.ClassType type) { + return new MethodInvocationBuilder(type.name()) + .setCtor() + .setType(type); + } + private Builders() { } } 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 index 6829c34d..026f6624 100644 --- 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 @@ -66,11 +66,6 @@ public MethodBuilder setReturnType(CodeType returnType) { return this; } - public MethodBuilder setParameters(List parameters) { - this.parameters = parameters; - return this; - } - public MethodBuilder setModifiers(Set modifiers) { this.modifiers = modifiers; return this; 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 index f6b21b22..9a3384e1 100644 --- 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 @@ -23,20 +23,21 @@ import net.strokkur.commands.internal.codegen.InvokesMethod; import net.strokkur.commands.internal.codegen.as.AsExpression; import net.strokkur.commands.internal.codegen.as.AsStatement; -import net.strokkur.commands.internal.util.ConvertableTo; import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -public class MethodInvocationBuilder implements ConvertableTo, AsExpression, AsStatement { +public class MethodInvocationBuilder implements AsExpression, AsStatement { private final String methodName; private CodeType.@Nullable ClassType type = null; private final List parameters = new ArrayList<>(); private @Nullable String instanceVariable = null; private boolean newline = false; private boolean isStatic = false; + private boolean isCtor = false; + private boolean multilineParameters = false; private final List chained = new ArrayList<>(); MethodInvocationBuilder(String methodName) { @@ -68,24 +69,38 @@ public MethodInvocationBuilder setStatic() { return this; } + public MethodInvocationBuilder setCtor() { + this.isCtor = true; + return this; + } + + public MethodInvocationBuilder setMultilineParameters() { + this.multilineParameters = true; + return this; + } + + public MethodInvocationBuilder chain(String methodName, AsExpression... parameters) { + return chain(methodName, false, false, parameters); + } + public MethodInvocationBuilder chain(String methodName, boolean newline, AsExpression... parameters) { + return chain(methodName, newline, false, parameters); + } + + public MethodInvocationBuilder chain(String methodName, boolean newline, boolean multilineParameters, AsExpression... parameters) { this.chained.add(new InvokesMethod.Chained( methodName, Arrays.stream(parameters) .map(AsExpression::getAsExpression) .toList(), + multilineParameters, newline )); return this; } public InvokesMethod build() { - return new InvokesMethod(methodName, type, List.copyOf(parameters), instanceVariable, newline, isStatic, List.copyOf(chained)); - } - - @Override - public InvokesMethod convert() { - return build(); + return new InvokesMethod(methodName, type, List.copyOf(parameters), instanceVariable, newline, isStatic, isCtor, multilineParameters, List.copyOf(chained)); } @Override diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/AbstractSourcePrintingVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/AbstractSourcePrintingVisitor.java index f64ad73a..1da9f6a0 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/AbstractSourcePrintingVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/AbstractSourcePrintingVisitor.java @@ -18,6 +18,7 @@ package net.strokkur.commands.internal.printer.command; 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; @@ -116,30 +117,54 @@ protected void printModifiersIndented(StringBuilder builder, Set modi .forEach(mod -> builder.append(mod).append(' ')); } + protected void appendMethodParametersMultiline(StringBuilder builder, List parameters) { + appendIndented(() -> { + 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) { - final String source; - if (method.instanceVariable() != null) { - source = method.instanceVariable(); - } else if (method.isStatic() && method.type() != null) { - source = method.type().name(); + if (method.isCtor()) { + builder.append("new "); } else { - source = null; - } + final String source; + if (method.instanceVariable() != null) { + source = method.instanceVariable(); + } else if (method.isStatic() && method.type() != null) { + source = method.type().name(); + } else { + source = null; + } - if (source != null) { - builder.append(source); - incrementIndent(); - if (method.newline()) { - builder.append("\n"); - appendIndent(builder); + if (source != null) { + builder.append(source); + incrementIndent(); + if (method.newline()) { + builder.append("\n"); + appendIndent(builder); + } + builder.append("."); + decrementIndent(); } - builder.append("."); - decrementIndent(); } builder.append(method.methodName()); builder.append("("); - builder.append(joining(method.parameters())); + if (method.multilineParameters()) { + appendMethodParametersMultiline(builder, method.parameters()); + } else { + builder.append(joining(method.parameters())); + } builder.append(")"); incrementIndent(); @@ -151,7 +176,11 @@ protected void appendMethodInvocation(StringBuilder builder, InvokesMethod metho builder.append("."); builder.append(chained.methodName()); builder.append("("); - builder.append(joining(chained.parameters())); + if (chained.multilineParameters()) { + appendMethodParametersMultiline(builder, chained.parameters()); + } else { + builder.append(joining(chained.parameters())); + } builder.append(")"); } decrementIndent(); diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java index 9191c389..00149119 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java @@ -31,6 +31,7 @@ import net.strokkur.commands.internal.codegen.visitor.CodeVisitor; import java.util.Collection; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -38,6 +39,10 @@ public class ImportGatheringVisitor implements CodeVisitor> { private Set collectMethodInvokesImports(InvokesMethod invokes) { + if (invokes.isCtor()) { + return Objects.requireNonNull(invokes.type()).accept(this); + } + if (invokes.isStatic() && invokes.type() != null) { return join( invokes.type().accept(this), @@ -119,11 +124,6 @@ public Set visitExpression(CodeExpression codeExpression) { return switch (codeExpression) { case CodeExpression.MethodInvocation(InvokesMethod invokes) -> collectMethodInvokesImports(invokes); - case CodeExpression.ConstructorInvocation ctorInvocation -> join( - ctorInvocation.type().accept(this), - collect(ctorInvocation.parameters()) - ); - case CodeExpression.MethodReference ref -> ref.type().accept(this); case CodeExpression.SingleLineLambda lambda -> lambda.lambdaExpression().accept(this); diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java index 9e55d0b4..401e73c3 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java @@ -154,13 +154,6 @@ public StringBuilder visitField(CodeField codeField) { public StringBuilder visitExpression(CodeExpression codeExpression) { return append(builder -> { switch (codeExpression) { - case CodeExpression.ConstructorInvocation constructorInvocation -> { - builder.append("new "); - appendNested(builder, constructorInvocation.type()); - builder.append("("); - builder.append(joining(constructorInvocation.parameters())); - builder.append(")"); - } case CodeExpression.MethodInvocation(InvokesMethod invokes) -> { appendMethodInvocation(builder, invokes); } 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 index b18f3296..72cbfdfd 100644 --- 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 @@ -140,10 +140,9 @@ private CodeClass constructExampleCommandClass() { CodeJavadoc.throwsMeta(CodeType.ofClass("java.lang.IllegalAccessException"), "always") )) .setCodeBlock(List.of( - CodeStatement.throwStatement(CodeExpression.constructorCall( - CodeType.ofClass("java.lang.IllegalAccessException"), - CodeExpression.string("This class cannot be instantiated.") - )) + CodeStatement.throwStatement(Builders.ctorInvocation(CodeType.ofClass("java.lang.IllegalAccessException")) + .addParameter(CodeExpression.string("This class cannot be instantiated.")) + ) )) .buildConstructor() ) 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 index 8780a058..e7089f14 100644 --- 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 @@ -68,7 +68,7 @@ void testGatherExpressionImports() { io.library.Value """, Builders.methodInvocation(Builders.method(CodeClass.simple("com.example.Test"), "execute") .setModifiers(Set.of(Modifiers.STATIC))) - .addParameter(CodeExpression.constructorCall(CodeType.ofClass("io.library.Value"))) + .addParameter(Builders.ctorInvocation(CodeType.ofClass("io.library.Value"))) .getAsExpression() ); @@ -82,7 +82,7 @@ void testGatherExpressionImports() { check(""" io.library.Value """, Builders.methodInvocation(Builders.method(CodeClass.simple("com.example.Test"), "execute")) - .addParameter(CodeExpression.constructorCall(CodeType.ofClass("io.library.Value"))) + .addParameter(Builders.ctorInvocation(CodeType.ofClass("io.library.Value"))) .setInstanceVariable("this") .getAsExpression() ); @@ -149,10 +149,8 @@ void testStatements() { // Throw statement check("java.lang.NullPointerException", CodeStatement.throwStatement( - CodeExpression.constructorCall( - CodeType.ofClass("java.lang.NullPointerException"), - CodeExpression.string("It was null :(") - ) + Builders.ctorInvocation(CodeType.ofClass("java.lang.NullPointerException")) + .addParameter(CodeExpression.string("It was null :(")) )); // Method invocation (instance) @@ -221,11 +219,10 @@ void testIfStatement() { CodeType.ofClass("org.bukkit.entity.Player"), null ).invert(), - CodeStatement.throwStatement(CodeExpression.constructorCall( - CodeType.ofClass("java.lang.IllegalStateException"), - CodeExpression.string("Don't do that.") + CodeStatement.throwStatement(Builders.ctorInvocation(CodeType.ofClass("java.lang.IllegalStateException")) + .addParameter(CodeExpression.string("Don't do that.")) )) - )); + ); } @Test 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 index 581694ff..f7e8ceb3 100644 --- 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 @@ -178,10 +178,9 @@ void method() throws NullPointerException, SQLException { CodeType.ofClass("java.sql.SQLException") )) .setCodeBlock(List.of( - CodeStatement.throwStatement(CodeExpression.constructorCall( - CodeType.ofClass("java.sql.SQLException"), - CodeExpression.string("No database present :(") - )) + CodeStatement.throwStatement(Builders.ctorInvocation(CodeType.ofClass("java.sql.SQLException")) + .addParameter(CodeExpression.string("No database present :(")) + ) )) .build()); } @@ -353,10 +352,31 @@ void testIfStatement() { CodeType.ofClass("org.bukkit.entity.Player"), null ).invert(), - CodeStatement.throwStatement(CodeExpression.constructorCall( - CodeType.ofClass("java.lang.IllegalStateException"), - CodeExpression.string("Don't do that.") - )) + 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") + ) )); } @@ -387,10 +407,8 @@ public void register(ProxyServer server, Object command$plugin) { CodeStatement.variableDeclarationFinal( CodeType.ofClass("velocity.BrigadierCommand"), "command", - CodeExpression.constructorCall( - CodeType.ofClass("velocity.BrigadierCommand"), - Builders.methodInvocation("create") - ) + Builders.ctorInvocation(CodeType.ofClass("velocity.BrigadierCommand")) + .addParameter(Builders.methodInvocation("create")) ), // CommandMeta meta @@ -399,7 +417,7 @@ public void register(ProxyServer server, Object command$plugin) { "meta", Builders.methodInvocation("getCommandManager") .setInstanceVariable("server") - .chain("metaBuilder", false, CodeExpression.variable("command")) + .chain("metaBuilder", CodeExpression.variable("command")) .chain("aliases", true, Builders.methodInvocation("toArray") .setInstanceVariable("ALIASES") .addParameter(CodeExpression.methodReference(CodeType.STRING_ARRAY, "new")) @@ -413,7 +431,7 @@ public void register(ProxyServer server, Object command$plugin) { // register call Builders.methodInvocation("getCommandManager") .setInstanceVariable("server") - .chain("register", false, CodeExpression.variable("meta"), CodeExpression.variable("command")) + .chain("register", CodeExpression.variable("meta"), CodeExpression.variable("command")) )) ) .build() From a26a9599a0f745ee337c24503d58af366508dfac Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Sun, 10 May 2026 18:31:06 +0200 Subject: [PATCH 10/16] Add package-info.java files to test module --- .../commands/internal/codegen/builder/package-info.java | 4 ++++ .../net/strokkur/commands/internal/codegen/package-info.java | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 processor/common/src/test/java/net/strokkur/commands/internal/codegen/builder/package-info.java create mode 100644 processor/common/src/test/java/net/strokkur/commands/internal/codegen/package-info.java 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/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; From e0f879228a2836338acc98d47c080dadcde625c6 Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Sun, 10 May 2026 19:15:50 +0200 Subject: [PATCH 11/16] Add field access expressions --- .../common/src/main/java/module-info.java | 1 + .../internal/codegen/CodeExpression.java | 9 +++ .../internal/codegen/builder/Builders.java | 4 + .../codegen/builder/FieldAccessBuilder.java | 68 +++++++++++++++++ .../command/ImportGatheringVisitor.java | 14 ++++ .../command/JavaSourcePrintingVisitor.java | 14 ++++ .../internal/codegen/ImportGatherTests.java | 34 +++++++++ .../internal/codegen/JavaCodeGenTests.java | 75 +++++++++++++++++++ 8 files changed, 219 insertions(+) create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/FieldAccessBuilder.java diff --git a/processor/common/src/main/java/module-info.java b/processor/common/src/main/java/module-info.java index 5614c916..3386dc42 100644 --- a/processor/common/src/main/java/module-info.java +++ b/processor/common/src/main/java/module-info.java @@ -9,6 +9,7 @@ requires static transitive org.jspecify; requires jdk.sctp; requires java.xml; + requires java.desktop; exports net.strokkur.commands.internal; exports net.strokkur.commands.internal.abstraction; 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 index eadac53a..2bf0934e 100644 --- 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 @@ -22,6 +22,7 @@ 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; @@ -219,4 +220,12 @@ public Instanceof invert() { ); } } + + 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/builder/Builders.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/Builders.java index 38d8b7db..cae8e5a2 100644 --- 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 @@ -76,6 +76,10 @@ public static MethodInvocationBuilder ctorInvocation(CodeType.ClassType type) { .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/FieldAccessBuilder.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/FieldAccessBuilder.java new file mode 100644 index 00000000..7ac09cca --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/FieldAccessBuilder.java @@ -0,0 +1,68 @@ +/* + * 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.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(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/printer/command/ImportGatheringVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java index 00149119..68a24aad 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java @@ -29,6 +29,7 @@ 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; @@ -59,6 +60,14 @@ private Set join(Set... all) { .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()) @@ -135,6 +144,11 @@ public Set visitExpression(CodeExpression codeExpression) { instStmt.type().accept(this) ); + case CodeExpression.FieldAccess field -> join( + maybeAccess(field.source()), + field.isStatic() ? maybeAccess(field.type()) : Set.of() + ); + default -> Set.of(); }; } diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java index 401e73c3..aa7cf6d5 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java @@ -198,6 +198,19 @@ public StringBuilder visitExpression(CodeExpression codeExpression) { 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()); } }); @@ -220,6 +233,7 @@ public StringBuilder visitStatement(CodeStatement codeStatement) { appendIndented(() -> { ifStmt.ifTrue().forEach(stmt -> appendNested(builder, stmt)); }); + appendIndent(builder); builder.append("}\n"); return; } 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 index e7089f14..592c9c69 100644 --- 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 @@ -225,6 +225,40 @@ void testIfStatement() { ); } + @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 index f7e8ceb3..0fb05499 100644 --- 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 @@ -380,6 +380,81 @@ void testIfStatement() { )); } + @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 From c5ebf21a9295103757966fdb8f62573d76ce3f22 Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Sun, 10 May 2026 21:50:09 +0200 Subject: [PATCH 12/16] Add full velocity integration test --- processor/common/build.gradle.kts | 2 +- .../commands/internal/codegen/CodeClass.java | 14 +- .../commands/internal/codegen/CodeMethod.java | 9 - .../internal/codegen/CodePackage.java | 16 +- .../commands/internal/codegen/CodeType.java | 58 ++- .../internal/codegen/InvokesMethod.java | 26 +- .../codegen/builder/ClassBuilder.java | 15 +- .../codegen/builder/FieldBuilder.java | 4 + .../codegen/builder/MethodBuilder.java | 8 + .../builder/MethodInvocationBuilder.java | 29 +- .../internal/codegen/javadoc/CodeJavadoc.java | 17 +- .../AbstractJavadocPrintingVisitor.java | 12 + .../javadoc/JavaMarkdownJavadocVisitor.java | 13 +- .../javadoc/JavaStarJavadocVisitor.java | 50 ++- .../AbstractSourcePrintingVisitor.java | 82 +++-- .../ImportGatheringVisitor.java | 73 ++-- .../JavaSourcePrintingVisitor.java | 32 +- .../codegen/CodePackageEqualityTests.java | 68 ++++ .../codegen/FullCommandClassBuilderTests.java | 13 +- .../internal/codegen/ImportGatherTests.java | 5 +- .../internal/codegen/JavaCodeGenTests.java | 18 +- .../codegen/JavadocMarkdownVisitorTests.java | 6 +- .../codegen/JavadocStarVisitorTests.java | 4 +- .../FullVelocityIntegrationTest.java | 346 ++++++++++++++++++ 24 files changed, 779 insertions(+), 141 deletions(-) rename processor/common/src/main/java/net/strokkur/commands/internal/printer/{command => source}/AbstractSourcePrintingVisitor.java (78%) rename processor/common/src/main/java/net/strokkur/commands/internal/printer/{command => source}/ImportGatheringVisitor.java (69%) rename processor/common/src/main/java/net/strokkur/commands/internal/printer/{command => source}/JavaSourcePrintingVisitor.java (89%) create mode 100644 processor/common/src/test/java/net/strokkur/commands/internal/codegen/CodePackageEqualityTests.java create mode 100644 processor/common/src/test/java/net/strokkur/commands/internal/codegen/integration/FullVelocityIntegrationTest.java diff --git a/processor/common/build.gradle.kts b/processor/common/build.gradle.kts index e808c323..1b39fae5 100644 --- a/processor/common/build.gradle.kts +++ b/processor/common/build.gradle.kts @@ -20,7 +20,7 @@ tasks { test { useJUnitPlatform() testLogging { - events("passed", "skipped", "failed") + events("skipped", "failed") } } 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 index a58e5496..3b09637c 100644 --- 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 @@ -30,10 +30,14 @@ 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 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( @@ -88,7 +92,7 @@ public List fields() { @Override @UnmodifiableView - public List typeParameters() { + public List typeParameters() { return Collections.unmodifiableList(typeParameters); } } 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 index 8eb5c488..a9d3ab62 100644 --- 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 @@ -25,7 +25,6 @@ import java.util.List; import java.util.Set; -import java.util.stream.Collectors; public class CodeMethod implements CodeVisitable, ConvertableTo.Self { private final CodeClass declaredClass; @@ -67,14 +66,6 @@ public String name() { return name; } - /// Example: `methodName(String, int)` - public String javadocName() { - return name() + "(" + parameters().stream() - .map(param -> param.type().fullyQualifiedName()) - .collect(Collectors.joining(", ")) - + ")"; - } - public CodeClass declaredClass() { return declaredClass; } 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 index 6a88093d..ec9370f6 100644 --- 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 @@ -19,11 +19,14 @@ 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 { +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) { @@ -43,16 +46,25 @@ 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 Objects.deepEquals(paths, that.paths); + 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/CodeType.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeType.java index 27e55632..73313741 100644 --- 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 @@ -19,8 +19,13 @@ 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; -public interface CodeType extends CodeVisitable { +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"); @@ -30,19 +35,31 @@ public interface CodeType extends CodeVisitable { 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) { return new GenericType(name); } static ClassType ofClass(CodeClass codeClass) { - return new ClassType(codeClass); + return new ClassType(codeClass, null); } static ClassType ofClass(String fqn) { - return new ClassType(CodeClass.simple(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) { @@ -58,6 +75,11 @@ 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; @@ -96,9 +118,16 @@ private GenericType(String name) { class ClassType implements CodeType { private final CodeClass codeClass; + private final @Nullable List types; - private ClassType(CodeClass codeClass) { + private ClassType(CodeClass codeClass, @Nullable List types) { this.codeClass = codeClass; + this.types = types; + } + + @Contract(pure = true) + public @Nullable List types() { + return types == null ? null : List.copyOf(types); } @Override @@ -110,6 +139,27 @@ public String name() { 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 { 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 index fc412e08..07dbb51c 100644 --- 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 @@ -27,18 +27,38 @@ public record InvokesMethod( CodeType.@Nullable ClassType type, List parameters, @Nullable String instanceVariable, - boolean newline, boolean isStatic, boolean isCtor, - boolean multilineParameters, + StyleConfig style, List chained ) implements ConvertableTo.Self { public record Chained( String methodName, List parameters, + StyleConfig style + ) { + } + + public record StyleConfig( + boolean newline, boolean multilineParameters, - boolean newline + 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/codegen/builder/ClassBuilder.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/ClassBuilder.java index bd5d3c9b..7e05b9b5 100644 --- 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 @@ -44,7 +44,7 @@ public class ClassBuilder implements ConvertableTo { private final List methods = new ArrayList<>(); private final List fields = new ArrayList<>(); private @Nullable CodeJavadoc javadoc = null; - private List typeParameters = new ArrayList<>(); + private List typeParameters = new ArrayList<>(); ClassBuilder(String name, CodePackage codePackage) { this.codePackage = codePackage; @@ -56,13 +56,22 @@ public ClassBuilder setParentClass(@Nullable ConvertableTo parentClas return this; } + public ClassBuilder setModifiers(Modifiers... modifiers) { + return setModifiers(Set.of(modifiers)); + } + public ClassBuilder setModifiers(Set modifiers) { this.modifiers = modifiers; return this; } public ClassBuilder setAnnotations(List annotations) { - this.annotations = annotations; + this.annotations = new ArrayList<>(annotations); + return this; + } + + public ClassBuilder addAnnotations(CodeAnnotation... annotations) { + this.annotations.addAll(List.of(annotations)); return this; } @@ -71,7 +80,7 @@ public ClassBuilder setJavadoc(@Nullable CodeJavadoc javadoc) { return this; } - public ClassBuilder setTypeParameters(List typeParameters) { + public ClassBuilder setTypeParameters(List typeParameters) { this.typeParameters = typeParameters; return this; } 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 index 9dd5310f..9a5f3c98 100644 --- 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 @@ -58,6 +58,10 @@ public FieldBuilder setInitialiser(AsExpression initialiser) { return this; } + public FieldBuilder setModifiers(Modifiers... modifiers) { + return setModifiers(Set.of(modifiers)); + } + public FieldBuilder setModifiers(Set modifiers) { this.modifiers.clear(); this.modifiers.addAll(modifiers); 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 index 026f6624..15000f73 100644 --- 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 @@ -66,6 +66,10 @@ public MethodBuilder setReturnType(CodeType returnType) { return this; } + public MethodBuilder setModifiers(Modifiers... modifiers) { + return setModifiers(Set.of(modifiers)); + } + public MethodBuilder setModifiers(Set modifiers) { this.modifiers = modifiers; return this; @@ -76,6 +80,10 @@ public MethodBuilder setJavadoc(@Nullable CodeJavadoc javadoc) { return this; } + public MethodBuilder setCodeBlock(AsStatement... statements) { + return setCodeBlock(List.of(statements)); + } + public MethodBuilder setCodeBlock(List statements) { this.codeBlock = new CodeBlock(statements.stream() .map(AsStatement::getAsStatement) 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 index 9a3384e1..7936a90d 100644 --- 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 @@ -38,6 +38,7 @@ public class MethodInvocationBuilder implements AsExpression, AsStatement { 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) { @@ -64,6 +65,10 @@ public MethodInvocationBuilder setNewline() { return this; } + public MethodInvocationBuilder setStatic(CodeType.ClassType type) { + return setType(type).setStatic(); + } + public MethodInvocationBuilder setStatic() { this.isStatic = true; return this; @@ -79,28 +84,36 @@ public MethodInvocationBuilder setMultilineParameters() { return this; } - public MethodInvocationBuilder chain(String methodName, AsExpression... parameters) { - return chain(methodName, false, false, parameters); + public MethodInvocationBuilder setNewlineClosingBrace() { + this.newlineClosingBrace = true; + return this; } - public MethodInvocationBuilder chain(String methodName, boolean newline, AsExpression... parameters) { - return chain(methodName, newline, false, parameters); + public MethodInvocationBuilder chain(String methodName, AsExpression... parameters) { + return chain(methodName, InvokesMethod.StyleConfig.DEFAULT, parameters); } - public MethodInvocationBuilder chain(String methodName, boolean newline, boolean multilineParameters, AsExpression... 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(), - multilineParameters, - newline + config )); return this; } public InvokesMethod build() { - return new InvokesMethod(methodName, type, List.copyOf(parameters), instanceVariable, newline, isStatic, isCtor, multilineParameters, List.copyOf(chained)); + return new InvokesMethod(methodName, + type, + List.copyOf(parameters), + instanceVariable, + isStatic, + isCtor, + new InvokesMethod.StyleConfig(newline, multilineParameters, newlineClosingBrace), + List.copyOf(chained) + ); } @Override 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 index fe61a37c..03928480 100644 --- 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 @@ -21,6 +21,7 @@ import net.strokkur.commands.internal.codegen.CodeMethod; import net.strokkur.commands.internal.codegen.CodeType; import net.strokkur.commands.internal.codegen.visitor.JavadocVisitor; +import net.strokkur.commands.internal.util.ConvertableTo; import org.jspecify.annotations.Nullable; import java.util.List; @@ -53,12 +54,12 @@ static CodeJavadoc version(String version) { return new Meta("version", version); } - static CodeJavadoc see(CodeMethod method, @Nullable String description) { + static CodeJavadoc see(ConvertableTo method, @Nullable String description) { return see(method, description, false); } - static CodeJavadoc see(CodeMethod method, @Nullable String description, boolean localMethod) { - return new MethodReferenceMeta("see", method, description, localMethod); + static CodeJavadoc see(ConvertableTo method, @Nullable String description, boolean localMethod) { + return new MethodReferenceMeta("see", method.convert(), description, localMethod); } static CodeJavadoc throwsMeta(CodeType.ClassType exception, @Nullable String description) { @@ -100,20 +101,20 @@ static CodeJavadoc classReference(CodeClass ref, @Nullable String description) { return new ClassReference(ref, description); } - static CodeJavadoc methodReference(CodeMethod ref) { + static CodeJavadoc methodReference(ConvertableTo ref) { return methodReference(ref, null); } - static CodeJavadoc methodReference(CodeMethod ref, @Nullable String description) { + static CodeJavadoc methodReference(ConvertableTo ref, @Nullable String description) { return methodReference(ref, description, false); } - static CodeJavadoc methodReference(CodeMethod ref, boolean localMethod) { + static CodeJavadoc methodReference(ConvertableTo ref, boolean localMethod) { return methodReference(ref, null, localMethod); } - static CodeJavadoc methodReference(CodeMethod ref, @Nullable String description, boolean localMethod) { - return new MethodReference(ref, description, localMethod); + static CodeJavadoc methodReference(ConvertableTo ref, @Nullable String description, boolean localMethod) { + return new MethodReference(ref.convert(), description, localMethod); } // 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 index 930ef274..90c5d0d6 100644 --- 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 @@ -17,13 +17,25 @@ */ 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 index e1e8e155..cce961b2 100644 --- 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 @@ -17,12 +17,23 @@ */ 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")) @@ -68,7 +79,7 @@ public void visit(CodeJavadoc.ClassReference value) { builder.append('[').append(value.name()).append(']'); } - builder.append('[').append(value.codeClass().fullyQualifiedName()).append(']'); + builder.append('[').append(getQualifiedTypeName(value.codeClass())).append(']'); } @Override 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 index 23d719e0..dd87295c 100644 --- 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 @@ -17,15 +17,29 @@ */ 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<>(); @@ -70,7 +84,7 @@ public void visit(CodeJavadoc.ClassReferenceMeta value) { .append('@') .append(value.descriptor()) .append(' ') - .append(value.type().fullyQualifiedName()); + .append(getQualifiedTypeName(value.type())); if (value.text() != null) { builder.append(' ').append(value.text()); @@ -117,7 +131,7 @@ public void visit(CodeJavadoc.Url value) { @Override public void visit(CodeJavadoc.ClassReference value) { - builder.append("{@link ").append(value.codeClass().fullyQualifiedName()); + builder.append("{@link ").append(getQualifiedTypeName(value.codeClass())); if (value.name() != null) { builder.append(" ").append(value.name()); } @@ -133,9 +147,37 @@ public void visit(CodeJavadoc.MethodReference value) { 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 - ? "#" + method.javadocName() - : method.declaredClass().fullyQualifiedName() + "#" + method.javadocName(); + ? "#" + javadocName(method) + : getQualifiedTypeName(method.declaredClass()) + "#" + javadocName(method); } } diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/AbstractSourcePrintingVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/AbstractSourcePrintingVisitor.java similarity index 78% rename from processor/common/src/main/java/net/strokkur/commands/internal/printer/command/AbstractSourcePrintingVisitor.java rename to processor/common/src/main/java/net/strokkur/commands/internal/printer/source/AbstractSourcePrintingVisitor.java index 1da9f6a0..2a2db566 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/AbstractSourcePrintingVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/AbstractSourcePrintingVisitor.java @@ -15,7 +15,7 @@ * 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.command; +package net.strokkur.commands.internal.printer.source; import net.strokkur.commands.internal.codegen.CodeAnnotation; import net.strokkur.commands.internal.codegen.CodeExpression; @@ -39,11 +39,14 @@ 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) { + public AbstractSourcePrintingVisitor(Supplier javadocPrintingVisitor, String indentString, String continuationIndentString) { this.javadocPrintingVisitor = javadocPrintingVisitor; this.indentString = indentString; + this.continuationIndentString = continuationIndentString; } /// Packages are never printed. @@ -53,9 +56,15 @@ public final StringBuilder visitPackage(CodePackage codePackage) { } protected final void appendIndented(Runnable run) { - incrementIndent(); + indentation++; + run.run(); + indentation--; + } + + protected final void appendIndentedContinuation(Runnable run) { + continuationIndent++; run.run(); - decrementIndent(); + continuationIndent--; } protected final StringBuilder append(Consumer run) { @@ -65,15 +74,7 @@ protected final StringBuilder append(Consumer run) { } protected final void appendIndent(StringBuilder builder) { - builder.repeat(indentString, indentation); - } - - protected final void incrementIndent() { - indentation++; - } - - protected final void decrementIndent() { - indentation--; + builder.repeat(indentString, indentation).repeat(continuationIndentString, continuationIndent); } // Utility methods @@ -118,7 +119,7 @@ protected void printModifiersIndented(StringBuilder builder, Set modi } protected void appendMethodParametersMultiline(StringBuilder builder, List parameters) { - appendIndented(() -> { + appendIndentedContinuation(() -> { builder.append("\n"); for (int i = 0, parametersSize = parameters.size(); i < parametersSize; i++) { final CodeExpression parameter = parameters.get(i); @@ -148,41 +149,46 @@ protected void appendMethodInvocation(StringBuilder builder, InvokesMethod metho if (source != null) { builder.append(source); - incrementIndent(); - if (method.newline()) { - builder.append("\n"); - appendIndent(builder); - } - builder.append("."); - decrementIndent(); + appendIndentedContinuation(() -> { + if (method.style().newline()) { + builder.append("\n"); + appendIndent(builder); + } + builder.append("."); + }); } } builder.append(method.methodName()); builder.append("("); - if (method.multilineParameters()) { + if (method.style().multilineParameters()) { appendMethodParametersMultiline(builder, method.parameters()); } else { builder.append(joining(method.parameters())); } builder.append(")"); - incrementIndent(); - for (InvokesMethod.Chained chained : method.chained()) { - if (chained.newline()) { - builder.append("\n"); - appendIndent(builder); - } - builder.append("."); - builder.append(chained.methodName()); - builder.append("("); - if (chained.multilineParameters()) { - appendMethodParametersMultiline(builder, chained.parameters()); - } else { - builder.append(joining(chained.parameters())); + 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(")"); } - builder.append(")"); - } - decrementIndent(); + }); } } diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/ImportGatheringVisitor.java similarity index 69% rename from processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java rename to processor/common/src/main/java/net/strokkur/commands/internal/printer/source/ImportGatheringVisitor.java index 68a24aad..3628e4b4 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/ImportGatheringVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/ImportGatheringVisitor.java @@ -15,7 +15,7 @@ * 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.command; +package net.strokkur.commands.internal.printer.source; import net.strokkur.commands.internal.codegen.CodeAnnotation; import net.strokkur.commands.internal.codegen.CodeClass; @@ -37,30 +37,41 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -public class ImportGatheringVisitor implements CodeVisitor> { +public class ImportGatheringVisitor implements CodeVisitor> { - private Set collectMethodInvokesImports(InvokesMethod invokes) { - if (invokes.isCtor()) { - return Objects.requireNonNull(invokes.type()).accept(this); - } + private Set collectMethodInvokesImports(InvokesMethod invokes) { + final Set chainedImports = collect(invokes.chained().stream() + .flatMap(chained -> chained.parameters().stream()) + .toList() + ); - if (invokes.isStatic() && invokes.type() != null) { + if (invokes.isCtor()) { return join( - invokes.type().accept(this), - collect(invokes.parameters()) + Objects.requireNonNull(invokes.type()).accept(this), + collect(invokes.parameters()), + chainedImports ); } - return collect(invokes.parameters()); + + 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) { + private Set join(Set... all) { return Stream.of(all) .flatMap(Collection::stream) .collect(Collectors.toSet()); } - private Set maybeAccess(@Nullable CodeVisitable visitable) { + private Set maybeAccess(@Nullable CodeVisitable visitable) { if (visitable != null) { return visitable.accept(this); } else { @@ -68,16 +79,16 @@ private Set maybeAccess(@Nullable CodeVisitable visitable) { } } - private Set collect(Collection collection) { + private Set collect(Collection collection) { return collection.stream() .flatMap(visitable -> visitable.accept(this).stream()) .collect(Collectors.toSet()); } @Override - public Set visitClass(CodeClass codeClass) { + public Set visitClass(CodeClass codeClass) { return join( - Set.of(codeClass.fullyQualifiedName()), + Set.of(CodeType.ofClass(codeClass)), collect(codeClass.methods()), collect(codeClass.fields()), collect(codeClass.annotations()) @@ -85,42 +96,48 @@ public Set visitClass(CodeClass codeClass) { } @Override - public Set visitMethod(CodeMethod codeMethod) { + public Set visitMethod(CodeMethod codeMethod) { return join( collect(codeMethod.parameters()), collect(codeMethod.throwsExceptions()), - codeMethod.returnType().accept(this) + codeMethod.returnType().accept(this), + collect(codeMethod.codeBlock().statements()) ); } @Override - public Set visitPackage(CodePackage codePackage) { + public Set visitPackage(CodePackage codePackage) { throw new IllegalStateException("This should not be called."); } @Override - public Set visitParameter(CodeParameter codeParameter) { + public Set visitParameter(CodeParameter codeParameter) { return codeParameter.type().accept(this); } @Override - public Set visitType(CodeType codeType) { + public Set visitType(CodeType codeType) { if (codeType instanceof CodeType.ArrayType array) { return array.inner().accept(this); } - return codeType instanceof CodeType.ClassType codeClass ? - Set.of(codeClass.fullyQualifiedName()) : - Set.of(); + 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().fullyQualifiedName()); + public Set visitAnnotation(CodeAnnotation codeAnnotation) { + return Set.of(codeAnnotation.type()); } @Override - public Set visitField(CodeField codeField) { + public Set visitField(CodeField codeField) { return join( codeField.initialiser() != null ? codeField.initialiser().accept(this) : Set.of(), collect(codeField.annotations()), @@ -129,7 +146,7 @@ public Set visitField(CodeField codeField) { } @Override - public Set visitExpression(CodeExpression codeExpression) { + public Set visitExpression(CodeExpression codeExpression) { return switch (codeExpression) { case CodeExpression.MethodInvocation(InvokesMethod invokes) -> collectMethodInvokesImports(invokes); @@ -154,7 +171,7 @@ public Set visitExpression(CodeExpression codeExpression) { } @Override - public Set visitStatement(CodeStatement codeStatement) { + public Set visitStatement(CodeStatement codeStatement) { return switch (codeStatement) { case CodeStatement.VariableDeclaration variableDeclaration -> { if (variableDeclaration.assignment() != null) { diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/JavaSourcePrintingVisitor.java similarity index 89% rename from processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java rename to processor/common/src/main/java/net/strokkur/commands/internal/printer/source/JavaSourcePrintingVisitor.java index aa7cf6d5..48eac1ff 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/command/JavaSourcePrintingVisitor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/JavaSourcePrintingVisitor.java @@ -15,7 +15,7 @@ * 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.command; +package net.strokkur.commands.internal.printer.source; import net.strokkur.commands.internal.codegen.CodeAnnotation; import net.strokkur.commands.internal.codegen.CodeClass; @@ -35,8 +35,8 @@ import java.util.function.Supplier; public class JavaSourcePrintingVisitor extends AbstractSourcePrintingVisitor { - public JavaSourcePrintingVisitor(Supplier javadocPrintingVisitor, String indentString) { - super(javadocPrintingVisitor, indentString); + public JavaSourcePrintingVisitor(Supplier javadocPrintingVisitor, String indent, String continuationIndent) { + super(javadocPrintingVisitor, indent, continuationIndent); } @Override @@ -60,7 +60,19 @@ private void printMethods(StringBuilder builder, List methods) { builder.append(" {\n"); appendIndented(() -> { - codeClass.fields().forEach(field -> appendNested(builder, field)); + 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)) @@ -124,7 +136,13 @@ public StringBuilder visitParameter(CodeParameter codeParameter) { @Override public StringBuilder visitType(CodeType codeType) { - return new StringBuilder(codeType.name()); + 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 @@ -139,6 +157,10 @@ public StringBuilder visitAnnotation(CodeAnnotation codeAnnotation) { 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()); 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..71f92eca --- /dev/null +++ b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/CodePackageEqualityTests.java @@ -0,0 +1,68 @@ +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/FullCommandClassBuilderTests.java b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/FullCommandClassBuilderTests.java index 72cbfdfd..4a46b570 100644 --- 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 @@ -19,9 +19,9 @@ import net.strokkur.commands.internal.codegen.builder.Builders; import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc; -import net.strokkur.commands.internal.printer.command.ImportGatheringVisitor; -import net.strokkur.commands.internal.printer.command.JavaSourcePrintingVisitor; 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 net.strokkur.commands.internal.util.Classes; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -37,7 +37,7 @@ void testFullClassExampleImports() { final CodeClass exampleClass = constructExampleCommandClass(); final ImportGatheringVisitor visitor = new ImportGatheringVisitor(); - final Set collectedImports = exampleClass.accept(visitor); + final Set collectedImports = exampleClass.accept(visitor); final String expected = """ com.example.ExampleCommandBrigadier @@ -48,6 +48,7 @@ void testFullClassExampleImports() { org.jspecify.annotations.Nullable"""; final String actual = collectedImports.stream() .sorted() + .map(CodeType::fullyQualifiedName) .collect(Collectors.joining("\n")); Assertions.assertEquals(expected, actual); @@ -56,7 +57,7 @@ void testFullClassExampleImports() { @Test void testFullClassExampleJavaPrinter() { final CodeClass exampleClass = constructExampleCommandClass(); - final JavaSourcePrintingVisitor visitor = new JavaSourcePrintingVisitor(JavaMarkdownJavadocVisitor::new, " "); + final JavaSourcePrintingVisitor visitor = new JavaSourcePrintingVisitor(JavaMarkdownJavadocVisitor::new, " ", " "); // language=java final String expected = """ @@ -66,7 +67,7 @@ void testFullClassExampleJavaPrinter() { @NullMarked public final class ExampleCommandBrigadier { public static final String NAME = "example"; - public static final String DESCRIPTION = null; + public static final @Nullable String DESCRIPTION = null; public static final String ALIASES = "example"; /// Registers your command. @@ -79,7 +80,7 @@ public static void create() { /// The constructor is inaccessible. /// - /// @throws java.lang.IllegalAccessException always + /// @throws IllegalAccessException always private ExampleCommandBrigadier() throws IllegalAccessException { throw new IllegalAccessException("This class cannot be instantiated."); } 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 index 592c9c69..5843e6af 100644 --- 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 @@ -19,7 +19,7 @@ import net.strokkur.commands.internal.codegen.builder.Builders; import net.strokkur.commands.internal.codegen.visitor.CodeVisitable; -import net.strokkur.commands.internal.printer.command.ImportGatheringVisitor; +import net.strokkur.commands.internal.printer.source.ImportGatheringVisitor; import org.junit.jupiter.api.Test; import java.util.Arrays; @@ -34,7 +34,8 @@ class ImportGatherTests { private void check(String expectedMultiline, CodeVisitable visitable) { - final Set imports = visitable.accept(new ImportGatheringVisitor()); + 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()); 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 index 0fb05499..3fd34119 100644 --- 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 @@ -19,8 +19,8 @@ import net.strokkur.commands.internal.codegen.builder.Builders; import net.strokkur.commands.internal.codegen.visitor.CodeVisitable; -import net.strokkur.commands.internal.printer.command.JavaSourcePrintingVisitor; 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; @@ -33,7 +33,7 @@ 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 JavaSourcePrintingVisitor visitor = new JavaSourcePrintingVisitor(JavaMarkdownJavadocVisitor::new, " ", " "); final String actual = visitable.accept(visitor).toString(); assertEquals(expected, actual); } @@ -41,7 +41,7 @@ private void check(String expected, CodeVisitable visitable) { @Test void testPackageThrows() { assertThrows(IllegalStateException.class, () -> { - CodePackage.of("com.example").accept(new JavaSourcePrintingVisitor(JavaMarkdownJavadocVisitor::new, " ")); + CodePackage.of("com.example").accept(new JavaSourcePrintingVisitor(JavaMarkdownJavadocVisitor::new, " ", " ")); }); } @@ -255,9 +255,9 @@ void methodInvocationFormat() { .setStatic() .setType(CodeType.STRING) .setNewline() - .chain("stream", true) - .chain("toLol", false) - .chain("toList", true) + .chain("stream", InvokesMethod.StyleConfig.NEWLINE) + .chain("toLol") + .chain("toList", InvokesMethod.StyleConfig.NEWLINE) .getAsStatement() ); } @@ -493,12 +493,12 @@ public void register(ProxyServer server, Object command$plugin) { Builders.methodInvocation("getCommandManager") .setInstanceVariable("server") .chain("metaBuilder", CodeExpression.variable("command")) - .chain("aliases", true, Builders.methodInvocation("toArray") + .chain("aliases", InvokesMethod.StyleConfig.NEWLINE, Builders.methodInvocation("toArray") .setInstanceVariable("ALIASES") .addParameter(CodeExpression.methodReference(CodeType.STRING_ARRAY, "new")) ) - .chain("plugin", true, CodeExpression.variable("command$plugin")) - .chain("build", true) + .chain("plugin", InvokesMethod.StyleConfig.NEWLINE, CodeExpression.variable("command$plugin")) + .chain("build", InvokesMethod.StyleConfig.NEWLINE) ), CodeStatement.blank(), 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 index a62ce59e..0c7f2c69 100644 --- 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 @@ -91,14 +91,14 @@ void testJavaMarkdownJavadocsCtor() { /// 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 java.lang.IllegalAccessException always"""; + /// @throws IllegalAccessException always"""; checkOutput(expected, ctorJd(), JavaMarkdownJavadocVisitor::new); } @Test void testNamedClassReference() { @Language("JAVA") final String expected = """ - /// This [environment][java.lang.ProcessEnvironment] does not help me + /// 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")), @@ -128,7 +128,7 @@ void testMiscCodeTypes() { /// This is text with a /// new line. /// - /// @see java.lang.String#concat(java.lang.String)"""; + /// @see String#concat(String)"""; checkOutput( expected, combineLines( 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 index fb6dbd7c..2a088a84 100644 --- 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 @@ -94,7 +94,7 @@ void testJavaStarJavadocsCtor() { * 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 java.lang.IllegalAccessException always + * @throws IllegalAccessException always */"""; checkOutput(expected, ctorJd(), JavaStarJavadocVisitor::new); } @@ -104,7 +104,7 @@ void testNamedClassReference() { // language=java final String expected = """ /** - * This {@link java.lang.ProcessEnvironment environment} does not help me + * This {@link ProcessEnvironment environment} does not help me * at all. */"""; checkOutput(expected, combineLines( 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..946491a6 --- /dev/null +++ b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/integration/FullVelocityIntegrationTest.java @@ -0,0 +1,346 @@ +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.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.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 java.util.stream.Collectors; + +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 expectedImportedClasses = 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 expectedCode = """ + /** + * 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 = builtClass.accept(importVisitor).stream() + .filter(gathered -> !CodePackage.isRedundantImport(builtClass.codePackage(), gathered.codePackage())) + .collect(Collectors.toSet()); + + // Check if imports match + final List sortedImports = imports.stream().map(CodeType::fullyQualifiedName).sorted().toList(); + final List sortedExpectedImports = expectedImportedClasses.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(expectedCode, 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) + + .setCodeBlock( + 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(".") + ) + )) + + .setCodeBlock(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(); + } + +} From 2fd7e9ed3f0d7cc2cfe91c2de10f481f9ef79cb7 Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Sun, 10 May 2026 22:03:32 +0200 Subject: [PATCH 13/16] Fixup spotless and checkstyle --- .checkstyle/suppressions.xml | 2 +- .../codegen/CodePackageEqualityTests.java | 17 +++++++++++++ .../FullVelocityIntegrationTest.java | 25 ++++++++++++++++--- 3 files changed, 39 insertions(+), 5 deletions(-) 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/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 index 71f92eca..515c5b43 100644 --- 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 @@ -1,3 +1,20 @@ +/* + * 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; 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 index 946491a6..781d62dd 100644 --- 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 @@ -1,3 +1,20 @@ +/* + * 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; @@ -44,14 +61,14 @@ class FullVelocityIntegrationTest { private static final CodeType.ClassType PROXY_INITIALIZE_EVENT = CodeType.ofClass("com.velocitypowered.api.event.proxy.ProxyInitializeEvent"); // - private static final Set expectedImportedClasses = Set.of( + 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 expectedCode = """ + private static final String EXPECTED_CODE = """ /** * A class holding the Brigadier source tree generated from * {@link TestCommand} using StrokkCommands. @@ -145,7 +162,7 @@ void testFullExample() { // Check if imports match final List sortedImports = imports.stream().map(CodeType::fullyQualifiedName).sorted().toList(); - final List sortedExpectedImports = expectedImportedClasses.stream() + final List sortedExpectedImports = EXPECTED_IMPORTED_CLASSES.stream() .map(CodeType::fullyQualifiedName).sorted().toList(); assertEquals(sortedExpectedImports.size(), sortedImports.size()); @@ -160,7 +177,7 @@ void testFullExample() { () -> new JavaStarJavadocVisitor(builtClass.codePackage(), imports), " ", " "); final StringBuilder result = builtClass.accept(sourceVisitor); - assertEquals(expectedCode, result.toString()); + assertEquals(EXPECTED_CODE, result.toString()); } private CodeClass buildClass() { From 5739e2093b1be272926d2d4bac893df217d6ef84 Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Sun, 10 May 2026 22:37:02 +0200 Subject: [PATCH 14/16] Minor reformat --- .../codegen/integration/FullVelocityIntegrationTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 index 781d62dd..5ff0e1f6 100644 --- 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 @@ -174,7 +174,8 @@ void testFullExample() { // Check generated class file final AbstractSourcePrintingVisitor sourceVisitor = new JavaSourcePrintingVisitor( - () -> new JavaStarJavadocVisitor(builtClass.codePackage(), imports), " ", " "); + () -> new JavaStarJavadocVisitor(builtClass.codePackage(), imports), " ", " " + ); final StringBuilder result = builtClass.accept(sourceVisitor); assertEquals(EXPECTED_CODE, result.toString()); From c79d633f5d757da7bbc4bba3662306875dbb77fc Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Mon, 11 May 2026 23:12:09 +0200 Subject: [PATCH 15/16] Hookup new code gen feature into common class builder --- .../common/src/main/java/module-info.java | 8 + .../internal/StrokkCommandsProcessor.java | 15 +- .../arguments/BrigadierArgumentConverter.java | 84 ++-- .../arguments/BrigadierArgumentType.java | 35 +- .../internal/codegen/CodeAnnotation.java | 5 + .../commands/internal/codegen/CodeClass.java | 26 +- .../internal/codegen/CodeExpression.java | 16 + .../internal/codegen/CodeStatement.java | 9 +- .../commands/internal/codegen/CodeType.java | 8 +- .../internal/codegen/InvokesMethod.java | 2 +- .../codegen/adapter/CodeTypeAdapter.java | 21 + .../internal/codegen/as/AsCodeType.java | 7 + .../commands/internal/codegen/as/AsField.java | 7 + .../internal/codegen/builder/Builders.java | 5 + .../codegen/builder/ClassBuilder.java | 7 +- .../codegen/builder/FieldAccessBuilder.java | 5 + .../codegen/builder/MethodBuilder.java | 47 ++- .../builder/MethodInvocationBuilder.java | 18 +- .../internal/codegen/javadoc/CodeJavadoc.java | 5 + .../intermediate/access/ExecuteAccess.java | 6 +- .../intermediate/access/FieldAccessImpl.java | 7 + .../access/InstanceAccessImpl.java | 7 + .../intermediate/attributes/Attributable.java | 4 + .../executable/DefaultExecutable.java | 35 +- .../CombinedRequirementProvider.java | 7 + .../intermediate/registrable/FieldImpl.java | 20 + .../registrable/InstanceImpl.java | 11 + .../intermediate/registrable/MethodImpl.java | 39 +- .../registrable/RequirementProvider.java | 3 + .../registrable/SuggestionProvider.java | 3 + .../internal/printer/AbstractPrinter.java | 111 ------ .../CommonBrigadierStatementBuilder.java | 201 ++++++++++ .../internal/printer/CommonClassBuilder.java | 239 ++++++++++++ .../printer/CommonCommandTreePrinter.java | 266 ------------- .../internal/printer/CommonImportPrinter.java | 181 --------- .../printer/CommonInstanceFieldPrinter.java | 259 ------------ .../internal/printer/CommonTreePrinter.java | 367 ------------------ .../commands/internal/printer/Printable.java | 42 -- .../internal/printer/PrintedAccessPath.java | 70 ++++ .../source/AbstractSourcePrintingVisitor.java | 4 +- .../source/ImportGatheringVisitor.java | 6 + .../source/JavaSourcePrintingVisitor.java | 3 + .../commands/internal/util/Classes.java | 79 +++- .../codegen/FullCommandClassBuilderTests.java | 11 +- .../internal/codegen/JavaCodeGenTests.java | 10 +- .../FullVelocityIntegrationTest.java | 10 +- ...reePrinter.java => PaperClassBuilder.java} | 6 +- .../paper/PaperCommandTreePrinter.java | 8 +- .../paper/PaperStrokkCommandsProcessor.java | 2 +- ...Printer.java => VelocityClassBuilder.java} | 6 +- .../velocity/VelocityCommandTreePrinter.java | 8 +- .../VelocityStrokkCommandsProcessor.java | 2 +- 52 files changed, 955 insertions(+), 1408 deletions(-) create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/adapter/CodeTypeAdapter.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/as/AsCodeType.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/codegen/as/AsField.java delete mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/printer/AbstractPrinter.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonBrigadierStatementBuilder.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonClassBuilder.java delete mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonCommandTreePrinter.java delete mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonImportPrinter.java delete mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonInstanceFieldPrinter.java delete mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonTreePrinter.java delete mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/printer/Printable.java create mode 100644 processor/common/src/main/java/net/strokkur/commands/internal/printer/PrintedAccessPath.java rename processor/paper/src/main/java/net/strokkur/commands/internal/paper/{PaperTreePrinter.java => PaperClassBuilder.java} (96%) rename processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/{VelocityTreePrinter.java => VelocityClassBuilder.java} (96%) diff --git a/processor/common/src/main/java/module-info.java b/processor/common/src/main/java/module-info.java index 3386dc42..2938421c 100644 --- a/processor/common/src/main/java/module-info.java +++ b/processor/common/src/main/java/module-info.java @@ -10,10 +10,16 @@ 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.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; @@ -22,6 +28,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..9dfbb341 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 @@ -36,7 +36,7 @@ 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.util.CommandInformation; import net.strokkur.commands.internal.util.MessagerWrapper; import net.strokkur.commands.meta.StrokkCommandsDebug; @@ -50,7 +50,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,7 +80,7 @@ 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); @@ -192,12 +192,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 index 4ad33d39..c9bbf0ab 100644 --- 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 @@ -19,8 +19,13 @@ 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); } 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 index 3b09637c..ac932828 100644 --- 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 @@ -50,11 +50,27 @@ private CodeClass( public static CodeClass simple(String string) { final String[] split = string.split("\\."); - return new CodeClass( - new CodePackage(Arrays.copyOf(split, split.length - 1)), - null, - split[split.length - 1] - ); + 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 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 index 2bf0934e..aee072d2 100644 --- 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 @@ -43,6 +43,10 @@ static Variable variable(String name) { return new Variable(name); } + static NumberConstant number(Number number) { + return new NumberConstant(number); + } + static MethodReference methodReference(CodeType type, String methodName) { return new MethodReference(type, methodName); } @@ -107,6 +111,18 @@ public String value() { } } + final class NumberConstant implements CodeExpression { + private final Number value; + + private NumberConstant(Number value) { + this.value = value; + } + + public Number value() { + return value; + } + } + final class Null implements CodeExpression { private static final Null INSTANCE = new Null(); 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 index 407cdc91..733a1dcd 100644 --- 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 @@ -18,6 +18,7 @@ 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; @@ -29,12 +30,12 @@ import java.util.List; public sealed interface CodeStatement extends CodeVisitable, AsStatement { - static VariableDeclaration variableDeclaration(CodeType type, String name, @Nullable AsExpression assignment) { - return new VariableDeclaration(type, name, assignment == null ? null : assignment.getAsExpression(), false); + 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(CodeType type, String name, @Nullable AsExpression assignment) { - return new VariableDeclaration(type, name, assignment == null ? null : assignment.getAsExpression(), true); + 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) { 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 index 73313741..97a1ab8f 100644 --- 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 @@ -17,6 +17,7 @@ */ 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; @@ -116,7 +117,7 @@ private GenericType(String name) { } } - class ClassType implements CodeType { + class ClassType implements CodeType, AsCodeType { private final CodeClass codeClass; private final @Nullable List types; @@ -125,6 +126,11 @@ private ClassType(CodeClass codeClass, @Nullable List types) { this.types = types; } + @Override + public CodeType.ClassType getAsCodeType() { + return this; + } + @Contract(pure = true) public @Nullable List types() { return types == null ? null : List.copyOf(types); 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 index 07dbb51c..fe6d5a61 100644 --- 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 @@ -26,7 +26,7 @@ public record InvokesMethod( String methodName, CodeType.@Nullable ClassType type, List parameters, - @Nullable String instanceVariable, + @Nullable CodeExpression instanceSource, boolean isStatic, boolean isCtor, StyleConfig style, 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..f19a7747 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/adapter/CodeTypeAdapter.java @@ -0,0 +1,21 @@ +package net.strokkur.commands.internal.codegen.adapter; + +import net.strokkur.commands.internal.abstraction.SourceType; +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.ClassType from(SourceType sourceClass) { + return CodeType.ofClass(CodeClass.nested( + CodePackage.of(sourceClass.getPackageName()), + List.of(sourceClass.getSourceName().split("\\.")) + )); + } + + private CodeTypeAdapter() { + } +} 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..56c67831 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/as/AsCodeType.java @@ -0,0 +1,7 @@ +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/AsField.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/as/AsField.java new file mode 100644 index 00000000..3793ef6f --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/as/AsField.java @@ -0,0 +1,7 @@ +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/builder/Builders.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/builder/Builders.java index cae8e5a2..aa285d25 100644 --- 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 @@ -22,6 +22,7 @@ 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; @@ -70,6 +71,10 @@ public static MethodInvocationBuilder methodInvocation(ConvertableTo 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() 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 index 7e05b9b5..b1ad541b 100644 --- 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 @@ -60,8 +60,13 @@ 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 = modifiers; + this.modifiers = new HashSet<>(modifiers); return this; } 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 index 7ac09cca..ac0042f0 100644 --- 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 @@ -19,6 +19,7 @@ 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; @@ -44,6 +45,10 @@ public FieldAccessBuilder setSource(AsExpression source) { return this; } + public FieldAccessBuilder setStatic(AsCodeType type) { + return setStatic(type.getAsCodeType()); + } + public FieldAccessBuilder setStatic(CodeType.ClassType type) { this.isStatic = true; this.type = type; 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 index 15000f73..aa53896b 100644 --- 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 @@ -22,14 +22,17 @@ 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; @@ -41,10 +44,10 @@ public class MethodBuilder implements ConvertableTo { private final String name; private CodeType returnType = CodeType.VOID; - private List parameters = new ArrayList<>(); + private final List parameters = new ArrayList<>(); private Set modifiers = new HashSet<>(); private @Nullable CodeJavadoc javadoc = null; - private CodeBlock codeBlock = new CodeBlock(List.of()); + private List methodStatements = new ArrayList<>(); private List throwsExceptions = List.of(); MethodBuilder(String name) { @@ -70,8 +73,13 @@ 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 = modifiers; + this.modifiers = new HashSet<>(modifiers); return this; } @@ -80,20 +88,33 @@ public MethodBuilder setJavadoc(@Nullable CodeJavadoc javadoc) { return this; } - public MethodBuilder setCodeBlock(AsStatement... statements) { - return setCodeBlock(List.of(statements)); + public MethodBuilder addMethodStatements(AsStatement... statements) { + this.methodStatements.addAll(Arrays.stream(statements) + .map(AsStatement::getAsStatement) + .toList()); + return this; } - public MethodBuilder setCodeBlock(List statements) { - this.codeBlock = new CodeBlock(statements.stream() + 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() - ); + .toList()); + return this; + } + + public MethodBuilder setThrowsExceptions(AsCodeType... throwsExceptions) { + this.throwsExceptions = Arrays.stream(throwsExceptions) + .map(AsCodeType::getAsCodeType) + .toList(); return this; } - public MethodBuilder setThrowsExceptions(List throwsExceptions) { - this.throwsExceptions = throwsExceptions; + public MethodBuilder setThrowsExceptions(CodeType.ClassType... throwsExceptions) { + this.throwsExceptions = List.of(throwsExceptions); return this; } @@ -106,7 +127,7 @@ public CodeMethod build() { List.copyOf(parameters), Set.copyOf(modifiers), javadoc, - codeBlock, + new CodeBlock(List.copyOf(methodStatements)), List.copyOf(throwsExceptions) ); } @@ -118,7 +139,7 @@ public CodeConstructor buildConstructor() { List.copyOf(parameters), Set.copyOf(modifiers), javadoc, - codeBlock, + new CodeBlock(List.copyOf(methodStatements)), List.copyOf(throwsExceptions) ); } 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 index 7936a90d..ded36210 100644 --- 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 @@ -21,6 +21,7 @@ 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; @@ -33,7 +34,7 @@ public class MethodInvocationBuilder implements AsExpression, AsStatement { private final String methodName; private CodeType.@Nullable ClassType type = null; private final List parameters = new ArrayList<>(); - private @Nullable String instanceVariable = null; + private @Nullable CodeExpression instanceSource = null; private boolean newline = false; private boolean isStatic = false; private boolean isCtor = false; @@ -55,8 +56,13 @@ public MethodInvocationBuilder addParameter(AsExpression expression) { return this; } - public MethodInvocationBuilder setInstanceVariable(@Nullable String instanceVariable) { - this.instanceVariable = instanceVariable; + public MethodInvocationBuilder setInstanceSource(AsExpression instanceSource) { + this.instanceSource = instanceSource.getAsExpression(); + return this; + } + + public MethodInvocationBuilder setInstanceVariable(String instanceVariable) { + this.instanceSource = CodeExpression.variable(instanceVariable); return this; } @@ -65,6 +71,10 @@ public MethodInvocationBuilder setNewline() { return this; } + public MethodInvocationBuilder setStatic(AsCodeType type) { + return setStatic(type.getAsCodeType()); + } + public MethodInvocationBuilder setStatic(CodeType.ClassType type) { return setType(type).setStatic(); } @@ -108,7 +118,7 @@ public InvokesMethod build() { return new InvokesMethod(methodName, type, List.copyOf(parameters), - instanceVariable, + instanceSource, isStatic, isCtor, new InvokesMethod.StyleConfig(newline, multilineParameters, newlineClosingBrace), 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 index 03928480..2eb748f1 100644 --- 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 @@ -20,6 +20,7 @@ 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; @@ -62,6 +63,10 @@ static CodeJavadoc see(ConvertableTo method, @Nullable String descri 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); } 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..d8b47dea 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..9e8fd50d 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.ClassType 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/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/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..5c1fb2e2 --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonBrigadierStatementBuilder.java @@ -0,0 +1,201 @@ +package net.strokkur.commands.internal.printer; + +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.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().stream() + .map(CommandArgument.class::cast) + .forEach(arg -> builder.addParameter(getArgumentValueExpr(arg))); + + 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 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..9a331bdc --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonClassBuilder.java @@ -0,0 +1,239 @@ +package net.strokkur.commands.internal.printer; + +import net.strokkur.commands.internal.BuildConstants; +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.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.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.stream.Collectors; + +public abstract class CommonClassBuilder { + private final CommandNode rootNode; + private final C commandInformation; + private final CommonBrigadierStatementBuilder statementBuilder; + private final BiFunction, AbstractSourcePrintingVisitor> sourceVisitor; + + private final CodeType.ClassType sourceType; + private 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(); + 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(), + Builders.ctorInvocation(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(); + } + + /// 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); + } + 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), + 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"), + CodeJavadoc.see(registerMethod, "registering the command") + ); + } + + /// 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(true).stream().sorted() + .forEach(type -> builder.append("import ").append(type.fullyQualifiedName()).append(";\n")); + + if (split.get(false).isEmpty()) { + builder.append("\n"); + split.get(false).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/Printable.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/Printable.java deleted file mode 100644 index c6bebd89..00000000 --- a/processor/common/src/main/java/net/strokkur/commands/internal/printer/Printable.java +++ /dev/null @@ -1,42 +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 java.io.IOException; - -interface Printable { - - void incrementIndent(); - - void decrementIndent(); - - 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); - } - - void println() throws IOException; - - void printBlock(String block, Object... format) throws IOException; -} 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..739dcf0e --- /dev/null +++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/PrintedAccessPath.java @@ -0,0 +1,70 @@ +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.ClassType 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/source/AbstractSourcePrintingVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/AbstractSourcePrintingVisitor.java index 2a2db566..8cd08af2 100644 --- 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 @@ -139,8 +139,8 @@ protected void appendMethodInvocation(StringBuilder builder, InvokesMethod metho builder.append("new "); } else { final String source; - if (method.instanceVariable() != null) { - source = method.instanceVariable(); + if (method.instanceSource() != null) { + source = method.instanceSource().accept(this).toString(); } else if (method.isStatic() && method.type() != null) { source = method.type().name(); } else { 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 index 3628e4b4..8701c9b8 100644 --- 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 @@ -39,6 +39,12 @@ 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()) 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 index 48eac1ff..fd22d328 100644 --- 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 @@ -185,6 +185,9 @@ public StringBuilder visitExpression(CodeExpression codeExpression) { 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()); } 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/test/java/net/strokkur/commands/internal/codegen/FullCommandClassBuilderTests.java b/processor/common/src/test/java/net/strokkur/commands/internal/codegen/FullCommandClassBuilderTests.java index 4a46b570..312405af 100644 --- 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 @@ -22,7 +22,6 @@ 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 net.strokkur.commands.internal.util.Classes; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -96,7 +95,7 @@ private CodeClass constructExampleCommandClass() { final CodeClass current = CodeClass.simple("com.example.ExampleCommandBrigadier"); return Builders.classBuilder(current.fullyQualifiedName()) - .setAnnotations(List.of(CodeAnnotation.of(CodeType.ofClass(Classes.NULL_MARKED)))) + .setAnnotations(List.of(CodeAnnotation.NULL_MARKED)) .setModifiers(Set.of(Modifiers.PUBLIC, Modifiers.FINAL)) .setJavadoc(CodeJavadoc.combineLines( CodeJavadoc.text("A very cool class."), @@ -110,7 +109,7 @@ private CodeClass constructExampleCommandClass() { .addField(Builders.field("DESCRIPTION", CodeType.ofClass(CodeClass.STRING)) .setModifiers(Set.of(Modifiers.PUBLIC, Modifiers.STATIC, Modifiers.FINAL)) .setInitialiser(CodeExpression.nullExpr()) - .addAnnotation(CodeAnnotation.of(CodeType.ofClass(Classes.NULLABLE))) + .addAnnotation(CodeAnnotation.NULLABLE) ) .addField(Builders.field("ALIASES", CodeType.ofClass(CodeClass.STRING)) .setModifiers(Set.of(Modifiers.PUBLIC, Modifiers.STATIC, Modifiers.FINAL)) @@ -120,7 +119,7 @@ private CodeClass constructExampleCommandClass() { .setJavadoc(CodeJavadoc.text("Registers your command.")) .setModifiers(Set.of(Modifiers.PUBLIC, Modifiers.STATIC)) .addParameter(CodeType.ofClass(commands), "commands") - .setCodeBlock(List.of( + .setMethodStatements(List.of( Builders.methodInvocation("register") .addParameter(Builders.methodInvocation("create")) .addParameter(CodeExpression.variable("DESCRIPTION")) @@ -133,14 +132,14 @@ private CodeClass constructExampleCommandClass() { .setModifiers(Set.of(Modifiers.PUBLIC, Modifiers.STATIC)) ) .addMethod(Builders.method(current) - .setThrowsExceptions(List.of(CodeType.ofClass("java.lang.IllegalAccessException"))) + .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") )) - .setCodeBlock(List.of( + .setMethodStatements(List.of( CodeStatement.throwStatement(Builders.ctorInvocation(CodeType.ofClass("java.lang.IllegalAccessException")) .addParameter(CodeExpression.string("This class cannot be instantiated.")) ) 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 index 3fd34119..d7b2b379 100644 --- 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 @@ -159,7 +159,7 @@ void method() { } """; check(expectedWithStatements, Builders.method(EXAMPLE_CLASS, "method") - .setCodeBlock(List.of( + .setMethodStatements(List.of( Builders.methodInvocation(Builders.method(EXAMPLE_CLASS, "otherMethod")) .addParameter(CodeExpression.variable("STATIC_VALUE")) .addParameter(CodeExpression.string("Now")) @@ -173,11 +173,11 @@ void method() throws NullPointerException, SQLException { } """; check(expectedWithThrows, Builders.method(EXAMPLE_CLASS, "method") - .setThrowsExceptions(List.of( + .setThrowsExceptions( CodeType.ofClass("java.lang.NullPointerException"), CodeType.ofClass("java.sql.SQLException") - )) - .setCodeBlock(List.of( + ) + .setMethodStatements(List.of( CodeStatement.throwStatement(Builders.ctorInvocation(CodeType.ofClass("java.sql.SQLException")) .addParameter(CodeExpression.string("No database present :(")) ) @@ -477,7 +477,7 @@ public void register(ProxyServer server, Object command$plugin) { .setModifiers(Set.of(Modifiers.PUBLIC)) .addParameter(CodeType.ofClass("velocity.ProxyServer"), "server") .addParameter(CodeType.ofClass("java.lang.Object"), "command$plugin") - .setCodeBlock(List.of( + .setMethodStatements(List.of( // BrigadierCommand command CodeStatement.variableDeclarationFinal( CodeType.ofClass("velocity.BrigadierCommand"), 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 index 5ff0e1f6..7561071a 100644 --- 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 @@ -20,7 +20,6 @@ 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.CodePackage; import net.strokkur.commands.internal.codegen.CodeStatement; import net.strokkur.commands.internal.codegen.CodeType; import net.strokkur.commands.internal.codegen.InvokesMethod; @@ -37,7 +36,6 @@ import java.util.List; import java.util.Set; -import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -156,9 +154,7 @@ void testFullExample() { final CodeClass builtClass = buildClass(); final ImportGatheringVisitor importVisitor = new ImportGatheringVisitor(); - final Set imports = builtClass.accept(importVisitor).stream() - .filter(gathered -> !CodePackage.isRedundantImport(builtClass.codePackage(), gathered.codePackage())) - .collect(Collectors.toSet()); + final Set imports = importVisitor.collectFilteredImports(builtClass); // Check if imports match final List sortedImports = imports.stream().map(CodeType::fullyQualifiedName).sorted().toList(); @@ -250,7 +246,7 @@ void onProxyInitialize(final ProxyInitializeEvent event) { )) .setModifiers(Modifiers.PUBLIC) - .setCodeBlock( + .setMethodStatements( CodeStatement.variableDeclarationFinal(BRIGADIER_COMMAND, "command", Builders.ctorInvocation(BRIGADIER_COMMAND) .addParameter(Builders.methodInvocation("create"))), CodeStatement.variableDeclarationFinal(COMMAND_META, "meta", Builders.methodInvocation("getCommandManager") @@ -285,7 +281,7 @@ void onProxyInitialize(final ProxyInitializeEvent event) { ) )) - .setCodeBlock(CodeStatement.returnStatement( + .setMethodStatements(CodeStatement.returnStatement( Builders.methodInvocation("literalArgumentBuilder") .setStatic(BRIGADIER_COMMAND) .addParameter(CodeExpression.variable("NAME")) 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/VelocityTreePrinter.java b/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityClassBuilder.java similarity index 96% rename from processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityTreePrinter.java rename to processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityClassBuilder.java index 81e211c8..21b41bd0 100644 --- a/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityTreePrinter.java +++ b/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityClassBuilder.java @@ -23,8 +23,8 @@ 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.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.internal.velocity.util.SenderType; import net.strokkur.commands.internal.velocity.util.VelocityAttributeKeys; @@ -37,8 +37,8 @@ import java.util.Objects; import java.util.Set; -final class VelocityTreePrinter extends CommonTreePrinter { - VelocityTreePrinter(CommonCommandTreePrinter printer) { +final class VelocityClassBuilder extends CommonClassBuilder { + VelocityClassBuilder(CommonCommandTreePrinter printer) { super(printer); } 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 index 13f9a4ae..c13849a2 100644 --- 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 @@ -21,10 +21,10 @@ 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.CommonClassBuilder; 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; @@ -51,7 +51,7 @@ final class VelocityCommandTreePrinter extends CommonCommandTreePrinter aliases = Optional.ofNullable(getCommandInformation().aliases()); if (aliases.isPresent()) { final String aliasesVarargs = String.join(", ", Arrays.stream(aliases.get()) @@ -67,8 +67,8 @@ protected CommonImportPrinter createImportPrinter() { } @Override - protected CommonTreePrinter createTreePrinter() { - return new VelocityTreePrinter(this); + protected CommonClassBuilder createTreePrinter() { + return new VelocityClassBuilder(this); } @Override 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..ac25a805 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 @@ -62,7 +62,7 @@ protected CommonTreePostProcessor createPostProcessor(MessagerWrapper messager) } @Override - protected CommonCommandTreePrinter createPrinter(CommandNode node, VelocityCommandInformation commandInformation) { + protected CommonCommandTreePrinter createBuilder(CommandNode node, VelocityCommandInformation commandInformation) { return new VelocityCommandTreePrinter(0, null, node, commandInformation, this.processingEnv, getPlatformUtils()); } From 470b870a1caf712eb2e5ac35f87fc5aeeddb2408 Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Tue, 12 May 2026 01:19:54 +0200 Subject: [PATCH 16/16] Implement processor for Velocity --- gradle/libs.versions.toml | 2 +- .../common/src/main/java/module-info.java | 1 + .../internal/StrokkCommandsProcessor.java | 22 +++ .../internal/codegen/CodeConstructor.java | 3 +- .../internal/codegen/CodeExpression.java | 20 +- .../commands/internal/codegen/CodeMethod.java | 7 + .../commands/internal/codegen/CodeType.java | 13 +- .../codegen/adapter/CodeTypeAdapter.java | 36 +++- .../internal/codegen/as/AsCodeType.java | 17 ++ .../commands/internal/codegen/as/AsField.java | 17 ++ .../codegen/builder/MethodBuilder.java | 11 +- .../intermediate/access/ExecuteAccess.java | 2 +- .../intermediate/access/FieldAccessImpl.java | 2 +- .../registrable/SuggestionsRegistry.java | 6 +- .../CommonBrigadierStatementBuilder.java | 29 ++- .../internal/printer/CommonClassBuilder.java | 95 +++++++-- .../internal/printer/PrintedAccessPath.java | 19 +- .../VelocityBrigadierStatementBuilder.java | 100 ++++++++++ .../velocity/VelocityClassBuilder.java | 184 ++++++++++-------- .../velocity/VelocityCommandTreePrinter.java | 166 ---------------- .../velocity/VelocityImportPrinter.java | 75 ------- .../VelocityInstanceFieldPrinter.java | 37 ---- .../velocity/VelocityPlatformUtils.java | 18 +- .../VelocityStrokkCommandsProcessor.java | 9 +- .../internal/velocity/util/SenderType.java | 28 ++- .../velocity/util/VelocityClasses.java | 41 +++- test-plugin/velocity/build.gradle.kts | 4 + .../velocity/TestPluginVelocity.java | 6 +- 28 files changed, 547 insertions(+), 423 deletions(-) create mode 100644 processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityBrigadierStatementBuilder.java delete mode 100644 processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityCommandTreePrinter.java delete mode 100644 processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityImportPrinter.java delete mode 100644 processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityInstanceFieldPrinter.java diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dfd39e2e..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" diff --git a/processor/common/src/main/java/module-info.java b/processor/common/src/main/java/module-info.java index 2938421c..ec355bd3 100644 --- a/processor/common/src/main/java/module-info.java +++ b/processor/common/src/main/java/module-info.java @@ -17,6 +17,7 @@ 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; 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 9dfbb341..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; @@ -37,6 +39,9 @@ import net.strokkur.commands.internal.parsing.DefaultExecutesTransform; import net.strokkur.commands.internal.parsing.ExecutesTransform; 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; @@ -86,6 +91,23 @@ protected void init() { 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) { diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeConstructor.java b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeConstructor.java index 84511ae2..9c7c110b 100644 --- a/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeConstructor.java +++ b/processor/common/src/main/java/net/strokkur/commands/internal/codegen/CodeConstructor.java @@ -30,8 +30,9 @@ public CodeConstructor( Set modifiers, @Nullable CodeJavadoc javadoc, CodeBlock codeBlock, + List generics, List throwsExceptions ) { - super(declaredClass, CodeType.ofClass(declaredClass), declaredClass.name(), parameters, modifiers, javadoc, codeBlock, 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 index aee072d2..4e16a86b 100644 --- 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 @@ -47,6 +47,10 @@ 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); } @@ -80,7 +84,7 @@ default CodeExpression getAsExpression() { sealed abstract class BooleanExpression> implements CodeExpression, AsBooleanExpression - permits Instanceof { + permits Instanceof, BooleanConstant { private final boolean inverted; public BooleanExpression(boolean inverted) { @@ -123,6 +127,20 @@ public Number 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(); 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 index a9d3ab62..4a188f10 100644 --- 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 @@ -34,6 +34,7 @@ public class CodeMethod implements CodeVisitable, ConvertableTo.Self private final Set modifiers; private final @Nullable CodeJavadoc javadoc; private final CodeBlock codeBlock; + private final List generics; private final List throwsExceptions; public CodeMethod( @@ -44,6 +45,7 @@ public CodeMethod( Set modifiers, @Nullable CodeJavadoc javadoc, CodeBlock codeBlock, + List generics, List throwsExceptions ) { this.declaredClass = declaredClass; @@ -53,6 +55,7 @@ public CodeMethod( this.modifiers = modifiers; this.javadoc = javadoc; this.codeBlock = codeBlock; + this.generics = generics; this.throwsExceptions = throwsExceptions; } @@ -93,4 +96,8 @@ public CodeBlock codeBlock() { public List throwsExceptions() { return throwsExceptions; } + + public List generics() { + return generics; + } } 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 index 97a1ab8f..48e97f21 100644 --- 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 @@ -43,8 +43,8 @@ public interface CodeType extends CodeVisitable, Comparable { ClassType LIST = CodeType.ofClass(CodeClass.LIST); ClassType LIST_STRING = CodeType.ofClassTyped(CodeClass.LIST, STRING); - static GenericType generic(String name) { - return new GenericType(name); + static GenericType generic(String name, @Nullable String definition) { + return new GenericType(name, definition); } static ClassType ofClass(CodeClass codeClass) { @@ -112,8 +112,15 @@ private PrimitiveType(String name) { } class GenericType extends SimpleType { - private GenericType(String name) { + private final @Nullable String definition; + + private GenericType(String name, @Nullable String definition) { super(name); + this.definition = definition; + } + + public @Nullable String definition() { + return definition; } } 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 index f19a7747..c53e713c 100644 --- 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 @@ -1,6 +1,25 @@ +/* + * 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; @@ -9,10 +28,21 @@ public final class CodeTypeAdapter { - public static CodeType.ClassType from(SourceType sourceClass) { + 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(sourceClass.getPackageName()), - List.of(sourceClass.getSourceName().split("\\.")) + CodePackage.of(sourceType.getPackageName()), + List.of(sourceType.getSourceName().split("\\.")) )); } 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 index 56c67831..fe03377c 100644 --- 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 @@ -1,3 +1,20 @@ +/* + * 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; 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 index 3793ef6f..31443e1a 100644 --- 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 @@ -1,3 +1,20 @@ +/* + * 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; 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 index aa53896b..2bf8d46f 100644 --- 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 @@ -48,6 +48,7 @@ public class MethodBuilder implements ConvertableTo { 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) { @@ -59,6 +60,11 @@ public MethodBuilder setDeclaringClass(CodeClass 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; @@ -106,7 +112,8 @@ public MethodBuilder setMethodStatements(List statements) return this; } - public MethodBuilder setThrowsExceptions(AsCodeType... throwsExceptions) { + @SafeVarargs + public final MethodBuilder setThrowsExceptions(AsCodeType... throwsExceptions) { this.throwsExceptions = Arrays.stream(throwsExceptions) .map(AsCodeType::getAsCodeType) .toList(); @@ -128,6 +135,7 @@ public CodeMethod build() { Set.copyOf(modifiers), javadoc, new CodeBlock(List.copyOf(methodStatements)), + List.copyOf(generics), List.copyOf(throwsExceptions) ); } @@ -140,6 +148,7 @@ public CodeConstructor buildConstructor() { Set.copyOf(modifiers), javadoc, new CodeBlock(List.copyOf(methodStatements)), + List.copyOf(generics), List.copyOf(throwsExceptions) ); } 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 d8b47dea..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 @@ -24,7 +24,7 @@ import net.strokkur.commands.internal.codegen.as.AsCodeType; public sealed interface ExecuteAccess - extends AsCodeType + extends AsCodeType permits ExecuteAccessImpl, FieldAccess, InstanceAccess { static FieldAccess of(SourceField 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 9e8fd50d..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 @@ -28,7 +28,7 @@ final class FieldAccessImpl extends ExecuteAccessImpl implements Fi } @Override - public CodeType.ClassType getAsCodeType() { + public CodeType getAsCodeType() { return CodeTypeAdapter.from(element.getType()); } 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/CommonBrigadierStatementBuilder.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/CommonBrigadierStatementBuilder.java index 5c1fb2e2..0205f618 100644 --- 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 @@ -1,5 +1,23 @@ +/* + * 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; @@ -16,6 +34,7 @@ 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; @@ -125,9 +144,11 @@ private AsStatement createCallStatement(AsExpression source, Executable executab final MethodInvocationBuilder builder = Builders.methodInvocation(executable.executesMethod().getName()) .setInstanceSource(source); - executable.parameterArguments().stream() - .map(CommandArgument.class::cast) - .forEach(arg -> builder.addParameter(getArgumentValueExpr(arg))); + executable.parameterArguments() + .forEach(arg -> builder.addParameter(switch (arg) { + case CommandArgument argument -> getArgumentValueExpr(argument); + case SourceParameterType(SourceVariable parameter) -> getParameterValueExpr(parameter); + })); return builder; } @@ -141,6 +162,8 @@ private AsExpression getArgumentValueExpr(CommandArgument argument) { }; } + protected abstract AsExpression getParameterValueExpr(SourceVariable parameter); + protected void appendNode(MethodInvocationBuilder builder, CommandNode node) { switch (node.argument()) { case LiteralCommandArgument literal -> { 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 index 9a331bdc..33f04c0c 100644 --- 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 @@ -1,6 +1,27 @@ +/* + * 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; @@ -9,10 +30,12 @@ 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; @@ -27,16 +50,17 @@ 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; - private final C commandInformation; + protected final C commandInformation; private final CommonBrigadierStatementBuilder statementBuilder; private final BiFunction, AbstractSourcePrintingVisitor> sourceVisitor; - private final CodeType.ClassType sourceType; - private final CodeType.ClassType selfType; + protected final CodeType.ClassType sourceType; + protected final CodeType.ClassType selfType; public CommonClassBuilder( CommandNode rootNode, @@ -58,6 +82,8 @@ 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"); @@ -106,7 +132,7 @@ private CodeClass createClass() { createMethod.addMethodStatements(CodeStatement.variableDeclarationFinal( path.access().getLast(), path.name(), - Builders.ctorInvocation(path.access().getLast().getAsCodeType()) + createInstanceConstructor((CodeType.ClassType) path.access().getLast().getAsCodeType()) )); }); if (!required.isEmpty()) { @@ -141,6 +167,48 @@ private CodeClass createClass() { 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. @@ -151,6 +219,10 @@ protected MethodBuilder getCreateMethodBuilder() { if (!commandInformation.useInjection()) { builder.addModifiers(Modifiers.STATIC); } + + // Propagate constructor parameters + addConstructorParametersTo(builder, f -> true); + return builder; } @@ -187,7 +259,7 @@ protected void applyCreateMethodJavadoc(MethodBuilder createMethod, ConvertableT CodeJavadoc.text(". You can either retrieve the unregistered node with this method")), CodeJavadoc.combine( CodeJavadoc.text("or register it directly with "), - CodeJavadoc.methodReference(registerMethod), + CodeJavadoc.methodReference(registerMethod, true), CodeJavadoc.text(".")) )); } @@ -208,8 +280,8 @@ private CodeJavadoc getClassJavadoc(ConvertableTo createMethod, Conv CodeJavadoc.blank(), CodeJavadoc.author("Strokkur24 - StrokkCommands"), CodeJavadoc.version(BuildConstants.VERSION), - CodeJavadoc.see(createMethod, "creating the command"), - CodeJavadoc.see(registerMethod, "registering the command") + CodeJavadoc.see(createMethod, "creating the command", true), + CodeJavadoc.see(registerMethod, "registering the command", true) ); } @@ -225,15 +297,16 @@ private Set gatherAndAppendImports(StringBuilder builder, Co final Map> split = imports.stream() .collect(Collectors.partitioningBy(type -> type.codePackage().path().startsWith("java"))); - split.get(true).stream().sorted() + split.get(false).stream().sorted() .forEach(type -> builder.append("import ").append(type.fullyQualifiedName()).append(";\n")); - if (split.get(false).isEmpty()) { + if (!split.get(false).isEmpty() && !split.get(true).isEmpty()) { builder.append("\n"); - split.get(false).stream().sorted() - .forEach(type -> builder.append("import ").append(type.fullyQualifiedName()).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/PrintedAccessPath.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/PrintedAccessPath.java index 739dcf0e..e83076ca 100644 --- 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 @@ -1,3 +1,20 @@ +/* + * 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; @@ -51,7 +68,7 @@ public AsExpression getVariableAccess() { return Builders.fieldAccess(elementName()).setSource(parent().getVariableAccess()); } - public CodeType.ClassType type() { + public CodeType type() { return access.getLast().getAsCodeType(); } 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 index 21b41bd0..29362707 100644 --- 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 @@ -17,111 +17,135 @@ */ 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.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.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.CommonCommandTreePrinter; -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.printer.source.AbstractSourcePrintingVisitor; +import net.strokkur.commands.internal.util.ConvertableTo; import net.strokkur.commands.internal.velocity.util.VelocityClasses; -import org.jspecify.annotations.Nullable; +import net.strokkur.commands.internal.velocity.util.VelocityCommandInformation; -import java.io.IOException; -import java.util.List; -import java.util.Locale; -import java.util.Objects; import java.util.Set; - -final class VelocityClassBuilder extends CommonClassBuilder { - VelocityClassBuilder(CommonCommandTreePrinter printer) { - super(printer); +import java.util.function.BiFunction; + +class VelocityClassBuilder extends CommonClassBuilder { + VelocityClassBuilder( + CommandNode rootNode, + VelocityCommandInformation commandInformation, + BiFunction, AbstractSourcePrintingVisitor> sourceVisitor + ) { + super(rootNode, commandInformation, new VelocityBrigadierStatementBuilder(), sourceVisitor); } @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(); - } + protected MethodBuilder getCreateMethodBuilder() { + return super.getCreateMethodBuilder() + .setReturnType(VelocityClasses.TYPED_LITERAL_COMMAND_NODE.getAsCodeType()); } @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"; - } + protected void populateStaticFields(ClassBuilder builder) { + super.populateStaticFields(builder); - final DefaultExecutable.Type type = DefaultExecutable.Type.getType(parameter); - if (type == DefaultExecutable.Type.LIST || type == DefaultExecutable.Type.ARRAY) { - return Objects.requireNonNull(type.getGetter()); + final MethodInvocationBuilder listOf = Builders.methodInvocation("of").setStatic(CodeType.LIST); + if (commandInformation.aliases() != null) { + for (String alias : commandInformation.aliases()) { + listOf.addParameter(CodeExpression.string(alias)); + } } - throw new PrinterException("Unknown parameter type: " + parameter.getName()); + builder.addField(Builders.field("ALIASES", CodeType.LIST_STRING) + .setModifiers(Modifiers.PUBLIC, Modifiers.STATIC, Modifiers.FINAL) + .setInitialiser(listOf) + ); } @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; + 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)); + } } - - 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()) - ); + 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; } - @Override - public String getLiteralMethodString() { - return "BrigadierCommand.literalArgumentBuilder"; + private boolean isProxyServer(SourceVariable sourceVar) { + return CodeTypeAdapter.from(sourceVar.getType()).equals(VelocityClasses.PROXY_SERVER.getAsCodeType()); } @Override - public String getArgumentMethodString() { - return "BrigadierCommand.requiredArgumentBuilder"; + 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 c13849a2..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.CommonClassBuilder; -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.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.collectFields(); - 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 CommonClassBuilder createTreePrinter() { - return new VelocityClassBuilder(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/VelocityInstanceFieldPrinter.java b/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityInstanceFieldPrinter.java deleted file mode 100644 index 106cbd61..00000000 --- a/processor/velocity/src/main/java/net/strokkur/commands/internal/velocity/VelocityInstanceFieldPrinter.java +++ /dev/null @@ -1,37 +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.SourceParameter; -import net.strokkur.commands.internal.printer.CommonCommandTreePrinter; -import net.strokkur.commands.internal.printer.CommonInstanceFieldPrinter; -import net.strokkur.commands.internal.velocity.util.VelocityClasses; - -final class VelocityInstanceFieldPrinter extends CommonInstanceFieldPrinter { - VelocityInstanceFieldPrinter(CommonCommandTreePrinter printer) { - super(printer); - } - - @Override - public String getParameterName(SourceParameter parameter) { - if (parameter.getType().getFullyQualifiedName().equals(VelocityClasses.PROXY_SERVER)) { - return "server"; - } - return super.getParameterName(parameter); - } -} 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 ac25a805..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 createBuilder(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/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 {