getLines() {
+ return Arrays.stream(builder.toString().strip().split("\n"))
+ .map(str -> str.isBlank() ? "" : " " + str)
+ .map(str -> "///" + str)
+ .toList();
+ }
+
+ @Override
+ public void visit(CodeJavadoc.Header value) {
+ builder.append('\n');
+ builder.repeat("#", value.level()).append(' ');
+ builder.append(value.text());
+ builder.append('\n');
+ }
+
+ @Override
+ public void visit(CodeJavadoc.Linebreak value) {
+ // '' in legacy JD; Markdown equivalent is to just keep the line empty.
+ }
+
+ @Override
+ public void visit(CodeJavadoc.InlineCode value) {
+ builder.append('`').append(value.code()).append('`');
+ }
+
+ @Override
+ public void visit(CodeJavadoc.CodeBlock value) {
+ builder.append("```\n");
+ builder.append(value.code());
+ builder.append("\n```");
+ }
+
+ @Override
+ public void visit(CodeJavadoc.Url value) {
+ builder.append('[').append(value.text()).append(']');
+ builder.append('(').append(value.url()).append(')');
+ }
+
+ @Override
+ public void visit(CodeJavadoc.ClassReference value) {
+ if (value.name() != null) {
+ builder.append('[').append(value.name()).append(']');
+ }
+
+ builder.append('[').append(getQualifiedTypeName(value.codeClass())).append(']');
+ }
+
+ @Override
+ public void visit(CodeJavadoc.MethodReference value) {
+ if (value.name() != null) {
+ builder.append('[').append(value.name()).append(']');
+ }
+
+ builder.append('[').append(getMethodRefString(value.method(), value.localMethod())).append(']');
+ }
+}
diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/javadoc/JavaStarJavadocVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/javadoc/JavaStarJavadocVisitor.java
new file mode 100644
index 00000000..dd87295c
--- /dev/null
+++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/javadoc/JavaStarJavadocVisitor.java
@@ -0,0 +1,183 @@
+/*
+ * StrokkCommands - A super simple annotation based zero-shade Paper command API library.
+ * Copyright (C) 2025 Strokkur24
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see .
+ */
+package net.strokkur.commands.internal.printer.javadoc;
+
+import net.strokkur.commands.internal.codegen.CodeClass;
+import net.strokkur.commands.internal.codegen.CodeMethod;
+import net.strokkur.commands.internal.codegen.CodePackage;
+import net.strokkur.commands.internal.codegen.CodeType;
+import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.SequencedCollection;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class JavaStarJavadocVisitor extends AbstractJavadocPrintingVisitor {
+ public JavaStarJavadocVisitor() {
+ super(null, null);
+ }
+
+ public JavaStarJavadocVisitor(CodePackage currentPath, Set imports) {
+ super(currentPath, imports);
+ }
+
+ @Override
+ public SequencedCollection getLines() {
+ final List out = new ArrayList<>();
+ out.add("/**");
+ out.addAll(Arrays.stream(builder.toString().strip().split("\n"))
+ .map(str -> str.isEmpty() ? " *" : " * " + str)
+ .toList());
+ out.add(" */");
+ return out;
+ }
+
+ @Override
+ public void visit(CodeJavadoc.PlainText value) {
+ builder.append(value.text());
+ }
+
+ @Override
+ public void visit(CodeJavadoc.Meta value) {
+ builder
+ .append('@')
+ .append(value.descriptor())
+ .append(' ')
+ .append(value.value());
+ }
+
+ @Override
+ public void visit(CodeJavadoc.MethodReferenceMeta value) {
+ builder
+ .append('@')
+ .append(value.descriptor())
+ .append(' ')
+ .append(getMethodRefString(value.codeMethod(), value.localMethod()));
+
+ if (value.text() != null) {
+ builder.append(' ').append(value.text());
+ }
+ }
+
+ @Override
+ public void visit(CodeJavadoc.ClassReferenceMeta value) {
+ builder
+ .append('@')
+ .append(value.descriptor())
+ .append(' ')
+ .append(getQualifiedTypeName(value.type()));
+
+ if (value.text() != null) {
+ builder.append(' ').append(value.text());
+ }
+ }
+
+ @Override
+ public void visit(CodeJavadoc.Header value) {
+ builder.append('\n');
+ builder.append("');
+ builder.append(value.text());
+ builder.append("');
+ builder.append('\n');
+ }
+
+ @Override
+ public void visit(CodeJavadoc.Newline value) {
+ builder.append('\n');
+ }
+
+ @Override
+ public void visit(CodeJavadoc.Linebreak value) {
+ builder.append("");
+ }
+
+ @Override
+ public void visit(CodeJavadoc.InlineCode value) {
+ builder.append("{@code ").append(value.code()).append("}");
+ }
+
+ @Override
+ public void visit(CodeJavadoc.CodeBlock value) {
+ builder.append("
{@code\n")
+ .append(value.code())
+ .append("\n}");
+ }
+
+ @Override
+ public void visit(CodeJavadoc.Url value) {
+ builder.append("")
+ .append(value.text())
+ .append("");
+ }
+
+ @Override
+ public void visit(CodeJavadoc.ClassReference value) {
+ builder.append("{@link ").append(getQualifiedTypeName(value.codeClass()));
+ if (value.name() != null) {
+ builder.append(" ").append(value.name());
+ }
+ builder.append("}");
+ }
+
+ @Override
+ public void visit(CodeJavadoc.MethodReference value) {
+ builder.append("{@link ").append(getMethodRefString(value.method(), value.localMethod()));
+ if (value.name() != null) {
+ builder.append(" ").append(value.name());
+ }
+ builder.append("}");
+ }
+
+ protected String getQualifiedTypeName(CodeClass type) {
+ if (type.codePackage().path().equals("java.lang") || Objects.equals(currentPath, type.codePackage())) {
+ return type.name();
+ }
+ return type.fullyQualifiedName();
+ }
+
+ protected String getQualifiedTypeName(CodeType.ClassType type) {
+ if (CodePackage.isRedundantImport(currentPath, type.codePackage())
+ || existingImports != null && existingImports.contains(type)
+ ) {
+ return type.name();
+ }
+ return type.fullyQualifiedName();
+ }
+
+ public String javadocName(CodeMethod method) {
+ return method.name() + "(" + method.parameters().stream()
+ .map(param -> {
+ if (param.type() instanceof CodeType.ClassType classType) {
+ return getQualifiedTypeName(classType);
+ }
+ return param.type().fullyQualifiedName();
+ })
+ .collect(Collectors.joining(", "))
+ + ")";
+ }
+
+ protected String getMethodRefString(CodeMethod method, boolean local) {
+ return local
+ ? "#" + javadocName(method)
+ : getQualifiedTypeName(method.declaredClass()) + "#" + javadocName(method);
+ }
+}
diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/AbstractSourcePrintingVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/AbstractSourcePrintingVisitor.java
new file mode 100644
index 00000000..8cd08af2
--- /dev/null
+++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/AbstractSourcePrintingVisitor.java
@@ -0,0 +1,194 @@
+/*
+ * StrokkCommands - A super simple annotation based zero-shade Paper command API library.
+ * Copyright (C) 2025 Strokkur24
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see .
+ */
+package net.strokkur.commands.internal.printer.source;
+
+import net.strokkur.commands.internal.codegen.CodeAnnotation;
+import net.strokkur.commands.internal.codegen.CodeExpression;
+import net.strokkur.commands.internal.codegen.CodePackage;
+import net.strokkur.commands.internal.codegen.InvokesMethod;
+import net.strokkur.commands.internal.codegen.Modifiers;
+import net.strokkur.commands.internal.codegen.javadoc.CodeJavadoc;
+import net.strokkur.commands.internal.codegen.visitor.CodeVisitable;
+import net.strokkur.commands.internal.codegen.visitor.CodeVisitor;
+import net.strokkur.commands.internal.printer.javadoc.AbstractJavadocPrintingVisitor;
+import org.jspecify.annotations.Nullable;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+public abstract class AbstractSourcePrintingVisitor implements CodeVisitor {
+ protected final Supplier javadocPrintingVisitor;
+ private final String indentString;
+ private final String continuationIndentString;
+ private int indentation = 0;
+ private int continuationIndent = 0;
+
+ public AbstractSourcePrintingVisitor(Supplier javadocPrintingVisitor, String indentString, String continuationIndentString) {
+ this.javadocPrintingVisitor = javadocPrintingVisitor;
+ this.indentString = indentString;
+ this.continuationIndentString = continuationIndentString;
+ }
+
+ /// Packages are never printed.
+ @Override
+ public final StringBuilder visitPackage(CodePackage codePackage) {
+ throw new IllegalStateException("This should not be called.");
+ }
+
+ protected final void appendIndented(Runnable run) {
+ indentation++;
+ run.run();
+ indentation--;
+ }
+
+ protected final void appendIndentedContinuation(Runnable run) {
+ continuationIndent++;
+ run.run();
+ continuationIndent--;
+ }
+
+ protected final StringBuilder append(Consumer run) {
+ final StringBuilder builder = new StringBuilder();
+ run.accept(builder);
+ return builder;
+ }
+
+ protected final void appendIndent(StringBuilder builder) {
+ builder.repeat(indentString, indentation).repeat(continuationIndentString, continuationIndent);
+ }
+
+ // Utility methods
+
+ protected void appendNested(StringBuilder builder, CodeVisitable visitable) {
+ builder.append(visitable.accept(this));
+ }
+
+ protected String joining(Collection nested) {
+ return nested.stream()
+ .map(visitable -> visitable.accept(this))
+ .map(StringBuilder::toString)
+ .collect(Collectors.joining(", "));
+ }
+
+ protected void printJavadocIndented(StringBuilder builder, @Nullable CodeJavadoc javadoc) {
+ if (javadoc != null) {
+ final AbstractJavadocPrintingVisitor visitor = javadocPrintingVisitor.get();
+ javadoc.accept(visitor);
+ for (String line : visitor.getLines()) {
+ appendIndent(builder);
+ builder.append(line);
+ builder.append("\n");
+ }
+ }
+ }
+
+ protected void printAnnotationsIndented(StringBuilder builder, List annotations) {
+ for (CodeAnnotation annotation : annotations) {
+ appendIndent(builder);
+ appendNested(builder, annotation);
+ builder.append("\n");
+ }
+ }
+
+ protected void printModifiersIndented(StringBuilder builder, Set modifiers) {
+ appendIndent(builder);
+ modifiers.stream()
+ .sorted(Comparator.comparingInt(Modifiers::priority))
+ .map(Modifiers::toString)
+ .forEach(mod -> builder.append(mod).append(' '));
+ }
+
+ protected void appendMethodParametersMultiline(StringBuilder builder, List parameters) {
+ appendIndentedContinuation(() -> {
+ builder.append("\n");
+ for (int i = 0, parametersSize = parameters.size(); i < parametersSize; i++) {
+ final CodeExpression parameter = parameters.get(i);
+ appendIndent(builder);
+ appendNested(builder, parameter);
+ if (i + 1 < parametersSize) {
+ builder.append(",");
+ }
+ builder.append("\n");
+ }
+ });
+ appendIndent(builder);
+ }
+
+ protected void appendMethodInvocation(StringBuilder builder, InvokesMethod method) {
+ if (method.isCtor()) {
+ builder.append("new ");
+ } else {
+ final String source;
+ if (method.instanceSource() != null) {
+ source = method.instanceSource().accept(this).toString();
+ } else if (method.isStatic() && method.type() != null) {
+ source = method.type().name();
+ } else {
+ source = null;
+ }
+
+ if (source != null) {
+ builder.append(source);
+ appendIndentedContinuation(() -> {
+ if (method.style().newline()) {
+ builder.append("\n");
+ appendIndent(builder);
+ }
+ builder.append(".");
+ });
+ }
+ }
+
+ builder.append(method.methodName());
+ builder.append("(");
+ if (method.style().multilineParameters()) {
+ appendMethodParametersMultiline(builder, method.parameters());
+ } else {
+ builder.append(joining(method.parameters()));
+ }
+ builder.append(")");
+
+ appendIndentedContinuation(() -> {
+ for (InvokesMethod.Chained chained : method.chained()) {
+ if (chained.style().newline()) {
+ builder.append("\n");
+ appendIndent(builder);
+ }
+ builder.append(".");
+ builder.append(chained.methodName());
+ builder.append("(");
+ if (chained.style().multilineParameters()) {
+ appendMethodParametersMultiline(builder, chained.parameters());
+ } else {
+ builder.append(joining(chained.parameters()));
+ }
+
+ if (chained.style().newlineClosingBrace()) {
+ builder.append("\n");
+ appendIndent(builder);
+ }
+ builder.append(")");
+ }
+ });
+ }
+}
diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/ImportGatheringVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/ImportGatheringVisitor.java
new file mode 100644
index 00000000..8701c9b8
--- /dev/null
+++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/ImportGatheringVisitor.java
@@ -0,0 +1,208 @@
+/*
+ * StrokkCommands - A super simple annotation based zero-shade Paper command API library.
+ * Copyright (C) 2025 Strokkur24
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see .
+ */
+package net.strokkur.commands.internal.printer.source;
+
+import net.strokkur.commands.internal.codegen.CodeAnnotation;
+import net.strokkur.commands.internal.codegen.CodeClass;
+import net.strokkur.commands.internal.codegen.CodeExpression;
+import net.strokkur.commands.internal.codegen.CodeField;
+import net.strokkur.commands.internal.codegen.CodeMethod;
+import net.strokkur.commands.internal.codegen.CodePackage;
+import net.strokkur.commands.internal.codegen.CodeParameter;
+import net.strokkur.commands.internal.codegen.CodeStatement;
+import net.strokkur.commands.internal.codegen.CodeType;
+import net.strokkur.commands.internal.codegen.InvokesMethod;
+import net.strokkur.commands.internal.codegen.visitor.CodeVisitable;
+import net.strokkur.commands.internal.codegen.visitor.CodeVisitor;
+import org.jspecify.annotations.Nullable;
+
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class ImportGatheringVisitor implements CodeVisitor> {
+
+ public Set collectFilteredImports(CodeClass codeClass) {
+ return codeClass.accept(this).stream()
+ .filter(gathered -> !CodePackage.isRedundantImport(codeClass.codePackage(), gathered.codePackage()))
+ .collect(Collectors.toSet());
+ }
+
+ private Set collectMethodInvokesImports(InvokesMethod invokes) {
+ final Set chainedImports = collect(invokes.chained().stream()
+ .flatMap(chained -> chained.parameters().stream())
+ .toList()
+ );
+
+ if (invokes.isCtor()) {
+ return join(
+ Objects.requireNonNull(invokes.type()).accept(this),
+ collect(invokes.parameters()),
+ chainedImports
+ );
+ }
+
+ final Set typeImport = invokes.isStatic() && invokes.type() != null
+ ? invokes.type().accept(this)
+ : Set.of();
+
+ return join(
+ typeImport,
+ collect(invokes.parameters()),
+ chainedImports
+ );
+ }
+
+ @SafeVarargs
+ private Set join(Set... all) {
+ return Stream.of(all)
+ .flatMap(Collection::stream)
+ .collect(Collectors.toSet());
+ }
+
+ private Set maybeAccess(@Nullable CodeVisitable visitable) {
+ if (visitable != null) {
+ return visitable.accept(this);
+ } else {
+ return Set.of();
+ }
+ }
+
+ private Set collect(Collection collection) {
+ return collection.stream()
+ .flatMap(visitable -> visitable.accept(this).stream())
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set visitClass(CodeClass codeClass) {
+ return join(
+ Set.of(CodeType.ofClass(codeClass)),
+ collect(codeClass.methods()),
+ collect(codeClass.fields()),
+ collect(codeClass.annotations())
+ );
+ }
+
+ @Override
+ public Set visitMethod(CodeMethod codeMethod) {
+ return join(
+ collect(codeMethod.parameters()),
+ collect(codeMethod.throwsExceptions()),
+ codeMethod.returnType().accept(this),
+ collect(codeMethod.codeBlock().statements())
+ );
+ }
+
+ @Override
+ public Set visitPackage(CodePackage codePackage) {
+ throw new IllegalStateException("This should not be called.");
+ }
+
+ @Override
+ public Set visitParameter(CodeParameter codeParameter) {
+ return codeParameter.type().accept(this);
+ }
+
+ @Override
+ public Set visitType(CodeType codeType) {
+ if (codeType instanceof CodeType.ArrayType array) {
+ return array.inner().accept(this);
+ }
+
+ if (codeType instanceof CodeType.ClassType codeClass) {
+ return join(
+ codeClass.types() == null ? Set.of() : collect(codeClass.types()),
+ Set.of(codeClass)
+ );
+ }
+
+ return Set.of();
+ }
+
+ @Override
+ public Set visitAnnotation(CodeAnnotation codeAnnotation) {
+ return Set.of(codeAnnotation.type());
+ }
+
+ @Override
+ public Set visitField(CodeField codeField) {
+ return join(
+ codeField.initialiser() != null ? codeField.initialiser().accept(this) : Set.of(),
+ collect(codeField.annotations()),
+ codeField.type().accept(this)
+ );
+ }
+
+ @Override
+ public Set visitExpression(CodeExpression codeExpression) {
+ return switch (codeExpression) {
+ case CodeExpression.MethodInvocation(InvokesMethod invokes) -> collectMethodInvokesImports(invokes);
+
+ case CodeExpression.MethodReference ref -> ref.type().accept(this);
+
+ case CodeExpression.SingleLineLambda lambda -> lambda.lambdaExpression().accept(this);
+
+ case CodeExpression.MultiLineLambda lambda -> collect(lambda.statements());
+
+ case CodeExpression.Instanceof instStmt -> join(
+ instStmt.left().accept(this),
+ instStmt.type().accept(this)
+ );
+
+ case CodeExpression.FieldAccess field -> join(
+ maybeAccess(field.source()),
+ field.isStatic() ? maybeAccess(field.type()) : Set.of()
+ );
+
+ default -> Set.of();
+ };
+ }
+
+ @Override
+ public Set visitStatement(CodeStatement codeStatement) {
+ return switch (codeStatement) {
+ case CodeStatement.VariableDeclaration variableDeclaration -> {
+ if (variableDeclaration.assignment() != null) {
+ yield join(
+ variableDeclaration.assignment().accept(this),
+ variableDeclaration.type().accept(this)
+ );
+ }
+ yield variableDeclaration.type().accept(this);
+ }
+
+ case CodeStatement.ReturnStatement returnStatement -> returnStatement.returnValue() != null ?
+ returnStatement.returnValue().accept(this) :
+ Set.of();
+
+ case CodeStatement.ThrowStatement throwStatement -> throwStatement.throwExpression().accept(this);
+
+ case CodeStatement.MethodInvocation(InvokesMethod invokes) -> collectMethodInvokesImports(invokes);
+
+ case CodeStatement.If ifStmt -> join(
+ ifStmt.booleanExpr().accept(this),
+ collect(ifStmt.ifTrue())
+ );
+
+ case CodeStatement.Blank blank -> Set.of();
+ };
+ }
+}
diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/JavaSourcePrintingVisitor.java b/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/JavaSourcePrintingVisitor.java
new file mode 100644
index 00000000..fd22d328
--- /dev/null
+++ b/processor/common/src/main/java/net/strokkur/commands/internal/printer/source/JavaSourcePrintingVisitor.java
@@ -0,0 +1,309 @@
+/*
+ * StrokkCommands - A super simple annotation based zero-shade Paper command API library.
+ * Copyright (C) 2025 Strokkur24
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see .
+ */
+package net.strokkur.commands.internal.printer.source;
+
+import net.strokkur.commands.internal.codegen.CodeAnnotation;
+import net.strokkur.commands.internal.codegen.CodeClass;
+import net.strokkur.commands.internal.codegen.CodeConstructor;
+import net.strokkur.commands.internal.codegen.CodeExpression;
+import net.strokkur.commands.internal.codegen.CodeField;
+import net.strokkur.commands.internal.codegen.CodeMethod;
+import net.strokkur.commands.internal.codegen.CodeParameter;
+import net.strokkur.commands.internal.codegen.CodeStatement;
+import net.strokkur.commands.internal.codegen.CodeType;
+import net.strokkur.commands.internal.codegen.InvokesMethod;
+import net.strokkur.commands.internal.codegen.Modifiers;
+import net.strokkur.commands.internal.printer.javadoc.AbstractJavadocPrintingVisitor;
+
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+public class JavaSourcePrintingVisitor extends AbstractSourcePrintingVisitor {
+ public JavaSourcePrintingVisitor(Supplier javadocPrintingVisitor, String indent, String continuationIndent) {
+ super(javadocPrintingVisitor, indent, continuationIndent);
+ }
+
+ @Override
+ public StringBuilder visitClass(CodeClass codeClass) {
+ class ClassPrintUtil {
+ private void printMethods(StringBuilder builder, List methods) {
+ methods.forEach(method -> {
+ builder.append("\n");
+ appendNested(builder, method);
+ });
+ }
+ }
+
+ final ClassPrintUtil util = new ClassPrintUtil();
+ return append(builder -> {
+ printJavadocIndented(builder, codeClass.javadoc());
+ printAnnotationsIndented(builder, codeClass.annotations());
+ printModifiersIndented(builder, codeClass.modifiers());
+ builder.append("class ");
+ builder.append(codeClass.name());
+ builder.append(" {\n");
+
+ appendIndented(() -> {
+ final List staticFields = codeClass.fields().stream()
+ .filter(field -> field.modifiers().contains(Modifiers.STATIC))
+ .toList();
+ staticFields.forEach(field -> appendNested(builder, field));
+
+ final List instanceFields = codeClass.fields().stream()
+ .filter(field -> !field.modifiers().contains(Modifiers.STATIC))
+ .toList();
+
+ if (!staticFields.isEmpty() && !instanceFields.isEmpty()) {
+ builder.append("\n");
+ }
+ instanceFields.forEach(field -> appendNested(builder, field));
+
+ final List staticMethods = codeClass.methods().stream()
+ .filter(method -> method.modifiers().contains(Modifiers.STATIC))
+ .toList();
+ util.printMethods(builder, staticMethods);
+
+ final List constructors = codeClass.methods().stream()
+ .filter(CodeConstructor.class::isInstance)
+ .toList();
+ util.printMethods(builder, constructors);
+
+ final List instanceMethods = codeClass.methods().stream()
+ .filter(Predicate.not(method -> method.modifiers().contains(Modifiers.STATIC)))
+ .filter(Predicate.not(CodeConstructor.class::isInstance))
+ .toList();
+ util.printMethods(builder, instanceMethods);
+ });
+
+ appendIndent(builder);
+ builder.append("}\n");
+ });
+ }
+
+ @Override
+ public StringBuilder visitMethod(CodeMethod codeMethod) {
+ return append(builder -> {
+ printJavadocIndented(builder, codeMethod.javadoc());
+ printModifiersIndented(builder, codeMethod.modifiers());
+ builder.append(codeMethod.returnType().accept(this));
+ if (!(codeMethod instanceof CodeConstructor)) {
+ builder.append(" ");
+ builder.append(codeMethod.name());
+ }
+
+ builder.append("(");
+ builder.append(joining(codeMethod.parameters()));
+ builder.append(")");
+
+ if (!codeMethod.throwsExceptions().isEmpty()) {
+ builder.append(" throws ");
+ builder.append(joining(codeMethod.throwsExceptions()));
+ }
+
+ builder.append(" {\n");
+ appendIndented(() -> codeMethod.codeBlock().statements().forEach(
+ stmt -> appendNested(builder, stmt)
+ ));
+ appendIndent(builder);
+ builder.append("}\n");
+ });
+ }
+
+ @Override
+ public StringBuilder visitParameter(CodeParameter codeParameter) {
+ return append(builder -> {
+ appendNested(builder, codeParameter.type());
+ builder.append(" ");
+ builder.append(codeParameter.name());
+ });
+ }
+
+ @Override
+ public StringBuilder visitType(CodeType codeType) {
+ final StringBuilder out = new StringBuilder(codeType.name());
+
+ if (codeType instanceof CodeType.ClassType classType && classType.types() != null) {
+ out.append("<").append(joining(classType.types())).append(">");
+ }
+
+ return out;
+ }
+
+ @Override
+ public StringBuilder visitAnnotation(CodeAnnotation codeAnnotation) {
+ return append(builder -> {
+ builder.append("@");
+ appendNested(builder, codeAnnotation.type());
+ });
+ }
+
+ @Override
+ public StringBuilder visitField(CodeField codeField) {
+ return append(builder -> {
+ printModifiersIndented(builder, codeField.modifiers());
+ if (!codeField.annotations().isEmpty()) {
+ builder.append(joining(codeField.annotations()));
+ builder.append(" ");
+ }
+ appendNested(builder, codeField.type());
+ builder.append(" ");
+ builder.append(codeField.name());
+ if (codeField.initialiser() != null) {
+ builder.append(" = ");
+ appendNested(builder, codeField.initialiser());
+ }
+ builder.append(";\n");
+ });
+ }
+
+ @Override
+ public StringBuilder visitExpression(CodeExpression codeExpression) {
+ return append(builder -> {
+ switch (codeExpression) {
+ case CodeExpression.MethodInvocation(InvokesMethod invokes) -> {
+ appendMethodInvocation(builder, invokes);
+ }
+ case CodeExpression.Null ignored -> {
+ builder.append("null");
+ }
+ case CodeExpression.StringLiteral stringLiteral -> {
+ builder.append('"').append(stringLiteral.value()).append('"');
+ }
+ case CodeExpression.NumberConstant number -> {
+ builder.append(number);
+ }
+ case CodeExpression.Variable variable -> {
+ builder.append(variable.name());
+ }
+ case CodeExpression.MethodReference ref -> {
+ builder.append(ref.type().name()).append("::").append(ref.methodName());
+ }
+ case CodeExpression.SingleLineLambda lambda -> {
+ appendLambdaHead(builder, lambda.lambdaParams());
+ appendNested(builder, lambda.lambdaExpression());
+ }
+ case CodeExpression.MultiLineLambda lambda -> {
+ appendLambdaHead(builder, lambda.lambdaParams());
+ builder.append("{\n");
+ appendIndented(() -> {
+ for (CodeStatement statement : lambda.statements()) {
+ appendNested(builder, statement);
+ }
+ });
+ appendIndent(builder);
+ builder.append("}");
+ }
+ case CodeExpression.Instanceof instExpr -> {
+ if (instExpr.isInverted()) {
+ builder.append("!(");
+ }
+ appendNested(builder, instExpr.left());
+ builder.append(" instanceof ");
+ builder.append(instExpr.type().name());
+ if (instExpr.name() != null) {
+ builder.append(" ").append(instExpr.name());
+ }
+ if (instExpr.isInverted()) {
+ builder.append(")");
+ }
+ }
+ case CodeExpression.FieldAccess(
+ CodeType.ClassType type, CodeExpression source, String fieldName, boolean isStatic
+ ) -> {
+ if (source != null) {
+ appendNested(builder, source);
+ builder.append(".");
+ } else if (type != null && isStatic) {
+ builder.append(type.name());
+ builder.append(".");
+ }
+
+ builder.append(fieldName);
+ }
+ default -> throw new IllegalStateException("Invalid expression: " + codeExpression.getClass().getName());
+ }
+ });
+ }
+
+ @Override
+ public StringBuilder visitStatement(CodeStatement codeStatement) {
+ return append(builder -> {
+ if (codeStatement instanceof CodeStatement.Blank) {
+ builder.append("\n");
+ return;
+ }
+
+ appendIndent(builder);
+
+ if (codeStatement instanceof CodeStatement.If ifStmt) {
+ builder.append("if (");
+ appendNested(builder, ifStmt.booleanExpr());
+ builder.append(") {\n");
+ appendIndented(() -> {
+ ifStmt.ifTrue().forEach(stmt -> appendNested(builder, stmt));
+ });
+ appendIndent(builder);
+ builder.append("}\n");
+ return;
+ }
+
+ switch (codeStatement) {
+ case CodeStatement.MethodInvocation(InvokesMethod invokes) -> {
+ appendMethodInvocation(builder, invokes);
+ }
+ case CodeStatement.ReturnStatement returnStatement -> {
+ builder.append("return");
+ if (returnStatement.returnValue() != null) {
+ builder.append(" ");
+ appendNested(builder, returnStatement.returnValue());
+ }
+ }
+ case CodeStatement.ThrowStatement throwStatement -> {
+ builder.append("throw ");
+ appendNested(builder, throwStatement.throwExpression());
+ }
+ case CodeStatement.VariableDeclaration variableDeclaration -> {
+ if (variableDeclaration.isFinal()) {
+ builder.append("final ");
+ }
+ appendNested(builder, variableDeclaration.type());
+ builder.append(" ");
+ builder.append(variableDeclaration.name());
+ if (variableDeclaration.assignment() != null) {
+ builder.append(" = ");
+ appendNested(builder, variableDeclaration.assignment());
+ }
+ }
+ default -> throw new IllegalStateException("Invalid statement: " + codeStatement.getClass().getName());
+ }
+ builder.append(";\n");
+ });
+ }
+
+ private void appendLambdaHead(StringBuilder builder, List lambdaParams) {
+ if (lambdaParams.size() != 1) {
+ builder.append("(");
+ builder.append(String.join(", ", lambdaParams));
+ builder.append(")");
+ } else {
+ builder.append(lambdaParams.getFirst());
+ }
+ builder.append(" -> ");
+ }
+}
diff --git a/processor/common/src/main/java/net/strokkur/commands/internal/util/Classes.java b/processor/common/src/main/java/net/strokkur/commands/internal/util/Classes.java
index ead1be2e..39933620 100644
--- a/processor/common/src/main/java/net/strokkur/commands/internal/util/Classes.java
+++ b/processor/common/src/main/java/net/strokkur/commands/internal/util/Classes.java
@@ -17,30 +17,67 @@
*/
package net.strokkur.commands.internal.util;
-public interface Classes {
+import net.strokkur.commands.internal.codegen.CodeType;
+import net.strokkur.commands.internal.codegen.as.AsCodeType;
+
+import java.util.Arrays;
+
+public enum Classes implements AsCodeType {
// Java types
- String LIST = "java.util.List";
- String COLLECTIONS = "java.util.Collections";
- String ARRAYS = "java.util.Arrays";
- String LIST_STRING = LIST + "