From f24998dae79c7139ca139192da0a41c80165ad71 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 2 Jul 2023 17:51:40 +0200 Subject: [PATCH 01/35] Initial work to support interface-based configs --- extra/interface/ap/build.gradle | 24 ++ ...onfigImplementationGeneratorProcessor.java | 263 ++++++++++++++++++ .../processor/TypeSpecBuilderTracker.java | 83 ++++++ .../ConfigImplementationGenerationTest.java | 118 ++++++++ .../src/test/resources/test/BasicConfig.java | 10 + .../resources/test/BasicConfig.properties | 1 + .../test/resources/test/BasicConfigImpl.java | 24 ++ .../test/resources/test/ExtendedConfig.java | 8 + .../resources/test/ExtendedConfig.properties | 1 + .../resources/test/ExtendedConfigImpl.java | 29 ++ .../test/resources/test/MultiLayerConfig.java | 15 + .../test/MultiLayerConfig.properties | 2 + .../resources/test/MultiLayerConfigImpl.java | 43 +++ extra/interface/build.gradle | 10 + .../configurate/interfaces/Constants.java | 35 +++ .../interfaces/InterfaceDefaultOptions.java | 67 +++++ .../interfaces/InterfaceTypeSerializer.java | 106 +++++++ .../configurate/interfaces/meta/Exclude.java | 33 +++ .../InterfaceTypeSerializerTest.java | 53 ++++ .../configurate/interfaces/TestConfig.java | 27 ++ .../interfaces/TestConfigImpl.java | 27 ++ .../interfaces/interface_mappings.properties | 2 + gradle/libs.versions.toml | 26 +- settings.gradle | 4 +- 24 files changed, 1001 insertions(+), 10 deletions(-) create mode 100644 extra/interface/ap/build.gradle create mode 100644 extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java create mode 100644 extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java create mode 100644 extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java create mode 100644 extra/interface/ap/src/test/resources/test/BasicConfig.java create mode 100644 extra/interface/ap/src/test/resources/test/BasicConfig.properties create mode 100644 extra/interface/ap/src/test/resources/test/BasicConfigImpl.java create mode 100644 extra/interface/ap/src/test/resources/test/ExtendedConfig.java create mode 100644 extra/interface/ap/src/test/resources/test/ExtendedConfig.properties create mode 100644 extra/interface/ap/src/test/resources/test/ExtendedConfigImpl.java create mode 100644 extra/interface/ap/src/test/resources/test/MultiLayerConfig.java create mode 100644 extra/interface/ap/src/test/resources/test/MultiLayerConfig.properties create mode 100644 extra/interface/ap/src/test/resources/test/MultiLayerConfigImpl.java create mode 100644 extra/interface/build.gradle create mode 100644 extra/interface/src/main/java/org/spongepowered/configurate/interfaces/Constants.java create mode 100644 extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java create mode 100644 extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializer.java create mode 100644 extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Exclude.java create mode 100644 extra/interface/src/test/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializerTest.java create mode 100644 extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TestConfig.java create mode 100644 extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TestConfigImpl.java create mode 100644 extra/interface/src/test/resources/org/spongepowered/configurate/interfaces/interface_mappings.properties diff --git a/extra/interface/ap/build.gradle b/extra/interface/ap/build.gradle new file mode 100644 index 000000000..702758605 --- /dev/null +++ b/extra/interface/ap/build.gradle @@ -0,0 +1,24 @@ +plugins { + id "org.spongepowered.configurate.build.component" +} + +description = "Annotation processor for Configurate to generate an implementation for config interfaces" + +dependencies { + implementation projects.core + implementation projects.extra.extraInterface + implementation libs.javapoet + implementation libs.auto.service + + testImplementation libs.compile.testing +} + +// there is no javadoc +tasks.withType(Javadoc).configureEach { enabled = false } + +test { + // See: https://github.com/google/compile-testing/issues/222 + jvmArgs '--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED' + jvmArgs '--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED' + jvmArgs '--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED' +} diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java new file mode 100644 index 000000000..9860485ec --- /dev/null +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java @@ -0,0 +1,263 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.spongepowered.configurate.interfaces.processor; + +import com.google.auto.service.AutoService; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import org.spongepowered.configurate.interfaces.Constants; +import org.spongepowered.configurate.interfaces.meta.Exclude; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + +import java.io.IOException; +import java.io.Writer; +import java.lang.annotation.Annotation; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Properties; +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Filer; +import javax.annotation.processing.Messager; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.AnnotatedConstruct; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; +import javax.tools.Diagnostic.Kind; +import javax.tools.FileObject; +import javax.tools.StandardLocation; + +@AutoService(Processor.class) +class ConfigImplementationGeneratorProcessor extends AbstractProcessor { + + private final Properties mappings = new Properties(); + private Types typeUtils; + private Filer filer; + private Messager messager; + + @Override + @SuppressWarnings("PMD.AvoidSynchronizedAtMethodLevel") + public synchronized void init(final ProcessingEnvironment processingEnv) { + super.init(processingEnv); + this.typeUtils = processingEnv.getTypeUtils(); + this.filer = processingEnv.getFiler(); + this.messager = processingEnv.getMessager(); + } + + @Override + public Set getSupportedAnnotationTypes() { + return Collections.singleton(ConfigSerializable.class.getCanonicalName()); + } + + @Override + public boolean process(final Set ignored, final RoundEnvironment env) { + if (env.processingOver()) { + if (!env.errorRaised()) { + writeMappings(); + } + return false; + } + + for (final Element element : env.getElementsAnnotatedWith(ConfigSerializable.class)) { + if (element.getKind() != ElementKind.INTERFACE) { + continue; + } + final TypeElement typeElement = (TypeElement) element; + + // nested classes are handled in their containing interfaces + if (isNestedConfig(typeElement)) { + continue; + } + + try { + processInterface(typeElement, this.mappings); + } catch (final IOException exception) { + throw new RuntimeException(exception); + } + } + + return false; + } + + private void writeMappings() { + final FileObject resource; + try { + resource = this.filer.createResource(StandardLocation.SOURCE_OUTPUT, "", Constants.MAPPING_FILE); + try (Writer writer = resource.openWriter()) { + this.mappings.store(writer, null); + } + } catch (final IOException exception) { + throw new RuntimeException("Failed to write interface mappings!", exception); + } + } + + /** + * Generate a class for the given interface and + * returns the name of the generated class. + */ + private void processInterface(final TypeElement type, final Properties generatedClasses) throws IOException { + final ClassName className = ClassName.get(type); + final TypeSpec spec = generateImplementation(type, generatedClasses).build(); + + JavaFile.builder(className.packageName(), spec) + .build() + .writeTo(this.filer); + } + + private TypeSpec.Builder generateImplementation(final TypeElement type, final Properties generatedClasses) { + final ClassName className = ClassName.get(type); + + info("Generating implementation for %s", type); + + final TypeSpec.Builder spec = TypeSpec + .classBuilder(className.simpleName() + "Impl") + .addSuperinterface(className) + .addModifiers(Modifier.FINAL) + .addAnnotation(ConfigSerializable.class) + .addJavadoc("Automatically generated implementation of the config"); + + final TypeSpecBuilderTracker tracker = new TypeSpecBuilderTracker(); + gatherElementSpec(tracker, type, generatedClasses); + tracker.writeTo(spec); + + final String qualifiedName = className.reflectionName(); + generatedClasses.put(qualifiedName, qualifiedName + "Impl"); + info("Generated implementation for %s", type); + + return spec; + } + + private void gatherElementSpec( + final TypeSpecBuilderTracker spec, + final TypeElement type, + final Properties generatedClasses + ) { + // first handle own elements + + for (final Element enclosedElement : type.getEnclosedElements()) { + final ElementKind kind = enclosedElement.getKind(); + + if (kind == ElementKind.INTERFACE && hasAnnotation(enclosedElement, ConfigSerializable.class)) { + spec.add( + enclosedElement.getSimpleName().toString(), + generateImplementation((TypeElement) enclosedElement, generatedClasses) + .addModifiers(Modifier.STATIC) + ); + continue; + } + if (kind != ElementKind.METHOD) { + continue; + } + + final ExecutableElement element = (ExecutableElement) enclosedElement; + + final boolean excluded = hasAnnotation(element, Exclude.class); + if (element.isDefault()) { + if (excluded) { + // no need to handle them + continue; + } + info("Overriding implementation for %s as it's not excluded", element); + } else if (excluded) { + throw new IllegalStateException(String.format( + Locale.ROOT, + "Cannot make config due to method %s, which is an excluded method that has no implementation!", + element + )); + } + + final List parameters = element.getParameters(); + if (parameters.size() > 1) { + throw new IllegalStateException("Setters cannot have more than one parameter! Method: " + element); + } + + final String simpleName = element.getSimpleName().toString(); + TypeMirror nodeType = element.getReturnType(); + + if (parameters.size() == 1) { + final VariableElement parameter = parameters.get(0); + // setter + spec.add( + simpleName + "#" + parameter.getSimpleName().toString(), + MethodSpec.overriding(element) + .addStatement( + "this.$N = $N", + element.getSimpleName(), + parameter.getSimpleName() + ) + ); + nodeType = parameter.asType(); + } else { + // getter + spec.add( + simpleName, + MethodSpec.overriding(element) + .addStatement("return $N", element.getSimpleName()) + ); + } + + spec.add(simpleName, FieldSpec.builder(TypeName.get(nodeType), simpleName, Modifier.PRIVATE)); + } + + // then handle parent elements + for (final TypeMirror parent : type.getInterfaces()) { + gatherElementSpec(spec, (TypeElement) this.typeUtils.asElement(parent), generatedClasses); + } + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } + + private boolean hasAnnotation(final AnnotatedConstruct element, final Class annotation) { + //noinspection ConstantValue not everything is nonnull by default + return element.getAnnotation(annotation) != null; + } + + private void info(final String message, final Object... arguments) { + this.messager.printMessage(Kind.NOTE, String.format(Locale.ROOT, message, arguments)); + } + + private boolean isNestedConfig(final TypeElement type) { + if (!type.getNestingKind().isNested()) { + return false; + } + + Element current = type; + while (current.getKind() == ElementKind.INTERFACE && hasAnnotation(current, ConfigSerializable.class)) { + current = current.getEnclosingElement(); + } + return current.getKind() == ElementKind.PACKAGE; + } + +} diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java new file mode 100644 index 000000000..72e43fb5f --- /dev/null +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java @@ -0,0 +1,83 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.spongepowered.configurate.interfaces.processor; + +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeSpec; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * {@link TypeSpec.Builder} does not keep track of duplicates, resulting in failures to compile. + * This will only allow a single definition of a given method/field + */ +class TypeSpecBuilderTracker { + + private final Map fieldSpecs = new LinkedHashMap<>(); + private final Map methodSpecs = new LinkedHashMap<>(); + private final Map typeSpecs = new LinkedHashMap<>(); + + void add(final String fieldIdentifier, final FieldSpec.Builder builder) { + final FieldSpec.Builder existing = this.fieldSpecs.get(fieldIdentifier); + if (existing != null) { + existing.addAnnotations(originalAnnotations(existing.build().annotations, builder.build().annotations)); + return; + } + this.fieldSpecs.put(fieldIdentifier, builder); + } + + void add(final String methodIdentifier, final MethodSpec.Builder builder) { + final MethodSpec.Builder existing = this.methodSpecs.get(methodIdentifier); + if (existing != null) { + existing.addAnnotations(originalAnnotations(existing.build().annotations, builder.build().annotations)); + return; + } + this.methodSpecs.put(methodIdentifier, builder); + } + + void add(final String typeIdentifier, final TypeSpec.Builder builder) { + if (this.typeSpecs.putIfAbsent(typeIdentifier, builder.build()) != null) { + throw new IllegalStateException( + "Cannot have multiple nested types with the same name! Name: " + typeIdentifier); + } + } + + void writeTo(final TypeSpec.Builder builder) { + for (FieldSpec.Builder field : this.fieldSpecs.values()) { + builder.addField(field.build()); + } + for (MethodSpec.Builder method : this.methodSpecs.values()) { + builder.addMethod(method.build()); + } + this.typeSpecs.values().forEach(builder::addType); + } + + private List originalAnnotations( + final List left, + final List right + ) { + final List result = new ArrayList<>(left); + result.removeAll(right); + return result; + } + +} diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java new file mode 100644 index 000000000..c55e59345 --- /dev/null +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java @@ -0,0 +1,118 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.spongepowered.configurate.interfaces.processor; + +import static com.google.testing.compile.CompilationSubject.assertThat; +import static com.google.testing.compile.Compiler.javac; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; + +import com.google.common.io.Resources; +import com.google.testing.compile.Compilation; +import com.google.testing.compile.JavaFileObjects; +import org.junit.experimental.runners.Enclosed; +import org.junit.jupiter.api.Test; +import org.junit.runner.RunWith; +import org.spongepowered.configurate.interfaces.Constants; + +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import javax.tools.StandardLocation; + +@RunWith(Enclosed.class) +class ConfigImplementationGenerationTest { + + @Test + void testBasicCompilation() { + // expect generated config + mapping + testCompilation("test/BasicConfig", 2); + } + + @Test + void testMultiLayerCompilation() { + // expect generated config + mapping + testCompilation("test/MultiLayerConfig", 2); + } + + @Test + void testExtendedCompilation() { + // expect generated config + mapping + testCompilation("test/ExtendedConfig", 2); + } + + /** + * Tests whether the compilation is successful and + * that the correct mappings have been made + */ + static Compilation testCompilation(final String sourceResourceName, final int expectedSourceCount) { + final Compilation compilation = + javac() + .withProcessors(new ConfigImplementationGeneratorProcessor()) + .compile(JavaFileObjects.forResource(sourceResourceName + ".java")); + + final String targetResourceName = sourceResourceName + "Impl"; + final String targetSourceName = targetResourceName.replace('/', '.'); + + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile(targetSourceName) + .hasSourceEquivalentTo(JavaFileObjects.forResource(targetResourceName + ".java")); + + try { + final URL localMappings = Resources.getResource(sourceResourceName + ".properties"); + + final String actualContent = compilation + .generatedFile(StandardLocation.SOURCE_OUTPUT, Constants.MAPPING_FILE) + .orElseThrow(() -> new IllegalStateException("Expected the interface mappings file to be created")) + .getCharContent(false) + .toString(); + + final List expectedLines = + Resources + .asCharSource(localMappings, StandardCharsets.UTF_8) + .readLines(); + + assertIterableEquals(expectedLines, removeComments(actualContent)); + } catch (final IOException exception) { + throw new RuntimeException(exception); + } + + if (expectedSourceCount != -1) { + // can't use compilation.generatedSourceFiles because the mapping + // file is written to the source output, but isn't a source file + assertEquals( + expectedSourceCount, + compilation.generatedFiles().stream() + .filter(file -> file.getName().startsWith("/SOURCE_OUTPUT/")) + .count() + ); + } + return compilation; + } + + static List removeComments(final String content) { + return Arrays.stream(content.split(System.lineSeparator())) + .filter(line -> !line.startsWith("#")) + .collect(Collectors.toList()); + } + +} diff --git a/extra/interface/ap/src/test/resources/test/BasicConfig.java b/extra/interface/ap/src/test/resources/test/BasicConfig.java new file mode 100644 index 000000000..bac0e28d7 --- /dev/null +++ b/extra/interface/ap/src/test/resources/test/BasicConfig.java @@ -0,0 +1,10 @@ +package test; + +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + +@ConfigSerializable +public interface BasicConfig { + String hello(); + + void hi(String value); +} diff --git a/extra/interface/ap/src/test/resources/test/BasicConfig.properties b/extra/interface/ap/src/test/resources/test/BasicConfig.properties new file mode 100644 index 000000000..b0e25c9af --- /dev/null +++ b/extra/interface/ap/src/test/resources/test/BasicConfig.properties @@ -0,0 +1 @@ +test.BasicConfig=test.BasicConfigImpl diff --git a/extra/interface/ap/src/test/resources/test/BasicConfigImpl.java b/extra/interface/ap/src/test/resources/test/BasicConfigImpl.java new file mode 100644 index 000000000..5388246f7 --- /dev/null +++ b/extra/interface/ap/src/test/resources/test/BasicConfigImpl.java @@ -0,0 +1,24 @@ +package test; + +import java.lang.Override; +import java.lang.String; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + +/** + * Automatically generated implementation of the config */ +@ConfigSerializable +final class BasicConfigImpl implements BasicConfig { + private String hello; + + private String hi; + + @Override + public String hello() { + return hello; + } + + @Override + public void hi(String value) { + this.hi = value; + } +} diff --git a/extra/interface/ap/src/test/resources/test/ExtendedConfig.java b/extra/interface/ap/src/test/resources/test/ExtendedConfig.java new file mode 100644 index 000000000..40f7eb059 --- /dev/null +++ b/extra/interface/ap/src/test/resources/test/ExtendedConfig.java @@ -0,0 +1,8 @@ +package test; + +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + +@ConfigSerializable +public interface ExtendedConfig extends BasicConfig { + String hi(); +} diff --git a/extra/interface/ap/src/test/resources/test/ExtendedConfig.properties b/extra/interface/ap/src/test/resources/test/ExtendedConfig.properties new file mode 100644 index 000000000..44fe5031b --- /dev/null +++ b/extra/interface/ap/src/test/resources/test/ExtendedConfig.properties @@ -0,0 +1 @@ +test.ExtendedConfig=test.ExtendedConfigImpl diff --git a/extra/interface/ap/src/test/resources/test/ExtendedConfigImpl.java b/extra/interface/ap/src/test/resources/test/ExtendedConfigImpl.java new file mode 100644 index 000000000..a553973f5 --- /dev/null +++ b/extra/interface/ap/src/test/resources/test/ExtendedConfigImpl.java @@ -0,0 +1,29 @@ +package test; + +import java.lang.Override; +import java.lang.String; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + +/** + * Automatically generated implementation of the config */ +@ConfigSerializable +final class ExtendedConfigImpl implements ExtendedConfig { + private String hi; + + private String hello; + + @Override + public String hi() { + return hi; + } + + @Override + public String hello() { + return hello; + } + + @Override + public void hi(String value) { + this.hi = value; + } +} diff --git a/extra/interface/ap/src/test/resources/test/MultiLayerConfig.java b/extra/interface/ap/src/test/resources/test/MultiLayerConfig.java new file mode 100644 index 000000000..48e3ca220 --- /dev/null +++ b/extra/interface/ap/src/test/resources/test/MultiLayerConfig.java @@ -0,0 +1,15 @@ +package test; + +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + +@ConfigSerializable +public interface MultiLayerConfig { + String test(); + SecondLayer second(); + + @ConfigSerializable + public interface SecondLayer { + String test(); + String test2(); + } +} diff --git a/extra/interface/ap/src/test/resources/test/MultiLayerConfig.properties b/extra/interface/ap/src/test/resources/test/MultiLayerConfig.properties new file mode 100644 index 000000000..f68c62a08 --- /dev/null +++ b/extra/interface/ap/src/test/resources/test/MultiLayerConfig.properties @@ -0,0 +1,2 @@ +test.MultiLayerConfig=test.MultiLayerConfigImpl +test.MultiLayerConfig$SecondLayer=test.MultiLayerConfig$SecondLayerImpl diff --git a/extra/interface/ap/src/test/resources/test/MultiLayerConfigImpl.java b/extra/interface/ap/src/test/resources/test/MultiLayerConfigImpl.java new file mode 100644 index 000000000..b38a97b3c --- /dev/null +++ b/extra/interface/ap/src/test/resources/test/MultiLayerConfigImpl.java @@ -0,0 +1,43 @@ +package test; + +import java.lang.Override; +import java.lang.String; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + +/** + * Automatically generated implementation of the config */ +@ConfigSerializable +final class MultiLayerConfigImpl implements MultiLayerConfig { + private String test; + + private MultiLayerConfig.SecondLayer second; + + @Override + public String test() { + return test; + } + + @Override + public MultiLayerConfig.SecondLayer second() { + return second; + } + + /** + * Automatically generated implementation of the config */ + @ConfigSerializable + static final class SecondLayerImpl implements MultiLayerConfig.SecondLayer { + private String test; + + private String test2; + + @Override + public String test() { + return test; + } + + @Override + public String test2() { + return test2; + } + } +} diff --git a/extra/interface/build.gradle b/extra/interface/build.gradle new file mode 100644 index 000000000..e19086955 --- /dev/null +++ b/extra/interface/build.gradle @@ -0,0 +1,10 @@ +plugins { + id "org.spongepowered.configurate.build.component" +} + +description = "Utility classes for generated config interface implementations" + +dependencies { + compileOnly projects.core + testImplementation projects.core +} diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/Constants.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/Constants.java new file mode 100644 index 000000000..67371395c --- /dev/null +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/Constants.java @@ -0,0 +1,35 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.spongepowered.configurate.interfaces; + +/** + * Constants that are used in multiple files. Meant to be used internally. + * + * @since 4.2.0 + */ +public final class Constants { + + private Constants() {} + + /** + * The file location of the interface mappings. + * + * @since 4.2.0 + */ + public static final String MAPPING_FILE = "org/spongepowered/configurate/interfaces/interface_mappings.properties"; + +} diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java new file mode 100644 index 000000000..bec27f021 --- /dev/null +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java @@ -0,0 +1,67 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.spongepowered.configurate.interfaces; + +import org.spongepowered.configurate.ConfigurationOptions; +import org.spongepowered.configurate.serialize.TypeSerializerCollection; + +import java.util.function.UnaryOperator; + +/** + * This class has the default {@link ConfigurationOptions} + * with {@link InterfaceTypeSerializer} added to the serializers. + * + * @since 4.2.0 + */ +public final class InterfaceDefaultOptions { + + private static final ConfigurationOptions DEFAULTS = + ConfigurationOptions.defaults() + .serializers( + TypeSerializerCollection.builder() + .registerAnnotated(InterfaceTypeSerializer::applicable, InterfaceTypeSerializer.INSTANCE) + .registerAll(TypeSerializerCollection.defaults()) + .build() + ); + + private InterfaceDefaultOptions() { + } + + /** + * The default ConfigurationOptions with {@link InterfaceTypeSerializer} added to the serializers. + * + * @return the default ConfigurationOptions with {@link InterfaceTypeSerializer} added to the serializers. + * @since 4.2.0 + */ + public static ConfigurationOptions get() { + return DEFAULTS; + } + + /** + * Sets the default configuration options to be used by the resultant loader + * by providing a function which takes the current {@link #get() default options} + * and applies any desired changes. + * + * @param options to transform the existing default options + * @return the default options with the applied changes + * @since 4.2.0 + */ + public static ConfigurationOptions with(final UnaryOperator options) { + return options.apply(DEFAULTS); + } + +} diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializer.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializer.java new file mode 100644 index 000000000..73ca8b1fd --- /dev/null +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializer.java @@ -0,0 +1,106 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.spongepowered.configurate.interfaces; + +import static io.leangen.geantyref.GenericTypeReflector.erase; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; +import org.spongepowered.configurate.serialize.SerializationException; +import org.spongepowered.configurate.serialize.TypeSerializer; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Type; +import java.net.URL; +import java.util.Locale; +import java.util.Properties; +import java.util.StringJoiner; + +final class InterfaceTypeSerializer implements TypeSerializer { + + public static final InterfaceTypeSerializer INSTANCE = new InterfaceTypeSerializer(); + + private final Properties interfaceMappings = new Properties(); + + public static boolean applicable(final AnnotatedType type) { + return type.isAnnotationPresent(ConfigSerializable.class) && erase(type.getType()).isInterface(); + } + + private InterfaceTypeSerializer() { + final @Nullable URL mappingsUrl = getClass().getClassLoader().getResource(Constants.MAPPING_FILE); + if (mappingsUrl == null) { + return; + } + + try (InputStream stream = mappingsUrl.openStream()) { + this.interfaceMappings.load(stream); + } catch (final IOException exception) { + throw new RuntimeException("Could not load interface mappings!", exception); + } + } + + @Override + public Object deserialize(final Type type, final ConfigurationNode node) throws SerializationException { + final String canonicalName = erase(type).getTypeName(); + final @Nullable String typeImpl = this.interfaceMappings.getProperty(canonicalName); + if (typeImpl == null) { + throw new SerializationException(String.format( + Locale.ROOT, + "No mapping found for type %s. Available mappings: %s", + canonicalName, availableMappings() + )); + } + + final Class implClass; + try { + implClass = Class.forName(typeImpl, true, erase(type).getClassLoader()); + } catch (final ClassNotFoundException exception) { + throw new SerializationException(String.format( + Locale.ROOT, + "Could not find implementation class %s for type %s!", + typeImpl, canonicalName + )); + } + + final @Nullable TypeSerializer serializer = node.options().serializers().get(implClass); + if (serializer == null) { + throw new SerializationException("No serializer found for implementation class " + implClass); + } + return serializer.deserialize(implClass, node); + } + + @Override + public void serialize( + final Type type, + final @Nullable Object obj, + final ConfigurationNode node + ) throws SerializationException { + throw new SerializationException( + "I can only deserialize stuff. Serialization has to be handled by ObjectMapper!" + ); + } + + private String availableMappings() { + final StringJoiner joiner = new StringJoiner(", "); + this.interfaceMappings.keySet().forEach((key) -> joiner.add((CharSequence) key)); + return joiner.toString(); + } + +} diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Exclude.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Exclude.java new file mode 100644 index 000000000..262d7650a --- /dev/null +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Exclude.java @@ -0,0 +1,33 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.spongepowered.configurate.interfaces.meta; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Prevents the annotated method from being handled as a config node. + * This is practically only used for default methods. + * + * @since 4.2.0 + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.METHOD) +public @interface Exclude { +} diff --git a/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializerTest.java b/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializerTest.java new file mode 100644 index 000000000..e5f1420da --- /dev/null +++ b/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializerTest.java @@ -0,0 +1,53 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.spongepowered.configurate.interfaces; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.spongepowered.configurate.BasicConfigurationNode; +import org.spongepowered.configurate.ConfigurateException; + +class InterfaceTypeSerializerTest { + + @Test + void testDeserialization() throws ConfigurateException { + final BasicConfigurationNode node = BasicConfigurationNode.root(InterfaceDefaultOptions.get()); + // doesn't deserialize if value is NullValue + node.node("hello").set("world"); + + final @Nullable TestConfig config = Assertions.assertDoesNotThrow(() -> node.get(TestConfig.class)); + assertNotNull(config); + assertInstanceOf(TestConfigImpl.class, config); + } + + @Test + void testInnerDeserialization() throws ConfigurateException { + final BasicConfigurationNode node = BasicConfigurationNode.root(InterfaceDefaultOptions.get()); + // doesn't deserialize if value is NullValue + node.node("hello").set("world"); + + final TestConfig.@Nullable TestInnerConfig config = + Assertions.assertDoesNotThrow(() -> node.get(TestConfig.TestInnerConfig.class)); + assertNotNull(config); + assertInstanceOf(TestConfigImpl.TestInnerConfig.class, config); + } + +} diff --git a/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TestConfig.java b/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TestConfig.java new file mode 100644 index 000000000..6150dd4ec --- /dev/null +++ b/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TestConfig.java @@ -0,0 +1,27 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.spongepowered.configurate.interfaces; + +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + +@ConfigSerializable +interface TestConfig { + + @ConfigSerializable + interface TestInnerConfig {} + +} diff --git a/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TestConfigImpl.java b/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TestConfigImpl.java new file mode 100644 index 000000000..f548babc8 --- /dev/null +++ b/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TestConfigImpl.java @@ -0,0 +1,27 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.spongepowered.configurate.interfaces; + +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + +@ConfigSerializable +final class TestConfigImpl implements TestConfig { + + @ConfigSerializable + static final class TestInnerConfigImpl implements TestConfig.TestInnerConfig {} + +} diff --git a/extra/interface/src/test/resources/org/spongepowered/configurate/interfaces/interface_mappings.properties b/extra/interface/src/test/resources/org/spongepowered/configurate/interfaces/interface_mappings.properties new file mode 100644 index 000000000..d7460e41e --- /dev/null +++ b/extra/interface/src/test/resources/org/spongepowered/configurate/interfaces/interface_mappings.properties @@ -0,0 +1,2 @@ +org.spongepowered.configurate.interfaces.TestConfig=org.spongepowered.configurate.interfaces.TestConfigImpl +org.spongepowered.configurate.interfaces.TestConfig$TestInnerConfig=org.spongepowered.configurate.interfaces.TestConfigImpl$TestInnerConfigImpl diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 588317477..1d2edb529 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,15 +1,18 @@ [versions] -assertj="3.24.2" -autoValue="1.10.1" -checkerQual="3.35.0" -checkstyle="10.12.0" +assertj = "3.24.2" +autoValue = "1.10.1" +checkerQual = "3.35.0" +checkstyle = "10.12.0" geantyref = "1.3.14" -errorprone="2.19.1" +errorprone = "2.19.1" indra = "3.1.1" -junit="5.9.3" -ktlint="0.49.1" +junit = "5.9.3" +ktlint = "0.49.1" pmd = "6.55.0" spotless = "6.19.0" +javapoet = "1.10.0" +auto-service = "1.1.1" +compile-testing = "0.21.0" [libraries] # Shared @@ -25,11 +28,16 @@ stylecheck = "ca.stellardrift:stylecheck:0.2.1" # Kotlin kotlin-coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2" -kotlin-reflect = {module = "org.jetbrains.kotlin:kotlin-reflect"} # version from Kotlin BOM +kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect" } # version from Kotlin BOM # Core checkerQual = { module = "org.checkerframework:checker-qual", version.ref = "checkerQual" } -geantyref = {module = "io.leangen.geantyref:geantyref", version.ref = "geantyref" } +geantyref = { module = "io.leangen.geantyref:geantyref", version.ref = "geantyref" } + +# Interface +auto-service = { module = "com.google.auto.service:auto-service", version.ref = "auto-service" } +javapoet = { module = "com.squareup:javapoet", version.ref = "javapoet" } +compile-testing = { module = "com.google.testing.compile:compile-testing", version.ref = "compile-testing" } # DFU dfu-v2 = "com.mojang:datafixerupper:2.0.24" diff --git a/settings.gradle b/settings.gradle index 8694b3801..777bd73ed 100644 --- a/settings.gradle +++ b/settings.gradle @@ -55,10 +55,12 @@ rootProject.name = "$prefix-parent" } // extras -["kotlin", "guice", "dfu2", "dfu3", "dfu4"].each { +["kotlin", "guice", "dfu2", "dfu3", "dfu4", "interface"].each { include ":extra:$it" findProject(":extra:$it")?.name = "extra-$it" } +include ":extra:extra-interface:ap" +findProject(":extra:extra-interface:ap")?.name = "extra-interface-ap" includeBuild 'vendor', { name = "configurate-vendor" From efcb2130e0062f071d6a97c04f62b7bf6c0f3d72 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Mon, 3 Jul 2023 09:06:10 +0200 Subject: [PATCH 02/35] Allow all tests to run properly --- extra/interface/ap/build.gradle | 2 +- ...onfigImplementationGeneratorProcessor.java | 32 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/extra/interface/ap/build.gradle b/extra/interface/ap/build.gradle index 702758605..776b5459b 100644 --- a/extra/interface/ap/build.gradle +++ b/extra/interface/ap/build.gradle @@ -16,7 +16,7 @@ dependencies { // there is no javadoc tasks.withType(Javadoc).configureEach { enabled = false } -test { +tasks.withType(Test).configureEach { // See: https://github.com/google/compile-testing/issues/222 jvmArgs '--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED' jvmArgs '--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED' diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java index 9860485ec..dd4f806ec 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java @@ -108,18 +108,6 @@ public boolean process(final Set ignored, final RoundEnvi return false; } - private void writeMappings() { - final FileObject resource; - try { - resource = this.filer.createResource(StandardLocation.SOURCE_OUTPUT, "", Constants.MAPPING_FILE); - try (Writer writer = resource.openWriter()) { - this.mappings.store(writer, null); - } - } catch (final IOException exception) { - throw new RuntimeException("Failed to write interface mappings!", exception); - } - } - /** * Generate a class for the given interface and * returns the name of the generated class. @@ -239,15 +227,23 @@ public SourceVersion getSupportedSourceVersion() { return SourceVersion.latest(); } + private void writeMappings() { + final FileObject resource; + try { + resource = this.filer.createResource(StandardLocation.SOURCE_OUTPUT, "", Constants.MAPPING_FILE); + try (Writer writer = resource.openWriter()) { + this.mappings.store(writer, null); + } + } catch (final IOException exception) { + throw new RuntimeException("Failed to write interface mappings!", exception); + } + } + private boolean hasAnnotation(final AnnotatedConstruct element, final Class annotation) { //noinspection ConstantValue not everything is nonnull by default return element.getAnnotation(annotation) != null; } - private void info(final String message, final Object... arguments) { - this.messager.printMessage(Kind.NOTE, String.format(Locale.ROOT, message, arguments)); - } - private boolean isNestedConfig(final TypeElement type) { if (!type.getNestingKind().isNested()) { return false; @@ -260,4 +256,8 @@ private boolean isNestedConfig(final TypeElement type) { return current.getKind() == ElementKind.PACKAGE; } + private void info(final String message, final Object... arguments) { + this.messager.printMessage(Kind.NOTE, String.format(Locale.ROOT, message, arguments)); + } + } From b29d24134ca77cdd71aa87e0e48ba029662b8ee1 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 3 Jan 2024 19:23:27 +0100 Subject: [PATCH 03/35] Split some classes and added Range annotations --- .gitignore | 2 +- .../ConfigImplementationGenerator.java | 137 ++++++++++++++++ ...onfigImplementationGeneratorProcessor.java | 147 ++---------------- .../interfaces/processor/Utils.java | 32 ++++ .../interfaces/InterfaceConstraints.java | 74 +++++++++ .../interfaces/InterfaceDefaultOptions.java | 4 +- .../configurate/interfaces/meta/Exclude.java | 3 +- .../interfaces/meta/range/DecimalRange.java | 35 +++++ .../interfaces/meta/range/NumericRange.java | 35 +++++ .../interfaces/meta/range/StringRange.java | 39 +++++ 10 files changed, 368 insertions(+), 140 deletions(-) create mode 100644 extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java create mode 100644 extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/Utils.java create mode 100644 extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceConstraints.java create mode 100644 extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/DecimalRange.java create mode 100644 extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/NumericRange.java create mode 100644 extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/StringRange.java diff --git a/.gitignore b/.gitignore index cebdde1b1..cb5617ec4 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,7 @@ build-logic/*/bin/ /format/*/.apt_generated/ /format/*/.apt_generated_tests/ /format/*/.checkstyle -/extra/*/build/ +/extra/**/build/ /extra/*/out/ /extra/*/bin/ /extra/*/.factorypath diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java new file mode 100644 index 000000000..a02c066e4 --- /dev/null +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java @@ -0,0 +1,137 @@ +package org.spongepowered.configurate.interfaces.processor; + +import static org.spongepowered.configurate.interfaces.processor.Utils.hasAnnotation; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import java.util.List; +import java.util.Locale; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import org.spongepowered.configurate.interfaces.meta.Exclude; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + +class ConfigImplementationGenerator { + + private final ConfigImplementationGeneratorProcessor processor; + private final TypeElement source; + + ConfigImplementationGenerator( + final ConfigImplementationGeneratorProcessor processor, + final TypeElement configInterfaceType + ) { + this.processor = processor; + this.source = configInterfaceType; + } + + public TypeSpec.Builder generate() { + final ClassName className = ClassName.get(this.source); + + this.processor.info("Generating implementation for %s", this.source); + + final TypeSpec.Builder spec = TypeSpec + .classBuilder(className.simpleName() + "Impl") + .addSuperinterface(className) + .addModifiers(Modifier.FINAL) + .addAnnotation(ConfigSerializable.class) + .addJavadoc("Automatically generated implementation of the config"); + + final TypeSpecBuilderTracker tracker = new TypeSpecBuilderTracker(); + gatherElementSpec(tracker, this.source); + tracker.writeTo(spec); + + final String qualifiedName = className.reflectionName(); + this.processor.generatedClasses().put(qualifiedName, qualifiedName + "Impl"); + this.processor.info("Generated implementation for %s", this.source); + + return spec; + } + + private void gatherElementSpec( + final TypeSpecBuilderTracker spec, + final TypeElement type + ) { + // first handle own elements + + for (final Element enclosedElement : type.getEnclosedElements()) { + final ElementKind kind = enclosedElement.getKind(); + + if (kind == ElementKind.INTERFACE && hasAnnotation(enclosedElement, ConfigSerializable.class)) { + spec.add( + enclosedElement.getSimpleName().toString(), + new ConfigImplementationGenerator(this.processor, (TypeElement) enclosedElement) + .generate() + .addModifiers(Modifier.STATIC) + ); + continue; + } + + if (kind != ElementKind.METHOD) { + continue; + } + + final ExecutableElement element = (ExecutableElement) enclosedElement; + + final boolean excluded = hasAnnotation(element, Exclude.class); + if (element.isDefault()) { + if (excluded) { + // no need to handle them + continue; + } + this.processor.info("Overriding implementation for %s as it's not excluded", element); + } else if (excluded) { + throw new IllegalStateException(String.format( + Locale.ROOT, + "Cannot make config due to method %s, which is a method excluded method that has no implementation!", + element + )); + } + + final List parameters = element.getParameters(); + if (parameters.size() > 1) { + throw new IllegalStateException("Setters cannot have more than one parameter! Method: " + element); + } + + final String simpleName = element.getSimpleName().toString(); + TypeMirror nodeType = element.getReturnType(); + + if (parameters.size() == 1) { + final VariableElement parameter = parameters.get(0); + // setter + spec.add( + simpleName + "#" + parameter.getSimpleName().toString(), + MethodSpec.overriding(element) + .addStatement( + "this.$N = $N", + element.getSimpleName(), + parameter.getSimpleName() + ) + ); + nodeType = parameter.asType(); + } else { + // getter + spec.add( + simpleName, + MethodSpec.overriding(element) + .addStatement("return $N", element.getSimpleName()) + ); + } + + spec.add(simpleName, FieldSpec.builder(TypeName.get(nodeType), simpleName, Modifier.PRIVATE)); + } + + // then handle parent elements + for (final TypeMirror parent : type.getInterfaces()) { + gatherElementSpec(spec, (TypeElement) this.processor.typeUtils.asElement(parent)); + } + } + +} diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java index dd4f806ec..1ec2c0915 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java @@ -16,51 +16,40 @@ */ package org.spongepowered.configurate.interfaces.processor; +import static org.spongepowered.configurate.interfaces.processor.Utils.isNestedConfig; + import com.google.auto.service.AutoService; import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.JavaFile; -import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; -import org.spongepowered.configurate.interfaces.Constants; -import org.spongepowered.configurate.interfaces.meta.Exclude; -import org.spongepowered.configurate.objectmapping.ConfigSerializable; - import java.io.IOException; import java.io.Writer; -import java.lang.annotation.Annotation; import java.util.Collections; -import java.util.List; import java.util.Locale; import java.util.Properties; import java.util.Set; - import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Filer; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; -import javax.lang.model.AnnotatedConstruct; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; import javax.tools.Diagnostic.Kind; import javax.tools.FileObject; import javax.tools.StandardLocation; +import org.spongepowered.configurate.interfaces.Constants; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; @AutoService(Processor.class) class ConfigImplementationGeneratorProcessor extends AbstractProcessor { private final Properties mappings = new Properties(); - private Types typeUtils; + Types typeUtils; private Filer filer; private Messager messager; @@ -99,7 +88,7 @@ public boolean process(final Set ignored, final RoundEnvi } try { - processInterface(typeElement, this.mappings); + processInterface(typeElement); } catch (final IOException exception) { throw new RuntimeException(exception); } @@ -112,116 +101,15 @@ public boolean process(final Set ignored, final RoundEnvi * Generate a class for the given interface and * returns the name of the generated class. */ - private void processInterface(final TypeElement type, final Properties generatedClasses) throws IOException { + private void processInterface(final TypeElement type) throws IOException { final ClassName className = ClassName.get(type); - final TypeSpec spec = generateImplementation(type, generatedClasses).build(); + final TypeSpec spec = new ConfigImplementationGenerator(this, type).generate().build(); JavaFile.builder(className.packageName(), spec) .build() .writeTo(this.filer); } - private TypeSpec.Builder generateImplementation(final TypeElement type, final Properties generatedClasses) { - final ClassName className = ClassName.get(type); - - info("Generating implementation for %s", type); - - final TypeSpec.Builder spec = TypeSpec - .classBuilder(className.simpleName() + "Impl") - .addSuperinterface(className) - .addModifiers(Modifier.FINAL) - .addAnnotation(ConfigSerializable.class) - .addJavadoc("Automatically generated implementation of the config"); - - final TypeSpecBuilderTracker tracker = new TypeSpecBuilderTracker(); - gatherElementSpec(tracker, type, generatedClasses); - tracker.writeTo(spec); - - final String qualifiedName = className.reflectionName(); - generatedClasses.put(qualifiedName, qualifiedName + "Impl"); - info("Generated implementation for %s", type); - - return spec; - } - - private void gatherElementSpec( - final TypeSpecBuilderTracker spec, - final TypeElement type, - final Properties generatedClasses - ) { - // first handle own elements - - for (final Element enclosedElement : type.getEnclosedElements()) { - final ElementKind kind = enclosedElement.getKind(); - - if (kind == ElementKind.INTERFACE && hasAnnotation(enclosedElement, ConfigSerializable.class)) { - spec.add( - enclosedElement.getSimpleName().toString(), - generateImplementation((TypeElement) enclosedElement, generatedClasses) - .addModifiers(Modifier.STATIC) - ); - continue; - } - if (kind != ElementKind.METHOD) { - continue; - } - - final ExecutableElement element = (ExecutableElement) enclosedElement; - - final boolean excluded = hasAnnotation(element, Exclude.class); - if (element.isDefault()) { - if (excluded) { - // no need to handle them - continue; - } - info("Overriding implementation for %s as it's not excluded", element); - } else if (excluded) { - throw new IllegalStateException(String.format( - Locale.ROOT, - "Cannot make config due to method %s, which is an excluded method that has no implementation!", - element - )); - } - - final List parameters = element.getParameters(); - if (parameters.size() > 1) { - throw new IllegalStateException("Setters cannot have more than one parameter! Method: " + element); - } - - final String simpleName = element.getSimpleName().toString(); - TypeMirror nodeType = element.getReturnType(); - - if (parameters.size() == 1) { - final VariableElement parameter = parameters.get(0); - // setter - spec.add( - simpleName + "#" + parameter.getSimpleName().toString(), - MethodSpec.overriding(element) - .addStatement( - "this.$N = $N", - element.getSimpleName(), - parameter.getSimpleName() - ) - ); - nodeType = parameter.asType(); - } else { - // getter - spec.add( - simpleName, - MethodSpec.overriding(element) - .addStatement("return $N", element.getSimpleName()) - ); - } - - spec.add(simpleName, FieldSpec.builder(TypeName.get(nodeType), simpleName, Modifier.PRIVATE)); - } - - // then handle parent elements - for (final TypeMirror parent : type.getInterfaces()) { - gatherElementSpec(spec, (TypeElement) this.typeUtils.asElement(parent), generatedClasses); - } - } - @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latest(); @@ -239,24 +127,11 @@ private void writeMappings() { } } - private boolean hasAnnotation(final AnnotatedConstruct element, final Class annotation) { - //noinspection ConstantValue not everything is nonnull by default - return element.getAnnotation(annotation) != null; - } - - private boolean isNestedConfig(final TypeElement type) { - if (!type.getNestingKind().isNested()) { - return false; - } - - Element current = type; - while (current.getKind() == ElementKind.INTERFACE && hasAnnotation(current, ConfigSerializable.class)) { - current = current.getEnclosingElement(); - } - return current.getKind() == ElementKind.PACKAGE; + Properties generatedClasses() { + return this.mappings; } - private void info(final String message, final Object... arguments) { + void info(final String message, final Object... arguments) { this.messager.printMessage(Kind.NOTE, String.format(Locale.ROOT, message, arguments)); } diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/Utils.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/Utils.java new file mode 100644 index 000000000..b49b9dcd3 --- /dev/null +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/Utils.java @@ -0,0 +1,32 @@ +package org.spongepowered.configurate.interfaces.processor; + +import java.lang.annotation.Annotation; +import javax.lang.model.AnnotatedConstruct; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + +final class Utils { + + private Utils() { + } + + public static boolean hasAnnotation(final AnnotatedConstruct element, final Class annotation) { + //noinspection ConstantValue not everything is nonnull by default + return element.getAnnotation(annotation) != null; + } + + public static boolean isNestedConfig(final TypeElement type) { + if (!type.getNestingKind().isNested()) { + return false; + } + + Element current = type; + while (current.getKind() == ElementKind.INTERFACE && hasAnnotation(current, ConfigSerializable.class)) { + current = current.getEnclosingElement(); + } + return current.getKind() == ElementKind.PACKAGE; + } + +} diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceConstraints.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceConstraints.java new file mode 100644 index 000000000..a8ff1fe3a --- /dev/null +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceConstraints.java @@ -0,0 +1,74 @@ +package org.spongepowered.configurate.interfaces; + +import org.spongepowered.configurate.interfaces.meta.range.DecimalRange; +import org.spongepowered.configurate.interfaces.meta.range.NumericRange; +import org.spongepowered.configurate.interfaces.meta.range.StringRange; +import org.spongepowered.configurate.objectmapping.ObjectMapper; +import org.spongepowered.configurate.objectmapping.meta.Constraint; +import org.spongepowered.configurate.serialize.SerializationException; + +final class InterfaceConstraints { + + private InterfaceConstraints() { + } + + static ObjectMapper.Factory buildObjectMapperWithConstraints() { + return ObjectMapper.factoryBuilder() + .addConstraint(DecimalRange.class, Number.class, decimalRange()) + .addConstraint(NumericRange.class, Number.class, numericRange()) + .addConstraint(StringRange.class, String.class, stringRange()) + .build(); + } + + private static Constraint.Factory decimalRange() { + return (data, type) -> number -> { + // Null requirement is part of @Required + if (number == null) { + return; + } + + final double value = number.doubleValue(); + if (!(data.from() >= value && data.to() <= value)) { + throw new SerializationException(String.format( + "'%s' is not in the allowed range of from: %s, to: %s!", + value, data.from(), data.to() + )); + } + }; + } + + private static Constraint.Factory numericRange() { + return (data, type) -> number -> { + // Null requirement is part of @Required + if (number == null) { + return; + } + + final long value = number.longValue(); + if (!(data.from() >= value && data.to() <= value)) { + throw new SerializationException(String.format( + "'%s' is not in the allowed range of from: %s, to: %s!", + value, data.from(), data.to() + )); + } + }; + } + + private static Constraint.Factory stringRange() { + return (data, type) -> string -> { + // Null requirement is part of @Required + if (string == null) { + return; + } + + final int length = string.length(); + if (!(data.from() >= length && data.to() <= length)) { + throw new SerializationException(String.format( + "'%s' is not in the allowed string length range of from: %s, to: %s!", + length, data.from(), data.to() + )); + } + }; + } + +} diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java index bec27f021..44f61297b 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java @@ -16,11 +16,10 @@ */ package org.spongepowered.configurate.interfaces; +import java.util.function.UnaryOperator; import org.spongepowered.configurate.ConfigurationOptions; import org.spongepowered.configurate.serialize.TypeSerializerCollection; -import java.util.function.UnaryOperator; - /** * This class has the default {@link ConfigurationOptions} * with {@link InterfaceTypeSerializer} added to the serializers. @@ -34,6 +33,7 @@ public final class InterfaceDefaultOptions { .serializers( TypeSerializerCollection.builder() .registerAnnotated(InterfaceTypeSerializer::applicable, InterfaceTypeSerializer.INSTANCE) + .registerAnnotatedObjects(InterfaceConstraints.buildObjectMapperWithConstraints()) .registerAll(TypeSerializerCollection.defaults()) .build() ); diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Exclude.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Exclude.java index 262d7650a..117f379c8 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Exclude.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Exclude.java @@ -23,7 +23,8 @@ /** * Prevents the annotated method from being handled as a config node. - * This is practically only used for default methods. + * This is practically only used for default methods, as normal interface + * methods have to have an implementation. * * @since 4.2.0 */ diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/DecimalRange.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/DecimalRange.java new file mode 100644 index 000000000..1bc534853 --- /dev/null +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/DecimalRange.java @@ -0,0 +1,35 @@ +package org.spongepowered.configurate.interfaces.meta.range; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation limits the values that a config node can have. + * Because of annotation limits, there is an annotation for: + * {@link DecimalRange decimals}, {@link NumericRange numerics} and + * {@link StringRange String length}. + * + * @since 4.2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface DecimalRange { + /** + * The minimal value allowed (inclusive.) + * + * @return the minimal value allowed (inclusive.) + * @since 4.2.0 + */ + double from(); + + /** + * The maximal value allowed (inclusive.) + * + * @return the maximal value allowed (inclusive.) + * @since 4.2.0 + */ + double to(); + +} diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/NumericRange.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/NumericRange.java new file mode 100644 index 000000000..8e8f2720c --- /dev/null +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/NumericRange.java @@ -0,0 +1,35 @@ +package org.spongepowered.configurate.interfaces.meta.range; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation limits the values that a config node can have. + * Because of annotation limits, there is an annotation for: + * {@link DecimalRange decimals}, {@link NumericRange numerics} and + * {@link StringRange String length}. + * + * @since 4.2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface NumericRange { + /** + * The minimal value allowed (inclusive.) + * + * @return the minimal value allowed (inclusive.) + * @since 4.2.0 + */ + long from(); + + /** + * The maximal value allowed (inclusive.) + * + * @return the maximal value allowed (inclusive.) + * @since 4.2.0 + */ + long to(); + +} diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/StringRange.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/StringRange.java new file mode 100644 index 000000000..4a1e13bb8 --- /dev/null +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/StringRange.java @@ -0,0 +1,39 @@ +package org.spongepowered.configurate.interfaces.meta.range; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation limits the values that a config node can have. + * Because of annotation limits, there is an annotation for: + * {@link DecimalRange decimals}, {@link NumericRange numerics} and + * {@link StringRange String length}. + * + *

When the String is null, the range validation is skipped. Use + * {@link org.spongepowered.configurate.objectmapping.meta.Required Required} + * if null shouldn't be allowed.

+ * + * @since 4.2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface StringRange { + /** + * The minimal String length allowed (inclusive.) + * + * @return the minimal value allowed (inclusive.) + * @since 4.2.0 + */ + int from(); + + /** + * The maximal String length allowed (inclusive.) + * + * @return the maximal value allowed (inclusive.) + * @since 4.2.0 + */ + int to(); + +} From e4b7f7ffb95f59928a5f131688bb479e47bfe412 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 3 Jan 2024 21:51:22 +0100 Subject: [PATCH 04/35] Added default value annotations and restructured tests --- .../ConfigImplementationGenerator.java | 9 +- .../processor/DefaultAnnotations.java | 71 +++++++++ .../processor/TypeSpecBuilderTracker.java | 8 +- .../interfaces/processor/Utils.java | 16 +- .../ConfigImplementationGenerationTest.java | 83 +--------- .../processor/DefaultValueTest.java | 20 +++ .../interfaces/processor/TestUtils.java | 80 ++++++++++ .../test/defaults/CorrectDefaults.java | 67 ++++++++ .../test/defaults/CorrectDefaults.properties | 1 + .../test/defaults/CorrectDefaultsImpl.java | 143 ++++++++++++++++++ .../test/defaults/MultipleDefaults.java | 22 +++ .../test/defaults/MultipleDefaults.properties | 1 + .../test/defaults/MultipleDefaultsImpl.java | 29 ++++ .../meta/defaults/DefaultBoolean.java | 28 ++++ .../meta/defaults/DefaultDecimal.java | 28 ++++ .../meta/defaults/DefaultNumeric.java | 28 ++++ .../meta/defaults/DefaultString.java | 28 ++++ 17 files changed, 580 insertions(+), 82 deletions(-) create mode 100644 extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/DefaultAnnotations.java create mode 100644 extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/DefaultValueTest.java create mode 100644 extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java create mode 100644 extra/interface/ap/src/test/resources/test/defaults/CorrectDefaults.java create mode 100644 extra/interface/ap/src/test/resources/test/defaults/CorrectDefaults.properties create mode 100644 extra/interface/ap/src/test/resources/test/defaults/CorrectDefaultsImpl.java create mode 100644 extra/interface/ap/src/test/resources/test/defaults/MultipleDefaults.java create mode 100644 extra/interface/ap/src/test/resources/test/defaults/MultipleDefaults.properties create mode 100644 extra/interface/ap/src/test/resources/test/defaults/MultipleDefaultsImpl.java create mode 100644 extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultBoolean.java create mode 100644 extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultDecimal.java create mode 100644 extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultNumeric.java create mode 100644 extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultString.java diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java index a02c066e4..513ea37ee 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java @@ -95,6 +95,8 @@ private void gatherElementSpec( )); } + // all methods are either setters or getters past this point + final List parameters = element.getParameters(); if (parameters.size() > 1) { throw new IllegalStateException("Setters cannot have more than one parameter! Method: " + element); @@ -125,7 +127,12 @@ private void gatherElementSpec( ); } - spec.add(simpleName, FieldSpec.builder(TypeName.get(nodeType), simpleName, Modifier.PRIVATE)); + final FieldSpec.Builder fieldSpec = FieldSpec.builder(TypeName.get(nodeType), simpleName, Modifier.PRIVATE); + + // add default value if it has any + DefaultAnnotations.process(element, nodeType, fieldSpec); + + spec.add(simpleName, fieldSpec); } // then handle parent elements diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/DefaultAnnotations.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/DefaultAnnotations.java new file mode 100644 index 000000000..891c089d9 --- /dev/null +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/DefaultAnnotations.java @@ -0,0 +1,71 @@ +package org.spongepowered.configurate.interfaces.processor; + +import com.google.auto.common.MoreTypes; +import com.squareup.javapoet.FieldSpec; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultBoolean; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultDecimal; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultNumeric; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultString; + +final class DefaultAnnotations { + + private DefaultAnnotations() {} + + static void process(final ExecutableElement element, final TypeMirror nodeType, final FieldSpec.Builder fieldSpec) { + final @Nullable DefaultBoolean defaultBoolean = element.getAnnotation(DefaultBoolean.class); + final @Nullable DefaultDecimal defaultDecimal = element.getAnnotation(DefaultDecimal.class); + final @Nullable DefaultNumeric defaultNumeric = element.getAnnotation(DefaultNumeric.class); + final @Nullable DefaultString defaultString = element.getAnnotation(DefaultString.class); + //noinspection ConstantValue not everything is nonnull by default + final boolean hasDefault = defaultBoolean != null || defaultDecimal != null || defaultNumeric != null || defaultString != null; + + @Nullable Object defaultValue = null; + boolean isString = false; + //noinspection ConstantValue + if (hasDefault) { + if (MoreTypes.isTypeOf(Boolean.TYPE, nodeType)) { + //noinspection ConstantValue + if (defaultBoolean == null) { + throw new IllegalStateException("A default value of the incorrect type was provided for " + element); + } + defaultValue = defaultBoolean.value(); + } + + if (Utils.isDecimal(nodeType)) { + //noinspection ConstantValue + if (defaultDecimal == null) { + throw new IllegalStateException("A default value of the incorrect type was provided for " + element); + } + defaultValue = defaultDecimal.value() + (MoreTypes.isTypeOf(Float.TYPE, nodeType) ? "F" : "D"); + } + + if (Utils.isNumeric(nodeType)) { + //noinspection ConstantValue + if (defaultNumeric == null) { + throw new IllegalStateException("A default value of the incorrect type was provided for " + element); + } + defaultValue = defaultNumeric.value(); + if (MoreTypes.isTypeOf(Long.TYPE, nodeType)) { + defaultValue += "L"; + } + } + + if (MoreTypes.isTypeOf(String.class, nodeType)) { + //noinspection ConstantValue + if (defaultString == null) { + throw new IllegalStateException("A default value of the incorrect type was provided for " + element); + } + defaultValue = defaultString.value(); + isString = true; + } + } + + if (defaultValue != null) { + fieldSpec.initializer(isString ? "$S" : "$L", defaultValue); + } + } + +} diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java index 72e43fb5f..37471cdc4 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java @@ -39,7 +39,13 @@ class TypeSpecBuilderTracker { void add(final String fieldIdentifier, final FieldSpec.Builder builder) { final FieldSpec.Builder existing = this.fieldSpecs.get(fieldIdentifier); if (existing != null) { - existing.addAnnotations(originalAnnotations(existing.build().annotations, builder.build().annotations)); + final FieldSpec existingBuild = existing.build(); + final FieldSpec builderBuild = builder.build(); + // copy initializer of the builder to the existing one if the existing one doesn't have an initializer + if (existingBuild.initializer.isEmpty() && !builderBuild.initializer.isEmpty()) { + existing.initializer(builderBuild.initializer); + } + existing.addAnnotations(originalAnnotations(existingBuild.annotations, builderBuild.annotations)); return; } this.fieldSpecs.put(fieldIdentifier, builder); diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/Utils.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/Utils.java index b49b9dcd3..8443ec089 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/Utils.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/Utils.java @@ -1,10 +1,12 @@ package org.spongepowered.configurate.interfaces.processor; +import com.google.auto.common.MoreTypes; import java.lang.annotation.Annotation; import javax.lang.model.AnnotatedConstruct; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; import org.spongepowered.configurate.objectmapping.ConfigSerializable; final class Utils { @@ -12,12 +14,12 @@ final class Utils { private Utils() { } - public static boolean hasAnnotation(final AnnotatedConstruct element, final Class annotation) { + static boolean hasAnnotation(final AnnotatedConstruct element, final Class annotation) { //noinspection ConstantValue not everything is nonnull by default return element.getAnnotation(annotation) != null; } - public static boolean isNestedConfig(final TypeElement type) { + static boolean isNestedConfig(final TypeElement type) { if (!type.getNestingKind().isNested()) { return false; } @@ -29,4 +31,14 @@ public static boolean isNestedConfig(final TypeElement type) { return current.getKind() == ElementKind.PACKAGE; } + static boolean isDecimal(final TypeMirror typeMirror) { + return MoreTypes.isTypeOf(Float.TYPE, typeMirror) || MoreTypes.isTypeOf(Double.TYPE, typeMirror); + } + + static boolean isNumeric(final TypeMirror typeMirror) { + return MoreTypes.isTypeOf(Byte.TYPE, typeMirror) || MoreTypes.isTypeOf(Character.TYPE, typeMirror) + || MoreTypes.isTypeOf(Short.TYPE, typeMirror) || MoreTypes.isTypeOf(Integer.TYPE, typeMirror) + || MoreTypes.isTypeOf(Long.TYPE, typeMirror); + } + } diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java index c55e59345..4da4388d5 100644 --- a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java @@ -16,103 +16,30 @@ */ package org.spongepowered.configurate.interfaces.processor; -import static com.google.testing.compile.CompilationSubject.assertThat; -import static com.google.testing.compile.Compiler.javac; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertIterableEquals; +import static org.spongepowered.configurate.interfaces.processor.TestUtils.EXPECT_CONFIG_AND_MAPPING; +import static org.spongepowered.configurate.interfaces.processor.TestUtils.testCompilation; -import com.google.common.io.Resources; -import com.google.testing.compile.Compilation; -import com.google.testing.compile.JavaFileObjects; import org.junit.experimental.runners.Enclosed; import org.junit.jupiter.api.Test; import org.junit.runner.RunWith; -import org.spongepowered.configurate.interfaces.Constants; - -import java.io.IOException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import javax.tools.StandardLocation; @RunWith(Enclosed.class) class ConfigImplementationGenerationTest { @Test void testBasicCompilation() { - // expect generated config + mapping - testCompilation("test/BasicConfig", 2); + testCompilation("test/BasicConfig", EXPECT_CONFIG_AND_MAPPING); } @Test void testMultiLayerCompilation() { // expect generated config + mapping - testCompilation("test/MultiLayerConfig", 2); + testCompilation("test/MultiLayerConfig", EXPECT_CONFIG_AND_MAPPING); } @Test void testExtendedCompilation() { // expect generated config + mapping - testCompilation("test/ExtendedConfig", 2); + testCompilation("test/ExtendedConfig", EXPECT_CONFIG_AND_MAPPING); } - - /** - * Tests whether the compilation is successful and - * that the correct mappings have been made - */ - static Compilation testCompilation(final String sourceResourceName, final int expectedSourceCount) { - final Compilation compilation = - javac() - .withProcessors(new ConfigImplementationGeneratorProcessor()) - .compile(JavaFileObjects.forResource(sourceResourceName + ".java")); - - final String targetResourceName = sourceResourceName + "Impl"; - final String targetSourceName = targetResourceName.replace('/', '.'); - - assertThat(compilation).succeeded(); - assertThat(compilation) - .generatedSourceFile(targetSourceName) - .hasSourceEquivalentTo(JavaFileObjects.forResource(targetResourceName + ".java")); - - try { - final URL localMappings = Resources.getResource(sourceResourceName + ".properties"); - - final String actualContent = compilation - .generatedFile(StandardLocation.SOURCE_OUTPUT, Constants.MAPPING_FILE) - .orElseThrow(() -> new IllegalStateException("Expected the interface mappings file to be created")) - .getCharContent(false) - .toString(); - - final List expectedLines = - Resources - .asCharSource(localMappings, StandardCharsets.UTF_8) - .readLines(); - - assertIterableEquals(expectedLines, removeComments(actualContent)); - } catch (final IOException exception) { - throw new RuntimeException(exception); - } - - if (expectedSourceCount != -1) { - // can't use compilation.generatedSourceFiles because the mapping - // file is written to the source output, but isn't a source file - assertEquals( - expectedSourceCount, - compilation.generatedFiles().stream() - .filter(file -> file.getName().startsWith("/SOURCE_OUTPUT/")) - .count() - ); - } - return compilation; - } - - static List removeComments(final String content) { - return Arrays.stream(content.split(System.lineSeparator())) - .filter(line -> !line.startsWith("#")) - .collect(Collectors.toList()); - } - } diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/DefaultValueTest.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/DefaultValueTest.java new file mode 100644 index 000000000..b20dd02f8 --- /dev/null +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/DefaultValueTest.java @@ -0,0 +1,20 @@ +package org.spongepowered.configurate.interfaces.processor; + +import static org.spongepowered.configurate.interfaces.processor.TestUtils.EXPECT_CONFIG_AND_MAPPING; +import static org.spongepowered.configurate.interfaces.processor.TestUtils.testCompilation; + +import org.junit.jupiter.api.Test; + +public class DefaultValueTest { + + @Test + void testCorrectDefaults() { + testCompilation("test/defaults/CorrectDefaults", EXPECT_CONFIG_AND_MAPPING); + } + + @Test + void testMultipleDefaults() { + testCompilation("test/defaults/MultipleDefaults", EXPECT_CONFIG_AND_MAPPING); + } + +} diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java new file mode 100644 index 000000000..9a4ad677c --- /dev/null +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java @@ -0,0 +1,80 @@ +package org.spongepowered.configurate.interfaces.processor; + +import static com.google.testing.compile.CompilationSubject.assertThat; +import static com.google.testing.compile.Compiler.javac; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; + +import com.google.common.io.Resources; +import com.google.testing.compile.Compilation; +import com.google.testing.compile.JavaFileObjects; +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import javax.tools.StandardLocation; +import org.spongepowered.configurate.interfaces.Constants; + +class TestUtils { + + static final int EXPECT_CONFIG_AND_MAPPING = 2; + + /** + * Tests whether the compilation is successful and + * that the correct mappings have been made + */ + static Compilation testCompilation(final String sourceResourceName, final int expectedSourceCount) { + final Compilation compilation = + javac() + .withProcessors(new ConfigImplementationGeneratorProcessor()) + .compile(JavaFileObjects.forResource(sourceResourceName + ".java")); + + final String targetResourceName = sourceResourceName + "Impl"; + final String targetSourceName = targetResourceName.replace('/', '.'); + + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile(targetSourceName) + .hasSourceEquivalentTo(JavaFileObjects.forResource(targetResourceName + ".java")); + + try { + final URL localMappings = Resources.getResource(sourceResourceName + ".properties"); + + final String actualContent = compilation + .generatedFile(StandardLocation.SOURCE_OUTPUT, Constants.MAPPING_FILE) + .orElseThrow(() -> new IllegalStateException("Expected the interface mappings file to be created")) + .getCharContent(false) + .toString(); + + final List expectedLines = + Resources + .asCharSource(localMappings, StandardCharsets.UTF_8) + .readLines(); + + assertIterableEquals(expectedLines, removeComments(actualContent)); + } catch (final IOException exception) { + throw new RuntimeException(exception); + } + + if (expectedSourceCount != -1) { + // can't use compilation.generatedSourceFiles because the mapping + // file is written to the source output, but isn't a source file + assertEquals( + expectedSourceCount, + compilation.generatedFiles().stream() + .filter(file -> file.getName().startsWith("/SOURCE_OUTPUT/")) + .count() + ); + } + return compilation; + } + + static List removeComments(final String content) { + return Arrays.stream(content.split(System.lineSeparator())) + .filter(line -> !line.startsWith("#")) + .collect(Collectors.toList()); + } + +} diff --git a/extra/interface/ap/src/test/resources/test/defaults/CorrectDefaults.java b/extra/interface/ap/src/test/resources/test/defaults/CorrectDefaults.java new file mode 100644 index 000000000..3f78049cd --- /dev/null +++ b/extra/interface/ap/src/test/resources/test/defaults/CorrectDefaults.java @@ -0,0 +1,67 @@ +package test.defaults; + +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultBoolean; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultDecimal; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultNumeric; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultString; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + +@ConfigSerializable +public interface CorrectDefaults { + @DefaultBoolean + boolean apple(); + + @DefaultBoolean(true) + boolean blueberry(); + + @DefaultDecimal + float cherry(); + + @DefaultDecimal(123.5) + float dragonfruit(); + + @DefaultDecimal + double eggplant(); + + @DefaultDecimal(234.23) + double fig(); + + @DefaultNumeric + byte grape(); + + @DefaultNumeric(127) + byte huckleberry(); + + @DefaultNumeric + char italianPrunePlum(); + + @DefaultNumeric(126) + char jackfruit(); + + @DefaultNumeric('c') + char kiwi(); + + @DefaultNumeric + short lemon(); + + @DefaultNumeric(1341) + short mango(); + + @DefaultNumeric + int nectarine(); + + @DefaultNumeric(1231241) + int orange(); + + @DefaultNumeric + long pineapple(); + + @DefaultNumeric(24524524521L) + long quince(); + + @DefaultString + String raspberry(); + + @DefaultString("Hello world!") + String strawberry(); +} diff --git a/extra/interface/ap/src/test/resources/test/defaults/CorrectDefaults.properties b/extra/interface/ap/src/test/resources/test/defaults/CorrectDefaults.properties new file mode 100644 index 000000000..7f15ddd62 --- /dev/null +++ b/extra/interface/ap/src/test/resources/test/defaults/CorrectDefaults.properties @@ -0,0 +1 @@ +test.defaults.CorrectDefaults=test.defaults.CorrectDefaultsImpl diff --git a/extra/interface/ap/src/test/resources/test/defaults/CorrectDefaultsImpl.java b/extra/interface/ap/src/test/resources/test/defaults/CorrectDefaultsImpl.java new file mode 100644 index 000000000..9db2bb247 --- /dev/null +++ b/extra/interface/ap/src/test/resources/test/defaults/CorrectDefaultsImpl.java @@ -0,0 +1,143 @@ +package test.defaults; + +import java.lang.Override; +import java.lang.String; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + +/** + * Automatically generated implementation of the config */ +@ConfigSerializable +final class CorrectDefaultsImpl implements CorrectDefaults { + private boolean apple = false; + + private boolean blueberry = true; + + private float cherry = 0.0F; + + private float dragonfruit = 123.5F; + + private double eggplant = 0.0D; + + private double fig = 234.23D; + + private byte grape = 0; + + private byte huckleberry = 127; + + private char italianPrunePlum = 0; + + private char jackfruit = 126; + + private char kiwi = 99; + + private short lemon = 0; + + private short mango = 1341; + + private int nectarine = 0; + + private int orange = 1231241; + + private long pineapple = 0L; + + private long quince = 24524524521L; + + private String raspberry = ""; + + private String strawberry = "Hello world!"; + + @Override + public boolean apple() { + return apple; + } + + @Override + public boolean blueberry() { + return blueberry; + } + + @Override + public float cherry() { + return cherry; + } + + @Override + public float dragonfruit() { + return dragonfruit; + } + + @Override + public double eggplant() { + return eggplant; + } + + @Override + public double fig() { + return fig; + } + + @Override + public byte grape() { + return grape; + } + + @Override + public byte huckleberry() { + return huckleberry; + } + + @Override + public char italianPrunePlum() { + return italianPrunePlum; + } + + @Override + public char jackfruit() { + return jackfruit; + } + + @Override + public char kiwi() { + return kiwi; + } + + @Override + public short lemon() { + return lemon; + } + + @Override + public short mango() { + return mango; + } + + @Override + public int nectarine() { + return nectarine; + } + + @Override + public int orange() { + return orange; + } + + @Override + public long pineapple() { + return pineapple; + } + + @Override + public long quince() { + return quince; + } + + @Override + public String raspberry() { + return raspberry; + } + + @Override + public String strawberry() { + return strawberry; + } +} diff --git a/extra/interface/ap/src/test/resources/test/defaults/MultipleDefaults.java b/extra/interface/ap/src/test/resources/test/defaults/MultipleDefaults.java new file mode 100644 index 000000000..64c8e23bd --- /dev/null +++ b/extra/interface/ap/src/test/resources/test/defaults/MultipleDefaults.java @@ -0,0 +1,22 @@ +package test.defaults; + +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultBoolean; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultDecimal; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultNumeric; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultString; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + +@ConfigSerializable +public interface MultipleDefaults { + @DefaultBoolean(true) + @DefaultDecimal(2) + @DefaultNumeric(3) + @DefaultString("Hi!") + int multipleSingle(); + + @DefaultString("Hey!") + String multipleOverride(); + + @DefaultString("Hello!") + void multipleOverride(String x); +} diff --git a/extra/interface/ap/src/test/resources/test/defaults/MultipleDefaults.properties b/extra/interface/ap/src/test/resources/test/defaults/MultipleDefaults.properties new file mode 100644 index 000000000..32d257cc9 --- /dev/null +++ b/extra/interface/ap/src/test/resources/test/defaults/MultipleDefaults.properties @@ -0,0 +1 @@ +test.defaults.MultipleDefaults=test.defaults.MultipleDefaultsImpl diff --git a/extra/interface/ap/src/test/resources/test/defaults/MultipleDefaultsImpl.java b/extra/interface/ap/src/test/resources/test/defaults/MultipleDefaultsImpl.java new file mode 100644 index 000000000..f4329dcb4 --- /dev/null +++ b/extra/interface/ap/src/test/resources/test/defaults/MultipleDefaultsImpl.java @@ -0,0 +1,29 @@ +package test.defaults; + +import java.lang.Override; +import java.lang.String; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + +/** + * Automatically generated implementation of the config */ +@ConfigSerializable +final class MultipleDefaultsImpl implements MultipleDefaults { + private int multipleSingle = 3; + + private String multipleOverride = "Hey!"; // this is expected as "Hey!" is handled before "Hello!" + + @Override + public int multipleSingle() { + return multipleSingle; + } + + @Override + public String multipleOverride() { + return multipleOverride; + } + + @Override + public void multipleOverride(String x) { + this.multipleOverride = x; + } +} diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultBoolean.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultBoolean.java new file mode 100644 index 000000000..d209d957b --- /dev/null +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultBoolean.java @@ -0,0 +1,28 @@ +package org.spongepowered.configurate.interfaces.meta.defaults; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation provides a default value for the annotated method. + * Because of annotation limits, there is an annotation for: + * {@link DefaultBoolean booleans}, {@link DefaultDecimal decimals}, + * {@link DefaultNumeric numerics} and {@link DefaultString Strings}. + * + * @since 4.2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface DefaultBoolean { + + /** + * The default value for the annotated method. + * + * @return the default value for the annotated method. + * @since 4.2.0 + */ + boolean value() default false; + +} diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultDecimal.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultDecimal.java new file mode 100644 index 000000000..9dfe38abc --- /dev/null +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultDecimal.java @@ -0,0 +1,28 @@ +package org.spongepowered.configurate.interfaces.meta.defaults; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation provides a default value for the annotated method. + * Because of annotation limits, there is an annotation for: + * {@link DefaultBoolean booleans}, {@link DefaultDecimal decimals}, + * {@link DefaultNumeric numerics} and {@link DefaultString Strings}. + * + * @since 4.2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface DefaultDecimal { + + /** + * The default value for the annotated method. + * + * @return the default value for the annotated method. + * @since 4.2.0 + */ + double value() default 0.0; + +} diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultNumeric.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultNumeric.java new file mode 100644 index 000000000..35a2e0404 --- /dev/null +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultNumeric.java @@ -0,0 +1,28 @@ +package org.spongepowered.configurate.interfaces.meta.defaults; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation provides a default value for the annotated method. + * Because of annotation limits, there is an annotation for: + * {@link DefaultBoolean booleans}, {@link DefaultDecimal decimals}, + * {@link DefaultNumeric numerics} and {@link DefaultString Strings}. + * + * @since 4.2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface DefaultNumeric { + + /** + * The default value for the annotated method. + * + * @return the default value for the annotated method. + * @since 4.2.0 + */ + long value() default 0; + +} diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultString.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultString.java new file mode 100644 index 000000000..f62d0f2ce --- /dev/null +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultString.java @@ -0,0 +1,28 @@ +package org.spongepowered.configurate.interfaces.meta.defaults; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation provides a default value for the annotated method. + * Because of annotation limits, there is an annotation for: + * {@link DefaultBoolean booleans}, {@link DefaultDecimal decimals}, + * {@link DefaultNumeric numerics} and {@link DefaultString Strings}. + * + * @since 4.2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface DefaultString { + + /** + * The default value for the annotated method. + * + * @return the default value for the annotated method. + * @since 4.2.0 + */ + String value() default ""; + +} From 948cebeff43cd0dceb10fa81073579f44cd95da2 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 3 Jan 2024 22:17:21 +0100 Subject: [PATCH 05/35] Allow setter return type to be non-void --- .../ConfigImplementationGenerator.java | 35 +++++++++++++------ .../ConfigImplementationGenerationTest.java | 10 +++--- .../src/test/resources/test/BasicConfig.java | 2 ++ .../test/resources/test/BasicConfigImpl.java | 6 ++++ .../test/resources/test/ExtendedConfig.java | 2 ++ .../resources/test/ExtendedConfigImpl.java | 15 ++++++++ 6 files changed, 54 insertions(+), 16 deletions(-) diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java index 513ea37ee..cc894d395 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java @@ -2,6 +2,7 @@ import static org.spongepowered.configurate.interfaces.processor.Utils.hasAnnotation; +import com.google.auto.common.MoreTypes; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.MethodSpec; @@ -106,17 +107,31 @@ private void gatherElementSpec( TypeMirror nodeType = element.getReturnType(); if (parameters.size() == 1) { - final VariableElement parameter = parameters.get(0); // setter - spec.add( - simpleName + "#" + parameter.getSimpleName().toString(), - MethodSpec.overriding(element) - .addStatement( - "this.$N = $N", - element.getSimpleName(), - parameter.getSimpleName() - ) - ); + final VariableElement parameter = parameters.get(0); + + final MethodSpec.Builder method = MethodSpec.overriding(element) + .addStatement( + "this.$N = $N", + element.getSimpleName(), + parameter.getSimpleName() + ); + + // if it's not void + if (!MoreTypes.isTypeOf(Void.TYPE, nodeType)) { + // the return type can be a parent type of parameter, but it has to be assignable + if (!processor.typeUtils.isAssignable(parameter.asType(), nodeType)) { + throw new IllegalStateException(String.format( + "Cannot create a setter with return type %s for argument type %s. Method: %s", + nodeType, + parameter.asType(), + element + )); + } + method.addStatement("return this.$N", element.getSimpleName()); + } + + spec.add(simpleName + "#" + parameter.getSimpleName().toString(), method); nodeType = parameter.asType(); } else { // getter diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java index 4da4388d5..64a059930 100644 --- a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java @@ -32,14 +32,12 @@ void testBasicCompilation() { } @Test - void testMultiLayerCompilation() { - // expect generated config + mapping - testCompilation("test/MultiLayerConfig", EXPECT_CONFIG_AND_MAPPING); + void testExtendedCompilation() { + testCompilation("test/ExtendedConfig", EXPECT_CONFIG_AND_MAPPING); } @Test - void testExtendedCompilation() { - // expect generated config + mapping - testCompilation("test/ExtendedConfig", EXPECT_CONFIG_AND_MAPPING); + void testMultiLayerCompilation() { + testCompilation("test/MultiLayerConfig", EXPECT_CONFIG_AND_MAPPING); } } diff --git a/extra/interface/ap/src/test/resources/test/BasicConfig.java b/extra/interface/ap/src/test/resources/test/BasicConfig.java index bac0e28d7..9cfd63bb2 100644 --- a/extra/interface/ap/src/test/resources/test/BasicConfig.java +++ b/extra/interface/ap/src/test/resources/test/BasicConfig.java @@ -7,4 +7,6 @@ public interface BasicConfig { String hello(); void hi(String value); + + String hello(String value); } diff --git a/extra/interface/ap/src/test/resources/test/BasicConfigImpl.java b/extra/interface/ap/src/test/resources/test/BasicConfigImpl.java index 5388246f7..1fe04a5b9 100644 --- a/extra/interface/ap/src/test/resources/test/BasicConfigImpl.java +++ b/extra/interface/ap/src/test/resources/test/BasicConfigImpl.java @@ -21,4 +21,10 @@ public String hello() { public void hi(String value) { this.hi = value; } + + @Override + public String hello(String value) { + this.hello = value; + return this.hello; + } } diff --git a/extra/interface/ap/src/test/resources/test/ExtendedConfig.java b/extra/interface/ap/src/test/resources/test/ExtendedConfig.java index 40f7eb059..f2c26a0b5 100644 --- a/extra/interface/ap/src/test/resources/test/ExtendedConfig.java +++ b/extra/interface/ap/src/test/resources/test/ExtendedConfig.java @@ -5,4 +5,6 @@ @ConfigSerializable public interface ExtendedConfig extends BasicConfig { String hi(); + + Number hey(int value); } diff --git a/extra/interface/ap/src/test/resources/test/ExtendedConfigImpl.java b/extra/interface/ap/src/test/resources/test/ExtendedConfigImpl.java index a553973f5..7dfab8bf6 100644 --- a/extra/interface/ap/src/test/resources/test/ExtendedConfigImpl.java +++ b/extra/interface/ap/src/test/resources/test/ExtendedConfigImpl.java @@ -1,5 +1,6 @@ package test; +import java.lang.Number; import java.lang.Override; import java.lang.String; import org.spongepowered.configurate.objectmapping.ConfigSerializable; @@ -10,6 +11,8 @@ final class ExtendedConfigImpl implements ExtendedConfig { private String hi; + private int hey; + private String hello; @Override @@ -17,6 +20,12 @@ public String hi() { return hi; } + @Override + public Number hey(int value) { + this.hey = value; + return this.hey; + } + @Override public String hello() { return hello; @@ -26,4 +35,10 @@ public String hello() { public void hi(String value) { this.hi = value; } + + @Override + public String hello(String value) { + this.hello = value; + return this.hello; + } } From ba11369344a08a69ba7778c527919bd5a3f27357 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 3 Jan 2024 22:26:12 +0100 Subject: [PATCH 06/35] Auto-generate simple mappings --- .../interfaces/processor/TestUtils.java | 25 ++++++++++++++----- .../resources/test/BasicConfig.properties | 1 - .../resources/test/ExtendedConfig.properties | 1 - .../test/defaults/CorrectDefaults.properties | 1 - .../test/defaults/MultipleDefaults.properties | 1 - 5 files changed, 19 insertions(+), 10 deletions(-) delete mode 100644 extra/interface/ap/src/test/resources/test/BasicConfig.properties delete mode 100644 extra/interface/ap/src/test/resources/test/ExtendedConfig.properties delete mode 100644 extra/interface/ap/src/test/resources/test/defaults/CorrectDefaults.properties delete mode 100644 extra/interface/ap/src/test/resources/test/defaults/MultipleDefaults.properties diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java index 9a4ad677c..c10e899b7 100644 --- a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java @@ -12,6 +12,7 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import javax.tools.StandardLocation; @@ -40,7 +41,6 @@ static Compilation testCompilation(final String sourceResourceName, final int ex .hasSourceEquivalentTo(JavaFileObjects.forResource(targetResourceName + ".java")); try { - final URL localMappings = Resources.getResource(sourceResourceName + ".properties"); final String actualContent = compilation .generatedFile(StandardLocation.SOURCE_OUTPUT, Constants.MAPPING_FILE) @@ -48,10 +48,7 @@ static Compilation testCompilation(final String sourceResourceName, final int ex .getCharContent(false) .toString(); - final List expectedLines = - Resources - .asCharSource(localMappings, StandardCharsets.UTF_8) - .readLines(); + final List expectedLines = readOrGenerateMappings(sourceResourceName, targetResourceName); assertIterableEquals(expectedLines, removeComments(actualContent)); } catch (final IOException exception) { @@ -71,10 +68,26 @@ static Compilation testCompilation(final String sourceResourceName, final int ex return compilation; } - static List removeComments(final String content) { + private static List removeComments(final String content) { return Arrays.stream(content.split(System.lineSeparator())) .filter(line -> !line.startsWith("#")) .collect(Collectors.toList()); } + private static List readOrGenerateMappings(String sourceResourceName, String targetResourceName) { + try { + final URL localMappings = Resources.getResource(sourceResourceName + ".properties"); + return Resources.asCharSource(localMappings, StandardCharsets.UTF_8).readLines(); + } catch (IllegalArgumentException ignored) { + System.out.println("Could not find resource " + sourceResourceName + ".properties, generating one"); + return Collections.singletonList(String.format( + "%s=%s", + sourceResourceName.replace('/', '.'), + targetResourceName.replace('/', '.') + )); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } diff --git a/extra/interface/ap/src/test/resources/test/BasicConfig.properties b/extra/interface/ap/src/test/resources/test/BasicConfig.properties deleted file mode 100644 index b0e25c9af..000000000 --- a/extra/interface/ap/src/test/resources/test/BasicConfig.properties +++ /dev/null @@ -1 +0,0 @@ -test.BasicConfig=test.BasicConfigImpl diff --git a/extra/interface/ap/src/test/resources/test/ExtendedConfig.properties b/extra/interface/ap/src/test/resources/test/ExtendedConfig.properties deleted file mode 100644 index 44fe5031b..000000000 --- a/extra/interface/ap/src/test/resources/test/ExtendedConfig.properties +++ /dev/null @@ -1 +0,0 @@ -test.ExtendedConfig=test.ExtendedConfigImpl diff --git a/extra/interface/ap/src/test/resources/test/defaults/CorrectDefaults.properties b/extra/interface/ap/src/test/resources/test/defaults/CorrectDefaults.properties deleted file mode 100644 index 7f15ddd62..000000000 --- a/extra/interface/ap/src/test/resources/test/defaults/CorrectDefaults.properties +++ /dev/null @@ -1 +0,0 @@ -test.defaults.CorrectDefaults=test.defaults.CorrectDefaultsImpl diff --git a/extra/interface/ap/src/test/resources/test/defaults/MultipleDefaults.properties b/extra/interface/ap/src/test/resources/test/defaults/MultipleDefaults.properties deleted file mode 100644 index 32d257cc9..000000000 --- a/extra/interface/ap/src/test/resources/test/defaults/MultipleDefaults.properties +++ /dev/null @@ -1 +0,0 @@ -test.defaults.MultipleDefaults=test.defaults.MultipleDefaultsImpl From 7f6059f771076853b9d2d2bf51c7a8163ef8c7a0 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Fri, 5 Jan 2024 18:32:40 +0100 Subject: [PATCH 07/35] Added Hidden annotation and added Processor.AdvancedFactory to aid it --- .../objectmapping/ObjectMapper.java | 4 +-- .../ObjectMapperFactoryImpl.java | 14 ++++---- .../objectmapping/meta/Processor.java | 32 ++++++++++++++++- ...notations.java => AnnotationDefaults.java} | 33 ++++++++++++----- .../processor/AnnotationHidden.java | 17 +++++++++ .../ConfigImplementationGenerator.java | 7 ++-- .../processor/TypeSpecBuilderTracker.java | 23 +++++++----- .../ConfigImplementationGenerationTest.java | 1 + .../interfaces/processor/TestUtils.java | 10 +++--- .../test/defaults/CorrectDefaultsImpl.java | 24 +++++++++++++ .../test/defaults/MultipleDefaultsImpl.java | 4 +++ .../interfaces/InterfaceConstraints.java | 36 +++++++++++++++++-- .../configurate/interfaces/TypeUtils.java | 26 ++++++++++++++ .../configurate/interfaces/meta/Exclude.java | 2 +- .../configurate/interfaces/meta/Hidden.java | 22 ++++++++++++ .../meta/defaults/DefaultBoolean.java | 2 +- .../meta/defaults/DefaultDecimal.java | 2 +- .../meta/defaults/DefaultNumeric.java | 2 +- .../meta/defaults/DefaultString.java | 2 +- 19 files changed, 222 insertions(+), 41 deletions(-) rename extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/{DefaultAnnotations.java => AnnotationDefaults.java} (72%) create mode 100644 extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationHidden.java create mode 100644 extra/interface/src/main/java/org/spongepowered/configurate/interfaces/TypeUtils.java create mode 100644 extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Hidden.java diff --git a/core/src/main/java/org/spongepowered/configurate/objectmapping/ObjectMapper.java b/core/src/main/java/org/spongepowered/configurate/objectmapping/ObjectMapper.java index 5f8d2e5f1..e9d879c85 100644 --- a/core/src/main/java/org/spongepowered/configurate/objectmapping/ObjectMapper.java +++ b/core/src/main/java/org/spongepowered/configurate/objectmapping/ObjectMapper.java @@ -296,7 +296,7 @@ interface Builder { * @return this builder * @since 4.0.0 */ - default Builder addProcessor(final Class definition, final Processor.Factory factory) { + default Builder addProcessor(final Class definition, final Processor.AdvancedFactory factory) { return addProcessor(definition, Object.class, factory); } @@ -314,7 +314,7 @@ default Builder addProcessor(final Class definition, f * @return this builder * @since 4.0.0 */ - Builder addProcessor(Class definition, Class valueType, Processor.Factory factory); + Builder addProcessor(Class definition, Class valueType, Processor.AdvancedFactory factory); /** * Register a {@link Constraint} that will be used to validate fields. diff --git a/core/src/main/java/org/spongepowered/configurate/objectmapping/ObjectMapperFactoryImpl.java b/core/src/main/java/org/spongepowered/configurate/objectmapping/ObjectMapperFactoryImpl.java index 2426c87e4..c14b905c6 100644 --- a/core/src/main/java/org/spongepowered/configurate/objectmapping/ObjectMapperFactoryImpl.java +++ b/core/src/main/java/org/spongepowered/configurate/objectmapping/ObjectMapperFactoryImpl.java @@ -72,7 +72,7 @@ protected boolean removeEldestEntry(final Map.Entry> eldes private final List resolverFactories; private final List> fieldDiscoverers; private final Map, List>>> constraints; - private final Map, List>>> processors; + private final Map, List>>> processors; private final List postProcessors; ObjectMapperFactoryImpl(final Builder builder) { @@ -97,7 +97,7 @@ protected boolean removeEldestEntry(final Map.Entry> eldes this.constraints.values().forEach(Collections::reverse); this.processors = new HashMap<>(); - for (final Definition> def : builder.processors) { + for (final Definition> def : builder.processors) { this.processors.computeIfAbsent(def.annotation(), k -> new ArrayList<>()).add(def); } this.processors.values().forEach(Collections::reverse); @@ -206,11 +206,11 @@ private void makeData(final List> fields, final String na } } - final List>> processorDefs = this.processors.get(annotation.annotationType()); + final List>> processorDefs = this.processors.get(annotation.annotationType()); if (processorDefs != null) { - for (final Definition> processorDef : processorDefs) { + for (final Definition> processorDef : processorDefs) { if (isSuperType(processorDef.type(), normalizedType)) { - processors.add(((Processor.Factory) processorDef.factory()).make(annotation, type.getType())); + processors.add(((Processor.AdvancedFactory) processorDef.factory()).make(annotation, type.getType(), container)); } } } @@ -356,7 +356,7 @@ static class Builder implements ObjectMapper.Factory.Builder { private final List resolvers = new ArrayList<>(); private final List> discoverer = new ArrayList<>(); private final List>> constraints = new ArrayList<>(); - private final List>> processors = new ArrayList<>(); + private final List>> processors = new ArrayList<>(); private final List postProcessors = new ArrayList<>(); @Override @@ -379,7 +379,7 @@ public Builder addDiscoverer(final FieldDiscoverer discoverer) { @Override public Builder addProcessor(final Class definition, final Class valueType, - final Processor.Factory factory) { + final Processor.AdvancedFactory factory) { this.processors.add(Definition.of(definition, valueType, factory)); return this; } diff --git a/core/src/main/java/org/spongepowered/configurate/objectmapping/meta/Processor.java b/core/src/main/java/org/spongepowered/configurate/objectmapping/meta/Processor.java index be223b150..6f8d33cb9 100644 --- a/core/src/main/java/org/spongepowered/configurate/objectmapping/meta/Processor.java +++ b/core/src/main/java/org/spongepowered/configurate/objectmapping/meta/Processor.java @@ -16,6 +16,7 @@ */ package org.spongepowered.configurate.objectmapping.meta; +import java.lang.reflect.AnnotatedElement; import org.spongepowered.configurate.CommentedConfigurationNodeIntermediary; import org.spongepowered.configurate.ConfigurationNode; @@ -40,6 +41,31 @@ public interface Processor { */ void process(V value, ConfigurationNode destination); + /** + * Provider to, given an annotation instance and the type it's on, + * create a {@link Processor}. If you don't need access to the other + * annotations on the field, you can also choose the simpler {@link Factory}. + * + * @param annotation type + * @param handled value type + * @since 4.0.0 + */ + @FunctionalInterface + interface AdvancedFactory { + + /** + * Create a new processor given the annotation and data type. + * + * @param data annotation type on record field + * @param value declared field type + * @param container container holding the field, with its annotations + * @return new processor + * @since 4.0.0 + */ + Processor make(A data, Type value, AnnotatedElement container); + + } + /** * Provider to, given an annotation instance and the type it's on, * create a {@link Processor}. @@ -49,7 +75,7 @@ public interface Processor { * @since 4.0.0 */ @FunctionalInterface - interface Factory { + interface Factory extends AdvancedFactory { /** * Create a new processor given the annotation and data type. @@ -61,6 +87,10 @@ interface Factory { */ Processor make(A data, Type value); + @Override + default Processor make(A data, Type value, AnnotatedElement element) { + return make(data, value); + } } /** diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/DefaultAnnotations.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationDefaults.java similarity index 72% rename from extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/DefaultAnnotations.java rename to extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationDefaults.java index 891c089d9..3686d87c9 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/DefaultAnnotations.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationDefaults.java @@ -1,7 +1,10 @@ package org.spongepowered.configurate.interfaces.processor; import com.google.auto.common.MoreTypes; +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.FieldSpec; +import java.lang.annotation.Annotation; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; @@ -10,9 +13,9 @@ import org.spongepowered.configurate.interfaces.meta.defaults.DefaultNumeric; import org.spongepowered.configurate.interfaces.meta.defaults.DefaultString; -final class DefaultAnnotations { +final class AnnotationDefaults { - private DefaultAnnotations() {} + private AnnotationDefaults() {} static void process(final ExecutableElement element, final TypeMirror nodeType, final FieldSpec.Builder fieldSpec) { final @Nullable DefaultBoolean defaultBoolean = element.getAnnotation(DefaultBoolean.class); @@ -32,17 +35,17 @@ static void process(final ExecutableElement element, final TypeMirror nodeType, throw new IllegalStateException("A default value of the incorrect type was provided for " + element); } defaultValue = defaultBoolean.value(); - } + buildAndAddAnnotation(fieldSpec, DefaultBoolean.class, defaultValue, false); - if (Utils.isDecimal(nodeType)) { + } else if (Utils.isDecimal(nodeType)) { //noinspection ConstantValue if (defaultDecimal == null) { throw new IllegalStateException("A default value of the incorrect type was provided for " + element); } defaultValue = defaultDecimal.value() + (MoreTypes.isTypeOf(Float.TYPE, nodeType) ? "F" : "D"); - } + buildAndAddAnnotation(fieldSpec, DefaultDecimal.class, defaultValue, false); - if (Utils.isNumeric(nodeType)) { + } else if (Utils.isNumeric(nodeType)) { //noinspection ConstantValue if (defaultNumeric == null) { throw new IllegalStateException("A default value of the incorrect type was provided for " + element); @@ -51,15 +54,16 @@ static void process(final ExecutableElement element, final TypeMirror nodeType, if (MoreTypes.isTypeOf(Long.TYPE, nodeType)) { defaultValue += "L"; } - } + buildAndAddAnnotation(fieldSpec, DefaultNumeric.class, defaultValue, false); - if (MoreTypes.isTypeOf(String.class, nodeType)) { + } else if (MoreTypes.isTypeOf(String.class, nodeType)) { //noinspection ConstantValue if (defaultString == null) { throw new IllegalStateException("A default value of the incorrect type was provided for " + element); } defaultValue = defaultString.value(); isString = true; + buildAndAddAnnotation(fieldSpec, DefaultString.class, defaultValue, true); } } @@ -68,4 +72,17 @@ static void process(final ExecutableElement element, final TypeMirror nodeType, } } + private static void buildAndAddAnnotation( + final FieldSpec.Builder fieldSpec, + final Class annotationClass, + final Object value, + final boolean isString + ) { + fieldSpec.addAnnotation( + AnnotationSpec.builder(annotationClass) + .addMember("value", CodeBlock.of(isString ? "$S" : "$L", value)) + .build() + ); + } + } diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationHidden.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationHidden.java new file mode 100644 index 000000000..c35998ebb --- /dev/null +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationHidden.java @@ -0,0 +1,17 @@ +package org.spongepowered.configurate.interfaces.processor; + +import com.squareup.javapoet.FieldSpec; +import javax.lang.model.element.ExecutableElement; +import org.spongepowered.configurate.interfaces.meta.Hidden; + +final class AnnotationHidden { + + private AnnotationHidden() {} + + static void process(final ExecutableElement element, final FieldSpec.Builder fieldSpec) { + if (Utils.hasAnnotation(element, Hidden.class)) { + fieldSpec.addAnnotation(Hidden.class); + } + } + +} diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java index cc894d395..c93fd3236 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java @@ -120,7 +120,7 @@ private void gatherElementSpec( // if it's not void if (!MoreTypes.isTypeOf(Void.TYPE, nodeType)) { // the return type can be a parent type of parameter, but it has to be assignable - if (!processor.typeUtils.isAssignable(parameter.asType(), nodeType)) { + if (!this.processor.typeUtils.isAssignable(parameter.asType(), nodeType)) { throw new IllegalStateException(String.format( "Cannot create a setter with return type %s for argument type %s. Method: %s", nodeType, @@ -144,8 +144,9 @@ private void gatherElementSpec( final FieldSpec.Builder fieldSpec = FieldSpec.builder(TypeName.get(nodeType), simpleName, Modifier.PRIVATE); - // add default value if it has any - DefaultAnnotations.process(element, nodeType, fieldSpec); + //todo add tests for hidden in both ap and interfaces and defaults in interfaces + AnnotationDefaults.process(element, nodeType, fieldSpec); + AnnotationHidden.process(element, fieldSpec); spec.add(simpleName, fieldSpec); } diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java index 37471cdc4..56a37cadc 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java @@ -20,7 +20,6 @@ import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; - import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -45,7 +44,7 @@ void add(final String fieldIdentifier, final FieldSpec.Builder builder) { if (existingBuild.initializer.isEmpty() && !builderBuild.initializer.isEmpty()) { existing.initializer(builderBuild.initializer); } - existing.addAnnotations(originalAnnotations(existingBuild.annotations, builderBuild.annotations)); + existing.addAnnotations(pickNewAnnotations(existingBuild.annotations, builderBuild.annotations)); return; } this.fieldSpecs.put(fieldIdentifier, builder); @@ -54,7 +53,7 @@ void add(final String fieldIdentifier, final FieldSpec.Builder builder) { void add(final String methodIdentifier, final MethodSpec.Builder builder) { final MethodSpec.Builder existing = this.methodSpecs.get(methodIdentifier); if (existing != null) { - existing.addAnnotations(originalAnnotations(existing.build().annotations, builder.build().annotations)); + existing.addAnnotations(pickNewAnnotations(existing.build().annotations, builder.build().annotations)); return; } this.methodSpecs.put(methodIdentifier, builder); @@ -77,12 +76,20 @@ void writeTo(final TypeSpec.Builder builder) { this.typeSpecs.values().forEach(builder::addType); } - private List originalAnnotations( - final List left, - final List right + private List pickNewAnnotations( + final List existing, + final List newOne ) { - final List result = new ArrayList<>(left); - result.removeAll(right); + final List result = new ArrayList<>(); + // only add annotations if they don't already exist + outer: for (AnnotationSpec spec : newOne) { + for (AnnotationSpec existingSpec : existing) { + if (existingSpec.type.equals(spec.type)) { + break outer; + } + } + result.add(spec); + } return result; } diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java index 64a059930..e545605df 100644 --- a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java @@ -40,4 +40,5 @@ void testExtendedCompilation() { void testMultiLayerCompilation() { testCompilation("test/MultiLayerConfig", EXPECT_CONFIG_AND_MAPPING); } + } diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java index c10e899b7..0b46a1a6a 100644 --- a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java @@ -18,10 +18,12 @@ import javax.tools.StandardLocation; import org.spongepowered.configurate.interfaces.Constants; -class TestUtils { +final class TestUtils { static final int EXPECT_CONFIG_AND_MAPPING = 2; + private TestUtils() {} + /** * Tests whether the compilation is successful and * that the correct mappings have been made @@ -74,18 +76,18 @@ private static List removeComments(final String content) { .collect(Collectors.toList()); } - private static List readOrGenerateMappings(String sourceResourceName, String targetResourceName) { + private static List readOrGenerateMappings(final String sourceResourceName, final String targetResourceName) { try { final URL localMappings = Resources.getResource(sourceResourceName + ".properties"); return Resources.asCharSource(localMappings, StandardCharsets.UTF_8).readLines(); - } catch (IllegalArgumentException ignored) { + } catch (final IllegalArgumentException ignored) { System.out.println("Could not find resource " + sourceResourceName + ".properties, generating one"); return Collections.singletonList(String.format( "%s=%s", sourceResourceName.replace('/', '.'), targetResourceName.replace('/', '.') )); - } catch (IOException e) { + } catch (final IOException e) { throw new RuntimeException(e); } } diff --git a/extra/interface/ap/src/test/resources/test/defaults/CorrectDefaultsImpl.java b/extra/interface/ap/src/test/resources/test/defaults/CorrectDefaultsImpl.java index 9db2bb247..012cdaf35 100644 --- a/extra/interface/ap/src/test/resources/test/defaults/CorrectDefaultsImpl.java +++ b/extra/interface/ap/src/test/resources/test/defaults/CorrectDefaultsImpl.java @@ -2,48 +2,72 @@ import java.lang.Override; import java.lang.String; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultBoolean; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultDecimal; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultNumeric; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultString; import org.spongepowered.configurate.objectmapping.ConfigSerializable; /** * Automatically generated implementation of the config */ @ConfigSerializable final class CorrectDefaultsImpl implements CorrectDefaults { + + @DefaultBoolean(false) private boolean apple = false; + @DefaultBoolean(true) private boolean blueberry = true; + @DefaultDecimal(0.0F) private float cherry = 0.0F; + @DefaultDecimal(123.5F) private float dragonfruit = 123.5F; + @DefaultDecimal(0.0D) private double eggplant = 0.0D; + @DefaultDecimal(234.23D) private double fig = 234.23D; + @DefaultNumeric(0) private byte grape = 0; + @DefaultNumeric(127) private byte huckleberry = 127; + @DefaultNumeric(0) private char italianPrunePlum = 0; + @DefaultNumeric(126) private char jackfruit = 126; + @DefaultNumeric(99) private char kiwi = 99; + @DefaultNumeric(0) private short lemon = 0; + @DefaultNumeric(1341) private short mango = 1341; + @DefaultNumeric(0) private int nectarine = 0; + @DefaultNumeric(1231241) private int orange = 1231241; + @DefaultNumeric(0L) private long pineapple = 0L; + @DefaultNumeric(24524524521L) private long quince = 24524524521L; + @DefaultString("") private String raspberry = ""; + @DefaultString("Hello world!") private String strawberry = "Hello world!"; @Override diff --git a/extra/interface/ap/src/test/resources/test/defaults/MultipleDefaultsImpl.java b/extra/interface/ap/src/test/resources/test/defaults/MultipleDefaultsImpl.java index f4329dcb4..f054c36cd 100644 --- a/extra/interface/ap/src/test/resources/test/defaults/MultipleDefaultsImpl.java +++ b/extra/interface/ap/src/test/resources/test/defaults/MultipleDefaultsImpl.java @@ -2,14 +2,18 @@ import java.lang.Override; import java.lang.String; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultNumeric; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultString; import org.spongepowered.configurate.objectmapping.ConfigSerializable; /** * Automatically generated implementation of the config */ @ConfigSerializable final class MultipleDefaultsImpl implements MultipleDefaults { + @DefaultNumeric(3) private int multipleSingle = 3; + @DefaultString("Hey!") private String multipleOverride = "Hey!"; // this is expected as "Hey!" is handled before "Hello!" @Override diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceConstraints.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceConstraints.java index a8ff1fe3a..bf4e69aa2 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceConstraints.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceConstraints.java @@ -1,10 +1,15 @@ package org.spongepowered.configurate.interfaces; +import org.spongepowered.configurate.interfaces.meta.Hidden; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultBoolean; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultDecimal; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultNumeric; import org.spongepowered.configurate.interfaces.meta.range.DecimalRange; import org.spongepowered.configurate.interfaces.meta.range.NumericRange; import org.spongepowered.configurate.interfaces.meta.range.StringRange; import org.spongepowered.configurate.objectmapping.ObjectMapper; import org.spongepowered.configurate.objectmapping.meta.Constraint; +import org.spongepowered.configurate.objectmapping.meta.Processor; import org.spongepowered.configurate.serialize.SerializationException; final class InterfaceConstraints { @@ -17,12 +22,13 @@ static ObjectMapper.Factory buildObjectMapperWithConstraints() { .addConstraint(DecimalRange.class, Number.class, decimalRange()) .addConstraint(NumericRange.class, Number.class, numericRange()) .addConstraint(StringRange.class, String.class, stringRange()) + .addProcessor(Hidden.class, hiddenProcessor()) .build(); } private static Constraint.Factory decimalRange() { return (data, type) -> number -> { - // Null requirement is part of @Required + // null requirement is part of @Required if (number == null) { return; } @@ -39,7 +45,7 @@ private static Constraint.Factory decimalRange() { private static Constraint.Factory numericRange() { return (data, type) -> number -> { - // Null requirement is part of @Required + // null requirement is part of @Required if (number == null) { return; } @@ -56,7 +62,7 @@ private static Constraint.Factory numericRange() { private static Constraint.Factory stringRange() { return (data, type) -> string -> { - // Null requirement is part of @Required + // null requirement is part of @Required if (string == null) { return; } @@ -71,4 +77,28 @@ private static Constraint.Factory stringRange() { }; } + private static Processor.AdvancedFactory hiddenProcessor() { + return (ignored, fieldType, element) -> (value, destination) -> { + // hidden fields are only not part of the config if the value is the default value, so we check that below + if (TypeUtils.isBoolean(fieldType) && element.isAnnotationPresent(DefaultBoolean.class)) { + if (!value.equals(element.getAnnotation(DefaultBoolean.class).value())) { + return; + } + } else if (TypeUtils.isDecimal(fieldType) && element.isAnnotationPresent(DefaultDecimal.class)) { + if (((Number) value).doubleValue() != element.getAnnotation(DefaultDecimal.class).value()) { + return; + } + } else if (TypeUtils.isNumeric(fieldType) && element.isAnnotationPresent(DefaultNumeric.class)) { + if (((Number) value).longValue() != element.getAnnotation(DefaultNumeric.class).value()) { + return; + } + } + + // as long as it uses the naming scheme-based resolver parent should be the object holding the field and + // key the field type + //noinspection DataFlowIssue + destination.parent().removeChild(destination.key()); + }; + } + } diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/TypeUtils.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/TypeUtils.java new file mode 100644 index 000000000..4d7b55c10 --- /dev/null +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/TypeUtils.java @@ -0,0 +1,26 @@ +package org.spongepowered.configurate.interfaces; + +import static io.leangen.geantyref.GenericTypeReflector.box; + +import java.lang.reflect.Type; + +final class TypeUtils { + + private TypeUtils() {} + + static boolean isNumeric(final Type type) { + final Type boxed = box(type); + return Byte.class.equals(boxed) || Character.class.equals(boxed) || Short.class.equals(boxed) + || Integer.class.equals(boxed) || Long.class.equals(boxed); + } + + static boolean isDecimal(final Type type) { + final Type boxed = box(type); + return Float.class.equals(boxed) || Double.class.equals(boxed); + } + + static boolean isBoolean(final Type type) { + return Boolean.class.equals(box(type)); + } + +} diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Exclude.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Exclude.java index 117f379c8..ac8ae669f 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Exclude.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Exclude.java @@ -24,7 +24,7 @@ /** * Prevents the annotated method from being handled as a config node. * This is practically only used for default methods, as normal interface - * methods have to have an implementation. + * methods need to have an implementation. * * @since 4.2.0 */ diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Hidden.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Hidden.java new file mode 100644 index 000000000..dded58966 --- /dev/null +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Hidden.java @@ -0,0 +1,22 @@ +package org.spongepowered.configurate.interfaces.meta; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation allows you to read the config node as normal, but it only + * writes the node when the value is not the default value. This is to ensure + * that when a user manually adds the entry, it remains there (as long as it's + * not the default value.) + * + *

Without a default value the annotated node will be read, but will never + * be written even if the user explicitly added it to their config.

+ * + * @since 4.2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD}) +public @interface Hidden { +} diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultBoolean.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultBoolean.java index d209d957b..68939e34e 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultBoolean.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultBoolean.java @@ -14,7 +14,7 @@ * @since 4.2.0 */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.FIELD}) public @interface DefaultBoolean { /** diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultDecimal.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultDecimal.java index 9dfe38abc..1a688ad1d 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultDecimal.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultDecimal.java @@ -14,7 +14,7 @@ * @since 4.2.0 */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.FIELD}) public @interface DefaultDecimal { /** diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultNumeric.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultNumeric.java index 35a2e0404..f9b91783d 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultNumeric.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultNumeric.java @@ -14,7 +14,7 @@ * @since 4.2.0 */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.FIELD}) public @interface DefaultNumeric { /** diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultString.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultString.java index f62d0f2ce..305f2cf09 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultString.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultString.java @@ -14,7 +14,7 @@ * @since 4.2.0 */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.FIELD}) public @interface DefaultString { /** From 78bcaf5e0b90a388cf1d05528dbc79d165a3e711 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 6 Jan 2024 12:33:00 +0100 Subject: [PATCH 08/35] Added support for some build-in annotations and added another addProcessor method --- .../objectmapping/ObjectMapper.java | 38 +++++++++++++++++++ .../ObjectMapperFactoryImpl.java | 30 +++++++++------ .../objectmapping/meta/Comment.java | 2 +- .../objectmapping/meta/Matches.java | 2 +- .../objectmapping/meta/Required.java | 2 +- .../processor/AnnotationConfigurate.java | 30 +++++++++++++++ .../ConfigImplementationGenerator.java | 8 ++++ .../ConfigImplementationGenerationTest.java | 5 +++ .../test/ConfigurateAnnotations.java | 17 +++++++++ .../test/ConfigurateAnnotationsImpl.java | 31 +++++++++++++++ 10 files changed, 150 insertions(+), 15 deletions(-) create mode 100644 extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationConfigurate.java create mode 100644 extra/interface/ap/src/test/resources/test/ConfigurateAnnotations.java create mode 100644 extra/interface/ap/src/test/resources/test/ConfigurateAnnotationsImpl.java diff --git a/core/src/main/java/org/spongepowered/configurate/objectmapping/ObjectMapper.java b/core/src/main/java/org/spongepowered/configurate/objectmapping/ObjectMapper.java index e9d879c85..6ca44b573 100644 --- a/core/src/main/java/org/spongepowered/configurate/objectmapping/ObjectMapper.java +++ b/core/src/main/java/org/spongepowered/configurate/objectmapping/ObjectMapper.java @@ -296,12 +296,50 @@ interface Builder { * @return this builder * @since 4.0.0 */ + default
Builder addProcessor(final Class definition, final Processor.Factory factory) { + return addProcessor(definition, Object.class, factory); + } + + /** + * Register a {@link Processor} that will process fields after write. + * + *

All value types will be tested against types normalized to + * their boxed variants.

+ * + * @param definition annotation providing data + * @param valueType value types the processor will handle + * @param factory factory for callback function + * @param
annotation type + * @param data type + * @return this builder + * @since 4.0.0 + */ + Builder addProcessor(Class definition, Class valueType, Processor.Factory factory); + + /** + * Register a {@link Processor} that will process fields after write. + * The difference between an AdvancedFactory and a Factory is that + * an AdvancedFactory has access to all the annotations on the + * field, which makes more advanced processors possible. + * + *

Processors registered without a specific data type should be + * able to operate on any value type.

+ * + * @param definition annotation providing data + * @param factory factory for callback function + * @param
annotation type + * @return this builder + * @since 4.0.0 + */ default Builder addProcessor(final Class definition, final Processor.AdvancedFactory factory) { return addProcessor(definition, Object.class, factory); } /** * Register a {@link Processor} that will process fields after write. + * The difference between an AdvancedFactory and a Factory is that + * an AdvancedFactory has access to all the annotations on the + * field, which makes more advanced processors possible. * *

All value types will be tested against types normalized to * their boxed variants.

diff --git a/core/src/main/java/org/spongepowered/configurate/objectmapping/ObjectMapperFactoryImpl.java b/core/src/main/java/org/spongepowered/configurate/objectmapping/ObjectMapperFactoryImpl.java index c14b905c6..31e2ad5bc 100644 --- a/core/src/main/java/org/spongepowered/configurate/objectmapping/ObjectMapperFactoryImpl.java +++ b/core/src/main/java/org/spongepowered/configurate/objectmapping/ObjectMapperFactoryImpl.java @@ -25,6 +25,17 @@ import static java.util.Objects.requireNonNull; import io.leangen.geantyref.GenericTypeReflector; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.configurate.BasicConfigurationNode; import org.spongepowered.configurate.ConfigurationNode; @@ -42,18 +53,6 @@ import org.spongepowered.configurate.util.NamingScheme; import org.spongepowered.configurate.util.NamingSchemes; -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.AnnotatedType; -import java.lang.reflect.Modifier; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - /** * Factory for a basic {@link ObjectMapper}. */ @@ -377,6 +376,13 @@ public Builder addDiscoverer(final FieldDiscoverer discoverer) { return this; } + @Override + public
Builder addProcessor(final Class definition, final Class valueType, + final Processor.Factory factory) { + this.processors.add(Definition.of(definition, valueType, factory)); + return this; + } + @Override public Builder addProcessor(final Class definition, final Class valueType, final Processor.AdvancedFactory factory) { diff --git a/core/src/main/java/org/spongepowered/configurate/objectmapping/meta/Comment.java b/core/src/main/java/org/spongepowered/configurate/objectmapping/meta/Comment.java index 1b0a969dc..468620cd3 100644 --- a/core/src/main/java/org/spongepowered/configurate/objectmapping/meta/Comment.java +++ b/core/src/main/java/org/spongepowered/configurate/objectmapping/meta/Comment.java @@ -33,7 +33,7 @@ * * @since 4.0.0 */ -@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface Comment { diff --git a/core/src/main/java/org/spongepowered/configurate/objectmapping/meta/Matches.java b/core/src/main/java/org/spongepowered/configurate/objectmapping/meta/Matches.java index 824b99632..beaf41ab2 100644 --- a/core/src/main/java/org/spongepowered/configurate/objectmapping/meta/Matches.java +++ b/core/src/main/java/org/spongepowered/configurate/objectmapping/meta/Matches.java @@ -35,7 +35,7 @@ */ @Documented @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) +@Target({ElementType.FIELD, ElementType.METHOD}) public @interface Matches { /** diff --git a/core/src/main/java/org/spongepowered/configurate/objectmapping/meta/Required.java b/core/src/main/java/org/spongepowered/configurate/objectmapping/meta/Required.java index c1354016d..95758e6b7 100644 --- a/core/src/main/java/org/spongepowered/configurate/objectmapping/meta/Required.java +++ b/core/src/main/java/org/spongepowered/configurate/objectmapping/meta/Required.java @@ -34,7 +34,7 @@ */ @Documented @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) +@Target({ElementType.FIELD, ElementType.METHOD}) @SubtypeOf(NonNull.class) public @interface Required { } diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationConfigurate.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationConfigurate.java new file mode 100644 index 000000000..c2f2bfb79 --- /dev/null +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationConfigurate.java @@ -0,0 +1,30 @@ +package org.spongepowered.configurate.interfaces.processor; + +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.FieldSpec; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.List; +import javax.lang.model.element.ExecutableElement; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.configurate.objectmapping.meta.Comment; +import org.spongepowered.configurate.objectmapping.meta.Matches; +import org.spongepowered.configurate.objectmapping.meta.Required; + +final class AnnotationConfigurate { + + private static final List> annotations = Arrays.asList(Comment.class, Matches.class, Required.class); + + private AnnotationConfigurate() {} + + static void process(final ExecutableElement element, final FieldSpec.Builder fieldSpec) { + for (final Class annotation : annotations) { + final @Nullable Annotation current = element.getAnnotation(annotation); + //noinspection ConstantValue + if (current != null) { + fieldSpec.addAnnotation(AnnotationSpec.get(current)); + } + } + } + +} diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java index c93fd3236..fb0a895ef 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java @@ -19,6 +19,7 @@ import javax.lang.model.type.TypeMirror; import org.spongepowered.configurate.interfaces.meta.Exclude; import org.spongepowered.configurate.objectmapping.ConfigSerializable; +import org.spongepowered.configurate.objectmapping.meta.PostProcess; class ConfigImplementationGenerator { @@ -81,6 +82,11 @@ private void gatherElementSpec( final ExecutableElement element = (ExecutableElement) enclosedElement; + if (hasAnnotation(element, PostProcess.class)) { + // A postprocess annotated method is not a config node + continue; + } + final boolean excluded = hasAnnotation(element, Exclude.class); if (element.isDefault()) { if (excluded) { @@ -147,6 +153,8 @@ private void gatherElementSpec( //todo add tests for hidden in both ap and interfaces and defaults in interfaces AnnotationDefaults.process(element, nodeType, fieldSpec); AnnotationHidden.process(element, fieldSpec); + // add Configurate's build-in annotations as well + AnnotationConfigurate.process(element, fieldSpec); spec.add(simpleName, fieldSpec); } diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java index e545605df..9379fc5be 100644 --- a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java @@ -41,4 +41,9 @@ void testMultiLayerCompilation() { testCompilation("test/MultiLayerConfig", EXPECT_CONFIG_AND_MAPPING); } + @Test + void testConfigurateAnnotationsCompilation() { + testCompilation("test/ConfigurateAnnotations", EXPECT_CONFIG_AND_MAPPING); + } + } diff --git a/extra/interface/ap/src/test/resources/test/ConfigurateAnnotations.java b/extra/interface/ap/src/test/resources/test/ConfigurateAnnotations.java new file mode 100644 index 000000000..11265ebfd --- /dev/null +++ b/extra/interface/ap/src/test/resources/test/ConfigurateAnnotations.java @@ -0,0 +1,17 @@ +package test; + +import org.spongepowered.configurate.objectmapping.ConfigSerializable; +import org.spongepowered.configurate.objectmapping.meta.Comment; +import org.spongepowered.configurate.objectmapping.meta.Matches; +import org.spongepowered.configurate.objectmapping.meta.Required; + +@ConfigSerializable +public interface ConfigurateAnnotations { + @Comment(value = "Hello!", override = true) + @Matches(value = "abc", failureMessage = "ohno!") + @Required + String hello(); + + @Comment("Hi!") + String hi(); +} diff --git a/extra/interface/ap/src/test/resources/test/ConfigurateAnnotationsImpl.java b/extra/interface/ap/src/test/resources/test/ConfigurateAnnotationsImpl.java new file mode 100644 index 000000000..c95ca8d54 --- /dev/null +++ b/extra/interface/ap/src/test/resources/test/ConfigurateAnnotationsImpl.java @@ -0,0 +1,31 @@ +package test; + +import java.lang.Override; +import java.lang.String; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; +import org.spongepowered.configurate.objectmapping.meta.Comment; +import org.spongepowered.configurate.objectmapping.meta.Matches; +import org.spongepowered.configurate.objectmapping.meta.Required; + +/** + * Automatically generated implementation of the config */ +@ConfigSerializable +final class ConfigurateAnnotationsImpl implements ConfigurateAnnotations { + @Comment(override = true, value = "Hello!") + @Matches(failureMessage = "ohno!", value = "abc") + @Required + private String hello; + + @Comment("Hi!") + private String hi; + + @Override + public String hello() { + return hello; + } + + @Override + public String hi() { + return hi; + } +} From c8524c876363201ce7d7b16198bf51c3e0c639b1 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 6 Jan 2024 23:03:00 +0100 Subject: [PATCH 09/35] Started working on adding tests for interfaces runtime --- extra/interface/ap/build.gradle | 1 + ...onfigImplementationGeneratorProcessor.java | 8 +++- .../ConfigImplementationGenerationTest.java | 9 ++--- .../processor/DefaultValueTest.java | 5 +-- .../interfaces/processor/TestUtils.java | 27 +++++++------ extra/interface/build.gradle | 2 + .../InterfaceTypeSerializerTest.java | 5 ++- .../interfaces/TestConfigImpl.java | 27 ------------- .../configurate/interfaces/TypeUtils.java | 39 +++++++++++++++++++ 9 files changed, 71 insertions(+), 52 deletions(-) delete mode 100644 extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TestConfigImpl.java create mode 100644 extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TypeUtils.java diff --git a/extra/interface/ap/build.gradle b/extra/interface/ap/build.gradle index 776b5459b..17efe9121 100644 --- a/extra/interface/ap/build.gradle +++ b/extra/interface/ap/build.gradle @@ -9,6 +9,7 @@ dependencies { implementation projects.extra.extraInterface implementation libs.javapoet implementation libs.auto.service + annotationProcessor libs.auto.service testImplementation libs.compile.testing } diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java index 1ec2c0915..c60b209e8 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java @@ -45,8 +45,14 @@ import org.spongepowered.configurate.interfaces.Constants; import org.spongepowered.configurate.objectmapping.ConfigSerializable; +/** + * Generates an implementation for a given interface based config, + * which then can be read by Configurate. + * + * @since 4.2.0 + */ @AutoService(Processor.class) -class ConfigImplementationGeneratorProcessor extends AbstractProcessor { +public final class ConfigImplementationGeneratorProcessor extends AbstractProcessor { private final Properties mappings = new Properties(); Types typeUtils; diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java index 9379fc5be..a57633a19 100644 --- a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java @@ -16,7 +16,6 @@ */ package org.spongepowered.configurate.interfaces.processor; -import static org.spongepowered.configurate.interfaces.processor.TestUtils.EXPECT_CONFIG_AND_MAPPING; import static org.spongepowered.configurate.interfaces.processor.TestUtils.testCompilation; import org.junit.experimental.runners.Enclosed; @@ -28,22 +27,22 @@ class ConfigImplementationGenerationTest { @Test void testBasicCompilation() { - testCompilation("test/BasicConfig", EXPECT_CONFIG_AND_MAPPING); + testCompilation("test/BasicConfig"); } @Test void testExtendedCompilation() { - testCompilation("test/ExtendedConfig", EXPECT_CONFIG_AND_MAPPING); + testCompilation("test/ExtendedConfig"); } @Test void testMultiLayerCompilation() { - testCompilation("test/MultiLayerConfig", EXPECT_CONFIG_AND_MAPPING); + testCompilation("test/MultiLayerConfig"); } @Test void testConfigurateAnnotationsCompilation() { - testCompilation("test/ConfigurateAnnotations", EXPECT_CONFIG_AND_MAPPING); + testCompilation("test/ConfigurateAnnotations"); } } diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/DefaultValueTest.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/DefaultValueTest.java index b20dd02f8..e4ab29759 100644 --- a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/DefaultValueTest.java +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/DefaultValueTest.java @@ -1,6 +1,5 @@ package org.spongepowered.configurate.interfaces.processor; -import static org.spongepowered.configurate.interfaces.processor.TestUtils.EXPECT_CONFIG_AND_MAPPING; import static org.spongepowered.configurate.interfaces.processor.TestUtils.testCompilation; import org.junit.jupiter.api.Test; @@ -9,12 +8,12 @@ public class DefaultValueTest { @Test void testCorrectDefaults() { - testCompilation("test/defaults/CorrectDefaults", EXPECT_CONFIG_AND_MAPPING); + testCompilation("test/defaults/CorrectDefaults"); } @Test void testMultipleDefaults() { - testCompilation("test/defaults/MultipleDefaults", EXPECT_CONFIG_AND_MAPPING); + testCompilation("test/defaults/MultipleDefaults"); } } diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java index 0b46a1a6a..40ef4df6a 100644 --- a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java @@ -22,13 +22,14 @@ final class TestUtils { static final int EXPECT_CONFIG_AND_MAPPING = 2; - private TestUtils() {} + private TestUtils() { + } /** - * Tests whether the compilation is successful and - * that the correct mappings have been made + * Tests whether the compilation is successful, that the correct mappings + * have been made and that the generated impl matches the expected impl. */ - static Compilation testCompilation(final String sourceResourceName, final int expectedSourceCount) { + static Compilation testCompilation(final String sourceResourceName) { final Compilation compilation = javac() .withProcessors(new ConfigImplementationGeneratorProcessor()) @@ -57,16 +58,14 @@ static Compilation testCompilation(final String sourceResourceName, final int ex throw new RuntimeException(exception); } - if (expectedSourceCount != -1) { - // can't use compilation.generatedSourceFiles because the mapping - // file is written to the source output, but isn't a source file - assertEquals( - expectedSourceCount, - compilation.generatedFiles().stream() - .filter(file -> file.getName().startsWith("/SOURCE_OUTPUT/")) - .count() - ); - } + // can't use compilation.generatedSourceFiles because the mapping + // file is written to the source output, but isn't a source file + assertEquals( + EXPECT_CONFIG_AND_MAPPING, + compilation.generatedFiles().stream() + .filter(file -> file.getName().startsWith("/SOURCE_OUTPUT/")) + .count() + ); return compilation; } diff --git a/extra/interface/build.gradle b/extra/interface/build.gradle index e19086955..865b6291a 100644 --- a/extra/interface/build.gradle +++ b/extra/interface/build.gradle @@ -6,5 +6,7 @@ description = "Utility classes for generated config interface implementations" dependencies { compileOnly projects.core + + testAnnotationProcessor projects.extra.extraInterface.extraInterfaceAp testImplementation projects.core } diff --git a/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializerTest.java b/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializerTest.java index e5f1420da..de47b5469 100644 --- a/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializerTest.java +++ b/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializerTest.java @@ -18,6 +18,7 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.spongepowered.configurate.interfaces.TypeUtils.configImplementationFor; import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.jupiter.api.Assertions; @@ -35,7 +36,7 @@ void testDeserialization() throws ConfigurateException { final @Nullable TestConfig config = Assertions.assertDoesNotThrow(() -> node.get(TestConfig.class)); assertNotNull(config); - assertInstanceOf(TestConfigImpl.class, config); + assertInstanceOf(configImplementationFor(TestConfig.class), config); } @Test @@ -47,7 +48,7 @@ void testInnerDeserialization() throws ConfigurateException { final TestConfig.@Nullable TestInnerConfig config = Assertions.assertDoesNotThrow(() -> node.get(TestConfig.TestInnerConfig.class)); assertNotNull(config); - assertInstanceOf(TestConfigImpl.TestInnerConfig.class, config); + assertInstanceOf(configImplementationFor(TestConfig.TestInnerConfig.class), config); } } diff --git a/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TestConfigImpl.java b/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TestConfigImpl.java deleted file mode 100644 index f548babc8..000000000 --- a/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TestConfigImpl.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Configurate - * Copyright (C) zml and Configurate contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.spongepowered.configurate.interfaces; - -import org.spongepowered.configurate.objectmapping.ConfigSerializable; - -@ConfigSerializable -final class TestConfigImpl implements TestConfig { - - @ConfigSerializable - static final class TestInnerConfigImpl implements TestConfig.TestInnerConfig {} - -} diff --git a/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TypeUtils.java b/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TypeUtils.java new file mode 100644 index 000000000..a9900967a --- /dev/null +++ b/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TypeUtils.java @@ -0,0 +1,39 @@ +package org.spongepowered.configurate.interfaces; + +import com.google.common.base.Splitter; + +final class TypeUtils { + + private TypeUtils() {} + + static Class configImplementationFor(final Class interfaceClass) { + try { + final String implClassName = implClassNameFor(interfaceClass); + + //noinspection unchecked + return (Class) Class.forName(implClassName); + } catch (final ClassNotFoundException notFound) { + throw new IllegalStateException("No implementation for " + interfaceClass.getCanonicalName(), notFound); + } + } + + @SuppressWarnings("UnstableApiUsage") + private static String implClassNameFor(final Class interfaceClass) { + final String packageName = interfaceClass.getPackage().getName(); + // include the package name dot as well + final String classHierarchy = interfaceClass.getCanonicalName().substring(packageName.length() + 1); + + // every subclass and the class itself has 'Impl' behind it + final String implClassName = + Splitter.on('.') + .splitToStream(classHierarchy) + .reduce("", (reduced, current) -> { + if (!reduced.isEmpty()) { + reduced += "$"; + } + return reduced + current + "Impl"; + }); + return packageName + "." + implClassName; + } + +} From 6f23846da2daed0cc5a1ea58df9a2d3987ab843d Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 6 Jan 2024 23:55:38 +0100 Subject: [PATCH 10/35] Use correct impl name for mappings --- .../ConfigImplementationGenerator.java | 3 ++- ...ConfigImplementationGeneratorProcessor.java | 2 +- .../interfaces/processor/TestUtils.java | 18 ++++-------------- .../resources/test/MultiLayerConfig.properties | 2 +- .../{TestConfig.java => ConfigEmpty.java} | 4 ++-- .../InterfaceTypeSerializerTest.java | 10 +++++----- .../configurate/interfaces/TypeUtils.java | 17 ++++++++--------- .../interfaces/interface_mappings.properties | 2 -- 8 files changed, 23 insertions(+), 35 deletions(-) rename extra/interface/src/test/java/org/spongepowered/configurate/interfaces/{TestConfig.java => ConfigEmpty.java} (93%) delete mode 100644 extra/interface/src/test/resources/org/spongepowered/configurate/interfaces/interface_mappings.properties diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java index fb0a895ef..60e2fc705 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java @@ -51,7 +51,8 @@ public TypeSpec.Builder generate() { tracker.writeTo(spec); final String qualifiedName = className.reflectionName(); - this.processor.generatedClasses().put(qualifiedName, qualifiedName + "Impl"); + final String qualifiedImplName = qualifiedName.replace("$", "Impl$") + "Impl"; + this.processor.generatedClasses().put(qualifiedName, qualifiedImplName); this.processor.info("Generated implementation for %s", this.source); return spec; diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java index c60b209e8..9696266d8 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java @@ -124,7 +124,7 @@ public SourceVersion getSupportedSourceVersion() { private void writeMappings() { final FileObject resource; try { - resource = this.filer.createResource(StandardLocation.SOURCE_OUTPUT, "", Constants.MAPPING_FILE); + resource = this.filer.createResource(StandardLocation.CLASS_OUTPUT, "", Constants.MAPPING_FILE); try (Writer writer = resource.openWriter()) { this.mappings.store(writer, null); } diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java index 40ef4df6a..c88962209 100644 --- a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java @@ -2,12 +2,14 @@ import static com.google.testing.compile.CompilationSubject.assertThat; import static com.google.testing.compile.Compiler.javac; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertIterableEquals; import com.google.common.io.Resources; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; +import javax.tools.StandardLocation; +import org.spongepowered.configurate.interfaces.Constants; + import java.io.IOException; import java.net.URL; import java.nio.charset.StandardCharsets; @@ -15,13 +17,9 @@ import java.util.Collections; import java.util.List; import java.util.stream.Collectors; -import javax.tools.StandardLocation; -import org.spongepowered.configurate.interfaces.Constants; final class TestUtils { - static final int EXPECT_CONFIG_AND_MAPPING = 2; - private TestUtils() { } @@ -46,7 +44,7 @@ static Compilation testCompilation(final String sourceResourceName) { try { final String actualContent = compilation - .generatedFile(StandardLocation.SOURCE_OUTPUT, Constants.MAPPING_FILE) + .generatedFile(StandardLocation.CLASS_OUTPUT, Constants.MAPPING_FILE) .orElseThrow(() -> new IllegalStateException("Expected the interface mappings file to be created")) .getCharContent(false) .toString(); @@ -58,14 +56,6 @@ static Compilation testCompilation(final String sourceResourceName) { throw new RuntimeException(exception); } - // can't use compilation.generatedSourceFiles because the mapping - // file is written to the source output, but isn't a source file - assertEquals( - EXPECT_CONFIG_AND_MAPPING, - compilation.generatedFiles().stream() - .filter(file -> file.getName().startsWith("/SOURCE_OUTPUT/")) - .count() - ); return compilation; } diff --git a/extra/interface/ap/src/test/resources/test/MultiLayerConfig.properties b/extra/interface/ap/src/test/resources/test/MultiLayerConfig.properties index f68c62a08..b67d7d678 100644 --- a/extra/interface/ap/src/test/resources/test/MultiLayerConfig.properties +++ b/extra/interface/ap/src/test/resources/test/MultiLayerConfig.properties @@ -1,2 +1,2 @@ test.MultiLayerConfig=test.MultiLayerConfigImpl -test.MultiLayerConfig$SecondLayer=test.MultiLayerConfig$SecondLayerImpl +test.MultiLayerConfig$SecondLayer=test.MultiLayerConfigImpl$SecondLayerImpl diff --git a/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TestConfig.java b/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/ConfigEmpty.java similarity index 93% rename from extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TestConfig.java rename to extra/interface/src/test/java/org/spongepowered/configurate/interfaces/ConfigEmpty.java index 6150dd4ec..5cfc33b86 100644 --- a/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TestConfig.java +++ b/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/ConfigEmpty.java @@ -19,9 +19,9 @@ import org.spongepowered.configurate.objectmapping.ConfigSerializable; @ConfigSerializable -interface TestConfig { +interface ConfigEmpty { @ConfigSerializable - interface TestInnerConfig {} + interface ConfigEmptyInner {} } diff --git a/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializerTest.java b/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializerTest.java index de47b5469..93c5dbeeb 100644 --- a/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializerTest.java +++ b/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializerTest.java @@ -34,9 +34,9 @@ void testDeserialization() throws ConfigurateException { // doesn't deserialize if value is NullValue node.node("hello").set("world"); - final @Nullable TestConfig config = Assertions.assertDoesNotThrow(() -> node.get(TestConfig.class)); + final @Nullable ConfigEmpty config = Assertions.assertDoesNotThrow(() -> node.get(ConfigEmpty.class)); assertNotNull(config); - assertInstanceOf(configImplementationFor(TestConfig.class), config); + assertInstanceOf(configImplementationFor(ConfigEmpty.class), config); } @Test @@ -45,10 +45,10 @@ void testInnerDeserialization() throws ConfigurateException { // doesn't deserialize if value is NullValue node.node("hello").set("world"); - final TestConfig.@Nullable TestInnerConfig config = - Assertions.assertDoesNotThrow(() -> node.get(TestConfig.TestInnerConfig.class)); + final ConfigEmpty.@Nullable ConfigEmptyInner config = + Assertions.assertDoesNotThrow(() -> node.get(ConfigEmpty.ConfigEmptyInner.class)); assertNotNull(config); - assertInstanceOf(configImplementationFor(TestConfig.TestInnerConfig.class), config); + assertInstanceOf(configImplementationFor(ConfigEmpty.ConfigEmptyInner.class), config); } } diff --git a/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TypeUtils.java b/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TypeUtils.java index a9900967a..cd7640236 100644 --- a/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TypeUtils.java +++ b/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TypeUtils.java @@ -1,6 +1,6 @@ package org.spongepowered.configurate.interfaces; -import com.google.common.base.Splitter; +import java.util.Arrays; final class TypeUtils { @@ -25,14 +25,13 @@ private static String implClassNameFor(final Class interfaceClass) { // every subclass and the class itself has 'Impl' behind it final String implClassName = - Splitter.on('.') - .splitToStream(classHierarchy) - .reduce("", (reduced, current) -> { - if (!reduced.isEmpty()) { - reduced += "$"; - } - return reduced + current + "Impl"; - }); + Arrays.stream(classHierarchy.split("\\.")) + .reduce("", (reduced, current) -> { + if (!reduced.isEmpty()) { + reduced += "$"; + } + return reduced + current + "Impl"; + }); return packageName + "." + implClassName; } diff --git a/extra/interface/src/test/resources/org/spongepowered/configurate/interfaces/interface_mappings.properties b/extra/interface/src/test/resources/org/spongepowered/configurate/interfaces/interface_mappings.properties deleted file mode 100644 index d7460e41e..000000000 --- a/extra/interface/src/test/resources/org/spongepowered/configurate/interfaces/interface_mappings.properties +++ /dev/null @@ -1,2 +0,0 @@ -org.spongepowered.configurate.interfaces.TestConfig=org.spongepowered.configurate.interfaces.TestConfigImpl -org.spongepowered.configurate.interfaces.TestConfig$TestInnerConfig=org.spongepowered.configurate.interfaces.TestConfigImpl$TestInnerConfigImpl From f07dc2f60caa8ad2c2eaabe613a0d3b6e57f9ba6 Mon Sep 17 00:00:00 2001 From: zml Date: Sat, 6 Jan 2024 19:28:02 -0800 Subject: [PATCH 11/35] chore(build): Only set test flags on newer JDK versions J8 still fails to build locally, but with a different issue --- extra/interface/ap/build.gradle | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/extra/interface/ap/build.gradle b/extra/interface/ap/build.gradle index 17efe9121..9b4dc7e91 100644 --- a/extra/interface/ap/build.gradle +++ b/extra/interface/ap/build.gradle @@ -18,8 +18,12 @@ dependencies { tasks.withType(Javadoc).configureEach { enabled = false } tasks.withType(Test).configureEach { - // See: https://github.com/google/compile-testing/issues/222 - jvmArgs '--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED' - jvmArgs '--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED' - jvmArgs '--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED' + doFirst { + // See: https://github.com/google/compile-testing/issues/222 + if (javaLauncher.get().metadata.languageVersion >= JavaLanguageVersion.of(9)) { + jvmArgs '--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED' + jvmArgs '--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED' + jvmArgs '--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED' + } + } } From 6c7f27c4044c2b239d0928a010996baa0f88b434 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 13 Jan 2024 13:27:40 +0100 Subject: [PATCH 12/35] Add all annotations that support fields. Use messager for errors --- .../processor/AnnotationConfigurate.java | 30 --------- .../processor/AnnotationDefaults.java | 44 +++++++----- .../processor/AnnotationHidden.java | 17 ----- .../processor/AnnotationOthers.java | 67 +++++++++++++++++++ .../processor/AnnotationProcessor.java | 29 ++++++++ .../processor/AnnotationProcessorHandler.java | 31 +++++++++ .../ConfigImplementationGenerator.java | 23 +++---- ...onfigImplementationGeneratorProcessor.java | 6 +- .../processor/FieldSpecBuilderTracker.java | 49 ++++++++++++++ .../processor/TypeSpecBuilderTracker.java | 2 +- .../interfaces/processor/Utils.java | 16 +++-- .../ConfigImplementationGenerationTest.java | 10 +-- .../util/AnnotationOthersAnnotations.java | 22 ++++++ .../{test => structure}/BasicConfig.java | 2 +- .../{test => structure}/BasicConfigImpl.java | 2 +- .../{test => structure}/ExtendedConfig.java | 2 +- .../ExtendedConfigImpl.java | 2 +- .../{test => structure}/MultiLayerConfig.java | 2 +- .../structure/MultiLayerConfig.properties | 2 + .../MultiLayerConfigImpl.java | 2 +- .../test/ConfigurateAnnotationsImpl.java | 31 --------- .../test/MultiLayerConfig.properties | 2 - ...Annotations.java => OtherAnnotations.java} | 15 ++++- .../resources/test/OtherAnnotationsImpl.java | 62 +++++++++++++++++ 24 files changed, 343 insertions(+), 127 deletions(-) delete mode 100644 extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationConfigurate.java delete mode 100644 extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationHidden.java create mode 100644 extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationOthers.java create mode 100644 extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessor.java create mode 100644 extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessorHandler.java create mode 100644 extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/FieldSpecBuilderTracker.java create mode 100644 extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/util/AnnotationOthersAnnotations.java rename extra/interface/ap/src/test/resources/{test => structure}/BasicConfig.java (91%) rename extra/interface/ap/src/test/resources/{test => structure}/BasicConfigImpl.java (96%) rename extra/interface/ap/src/test/resources/{test => structure}/ExtendedConfig.java (91%) rename extra/interface/ap/src/test/resources/{test => structure}/ExtendedConfigImpl.java (97%) rename extra/interface/ap/src/test/resources/{test => structure}/MultiLayerConfig.java (93%) create mode 100644 extra/interface/ap/src/test/resources/structure/MultiLayerConfig.properties rename extra/interface/ap/src/test/resources/{test => structure}/MultiLayerConfigImpl.java (97%) delete mode 100644 extra/interface/ap/src/test/resources/test/ConfigurateAnnotationsImpl.java delete mode 100644 extra/interface/ap/src/test/resources/test/MultiLayerConfig.properties rename extra/interface/ap/src/test/resources/test/{ConfigurateAnnotations.java => OtherAnnotations.java} (51%) create mode 100644 extra/interface/ap/src/test/resources/test/OtherAnnotationsImpl.java diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationConfigurate.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationConfigurate.java deleted file mode 100644 index c2f2bfb79..000000000 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationConfigurate.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.spongepowered.configurate.interfaces.processor; - -import com.squareup.javapoet.AnnotationSpec; -import com.squareup.javapoet.FieldSpec; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.List; -import javax.lang.model.element.ExecutableElement; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.spongepowered.configurate.objectmapping.meta.Comment; -import org.spongepowered.configurate.objectmapping.meta.Matches; -import org.spongepowered.configurate.objectmapping.meta.Required; - -final class AnnotationConfigurate { - - private static final List> annotations = Arrays.asList(Comment.class, Matches.class, Required.class); - - private AnnotationConfigurate() {} - - static void process(final ExecutableElement element, final FieldSpec.Builder fieldSpec) { - for (final Class annotation : annotations) { - final @Nullable Annotation current = element.getAnnotation(annotation); - //noinspection ConstantValue - if (current != null) { - fieldSpec.addAnnotation(AnnotationSpec.get(current)); - } - } - } - -} diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationDefaults.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationDefaults.java index 3686d87c9..fd200c1e0 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationDefaults.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationDefaults.java @@ -1,10 +1,10 @@ package org.spongepowered.configurate.interfaces.processor; +import static org.spongepowered.configurate.interfaces.processor.Utils.annotation; + import com.google.auto.common.MoreTypes; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.FieldSpec; -import java.lang.annotation.Annotation; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; @@ -13,24 +13,38 @@ import org.spongepowered.configurate.interfaces.meta.defaults.DefaultNumeric; import org.spongepowered.configurate.interfaces.meta.defaults.DefaultString; -final class AnnotationDefaults { +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +final class AnnotationDefaults implements AnnotationProcessor { + + static final AnnotationDefaults INSTANCE = new AnnotationDefaults(); private AnnotationDefaults() {} - static void process(final ExecutableElement element, final TypeMirror nodeType, final FieldSpec.Builder fieldSpec) { - final @Nullable DefaultBoolean defaultBoolean = element.getAnnotation(DefaultBoolean.class); - final @Nullable DefaultDecimal defaultDecimal = element.getAnnotation(DefaultDecimal.class); - final @Nullable DefaultNumeric defaultNumeric = element.getAnnotation(DefaultNumeric.class); - final @Nullable DefaultString defaultString = element.getAnnotation(DefaultString.class); - //noinspection ConstantValue not everything is nonnull by default + @Override + public Set> processes() { + return new HashSet<>(Arrays.asList(DefaultBoolean.class, DefaultDecimal.class, DefaultNumeric.class, DefaultString.class)); + } + + @Override + public void process( + final ExecutableElement element, + final TypeMirror nodeType, + final FieldSpecBuilderTracker fieldSpec + ) throws IllegalStateException { + final @Nullable DefaultBoolean defaultBoolean = annotation(element, DefaultBoolean.class); + final @Nullable DefaultDecimal defaultDecimal = annotation(element, DefaultDecimal.class); + final @Nullable DefaultNumeric defaultNumeric = annotation(element, DefaultNumeric.class); + final @Nullable DefaultString defaultString = annotation(element, DefaultString.class); final boolean hasDefault = defaultBoolean != null || defaultDecimal != null || defaultNumeric != null || defaultString != null; @Nullable Object defaultValue = null; boolean isString = false; - //noinspection ConstantValue if (hasDefault) { if (MoreTypes.isTypeOf(Boolean.TYPE, nodeType)) { - //noinspection ConstantValue if (defaultBoolean == null) { throw new IllegalStateException("A default value of the incorrect type was provided for " + element); } @@ -38,7 +52,6 @@ static void process(final ExecutableElement element, final TypeMirror nodeType, buildAndAddAnnotation(fieldSpec, DefaultBoolean.class, defaultValue, false); } else if (Utils.isDecimal(nodeType)) { - //noinspection ConstantValue if (defaultDecimal == null) { throw new IllegalStateException("A default value of the incorrect type was provided for " + element); } @@ -46,7 +59,6 @@ static void process(final ExecutableElement element, final TypeMirror nodeType, buildAndAddAnnotation(fieldSpec, DefaultDecimal.class, defaultValue, false); } else if (Utils.isNumeric(nodeType)) { - //noinspection ConstantValue if (defaultNumeric == null) { throw new IllegalStateException("A default value of the incorrect type was provided for " + element); } @@ -57,7 +69,6 @@ static void process(final ExecutableElement element, final TypeMirror nodeType, buildAndAddAnnotation(fieldSpec, DefaultNumeric.class, defaultValue, false); } else if (MoreTypes.isTypeOf(String.class, nodeType)) { - //noinspection ConstantValue if (defaultString == null) { throw new IllegalStateException("A default value of the incorrect type was provided for " + element); } @@ -72,8 +83,8 @@ static void process(final ExecutableElement element, final TypeMirror nodeType, } } - private static void buildAndAddAnnotation( - final FieldSpec.Builder fieldSpec, + private void buildAndAddAnnotation( + final FieldSpecBuilderTracker fieldSpec, final Class annotationClass, final Object value, final boolean isString @@ -81,7 +92,6 @@ private static void buildAndAddAnnotation( fieldSpec.addAnnotation( AnnotationSpec.builder(annotationClass) .addMember("value", CodeBlock.of(isString ? "$S" : "$L", value)) - .build() ); } diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationHidden.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationHidden.java deleted file mode 100644 index c35998ebb..000000000 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationHidden.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.spongepowered.configurate.interfaces.processor; - -import com.squareup.javapoet.FieldSpec; -import javax.lang.model.element.ExecutableElement; -import org.spongepowered.configurate.interfaces.meta.Hidden; - -final class AnnotationHidden { - - private AnnotationHidden() {} - - static void process(final ExecutableElement element, final FieldSpec.Builder fieldSpec) { - if (Utils.hasAnnotation(element, Hidden.class)) { - fieldSpec.addAnnotation(Hidden.class); - } - } - -} diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationOthers.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationOthers.java new file mode 100644 index 000000000..fa60ad119 --- /dev/null +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationOthers.java @@ -0,0 +1,67 @@ +package org.spongepowered.configurate.interfaces.processor; + +import static org.spongepowered.configurate.interfaces.processor.Utils.annotation; + +import com.google.auto.common.MoreElements; + +import com.squareup.javapoet.AnnotationSpec; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import java.util.Arrays; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +import java.lang.annotation.Annotation; +import java.util.HashSet; +import java.util.Set; + +import org.checkerframework.checker.nullness.qual.Nullable; + +final class AnnotationOthers implements AnnotationProcessor { + + static final AnnotationOthers INSTANCE = new AnnotationOthers(); + + private AnnotationOthers() {} + + @Override + public Set> processes() { + return new HashSet<>(); + } + + @Override + public void process(final ExecutableElement element, final TypeMirror nodeType, final FieldSpecBuilderTracker fieldSpec) { + for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) { + //noinspection UnstableApiUsage + final TypeElement annotationType = MoreElements.asType(annotationMirror.getAnnotationType().asElement()); + + // only handle not yet processed annotations + if (fieldSpec.isProcessed(annotationType)) { + continue; + } + + final @Nullable Target target = annotation(annotationType, Target.class); + final boolean isCompatible = target == null || Arrays.stream(target.value()).anyMatch(elementType -> ElementType.FIELD == elementType); + // an annotation is only compatible if it supports fields, if it has no target it supports everything + if (!isCompatible) { + continue; + } + + final @Nullable Retention retention = annotation(annotationType, Retention.class); + final boolean hasRuntimeRetention = retention != null && RetentionPolicy.RUNTIME == retention.value(); + // not needed to add an annotation if it has no runtime retention + if (!hasRuntimeRetention) { + continue; + } + + fieldSpec.addAnnotation(AnnotationSpec.get(annotationMirror)); + } + } + +} diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessor.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessor.java new file mode 100644 index 000000000..e034acfb8 --- /dev/null +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessor.java @@ -0,0 +1,29 @@ +package org.spongepowered.configurate.interfaces.processor; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; + +import java.lang.annotation.Annotation; +import java.util.Set; + +interface AnnotationProcessor { + + /** + * A set of annotations this processor will process. + * + * @return a set of annotations this processor will process. + */ + Set> processes(); + + /** + * Process a method. + * There is no guarantee that one of the {@link #processes()} annotations is present on this element. + * + * @param element the method that is being processed + * @param nodeType the type of the field that is being generated + * @param fieldSpec the builder of the field that is being generated + * @throws IllegalStateException when something goes wrong + */ + void process(ExecutableElement element, TypeMirror nodeType, FieldSpecBuilderTracker fieldSpec) throws IllegalStateException; + +} diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessorHandler.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessorHandler.java new file mode 100644 index 000000000..13cbe1b56 --- /dev/null +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessorHandler.java @@ -0,0 +1,31 @@ +package org.spongepowered.configurate.interfaces.processor; + +import com.squareup.javapoet.FieldSpec; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; + +import java.util.ArrayList; +import java.util.List; + +final class AnnotationProcessorHandler { + + private static final List HANDLERS = new ArrayList<>(); + + static { + HANDLERS.add(AnnotationDefaults.INSTANCE); + // always add others as last because it adds all annotations that have not been processed + HANDLERS.add(AnnotationOthers.INSTANCE); + } + + private AnnotationProcessorHandler() {} + + static void handle(final ExecutableElement element, final TypeMirror nodeType, final FieldSpec.Builder fieldSpec) { + final FieldSpecBuilderTracker fieldTracker = new FieldSpecBuilderTracker(fieldSpec); + + for (AnnotationProcessor handler : HANDLERS) { + handler.process(element, nodeType, fieldTracker); + fieldTracker.processed(handler.processes()); + } + } + +} diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java index 60e2fc705..813be9022 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java @@ -8,8 +8,6 @@ import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; -import java.util.List; -import java.util.Locale; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; @@ -21,6 +19,8 @@ import org.spongepowered.configurate.objectmapping.ConfigSerializable; import org.spongepowered.configurate.objectmapping.meta.PostProcess; +import java.util.List; + class ConfigImplementationGenerator { private final ConfigImplementationGeneratorProcessor processor; @@ -96,18 +96,19 @@ private void gatherElementSpec( } this.processor.info("Overriding implementation for %s as it's not excluded", element); } else if (excluded) { - throw new IllegalStateException(String.format( - Locale.ROOT, + this.processor.error( "Cannot make config due to method %s, which is a method excluded method that has no implementation!", element - )); + ); + continue; } // all methods are either setters or getters past this point final List parameters = element.getParameters(); if (parameters.size() > 1) { - throw new IllegalStateException("Setters cannot have more than one parameter! Method: " + element); + this.processor.error("Setters cannot have more than one parameter! Method: " + element); + continue; } final String simpleName = element.getSimpleName().toString(); @@ -128,12 +129,13 @@ private void gatherElementSpec( if (!MoreTypes.isTypeOf(Void.TYPE, nodeType)) { // the return type can be a parent type of parameter, but it has to be assignable if (!this.processor.typeUtils.isAssignable(parameter.asType(), nodeType)) { - throw new IllegalStateException(String.format( + this.processor.error( "Cannot create a setter with return type %s for argument type %s. Method: %s", nodeType, parameter.asType(), element - )); + ); + continue; } method.addStatement("return this.$N", element.getSimpleName()); } @@ -152,10 +154,7 @@ private void gatherElementSpec( final FieldSpec.Builder fieldSpec = FieldSpec.builder(TypeName.get(nodeType), simpleName, Modifier.PRIVATE); //todo add tests for hidden in both ap and interfaces and defaults in interfaces - AnnotationDefaults.process(element, nodeType, fieldSpec); - AnnotationHidden.process(element, fieldSpec); - // add Configurate's build-in annotations as well - AnnotationConfigurate.process(element, fieldSpec); + AnnotationProcessorHandler.handle(element, nodeType, fieldSpec); spec.add(simpleName, fieldSpec); } diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java index 9696266d8..5fd5eb8e3 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java @@ -96,7 +96,7 @@ public boolean process(final Set ignored, final RoundEnvi try { processInterface(typeElement); } catch (final IOException exception) { - throw new RuntimeException(exception); + error(exception.getMessage()); } } @@ -141,4 +141,8 @@ void info(final String message, final Object... arguments) { this.messager.printMessage(Kind.NOTE, String.format(Locale.ROOT, message, arguments)); } + void error(final String message, final Object... arguments) { + this.messager.printMessage(Kind.ERROR, String.format(Locale.ROOT, message, arguments)); + } + } diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/FieldSpecBuilderTracker.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/FieldSpecBuilderTracker.java new file mode 100644 index 000000000..bd163965f --- /dev/null +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/FieldSpecBuilderTracker.java @@ -0,0 +1,49 @@ +package org.spongepowered.configurate.interfaces.processor; + +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.FieldSpec; +import javax.lang.model.element.TypeElement; + +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +final class FieldSpecBuilderTracker { + + private final Set processed = new HashSet<>(); + private final FieldSpec.Builder builder; + + FieldSpecBuilderTracker(final FieldSpec.Builder builder) { + this.builder = builder; + } + + void addAnnotation(final AnnotationSpec annotation) { + this.processed.add(((ClassName) annotation.type)); + this.builder.addAnnotation(annotation); + } + + void addAnnotation(final AnnotationSpec.Builder annotationBuilder) { + addAnnotation(annotationBuilder.build()); + } + + void initializer(final String format, final Object... args) { + this.builder.initializer(format, args); + } + + boolean isProcessed(final Class annotation) { + return this.processed.contains(ClassName.get(annotation)); + } + + boolean isProcessed(final TypeElement annotationType) { + return this.processed.contains(ClassName.get(annotationType)); + } + + void processed(final Collection> annotations) { + for (Class annotation : annotations) { + this.processed.add(ClassName.get(annotation)); + } + } + +} diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java index 56a37cadc..72794f1b5 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java @@ -29,7 +29,7 @@ * {@link TypeSpec.Builder} does not keep track of duplicates, resulting in failures to compile. * This will only allow a single definition of a given method/field */ -class TypeSpecBuilderTracker { +final class TypeSpecBuilderTracker { private final Map fieldSpecs = new LinkedHashMap<>(); private final Map methodSpecs = new LinkedHashMap<>(); diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/Utils.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/Utils.java index 8443ec089..494b8a02b 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/Utils.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/Utils.java @@ -7,16 +7,24 @@ import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.configurate.objectmapping.ConfigSerializable; final class Utils { - private Utils() { - } + private Utils() {} static boolean hasAnnotation(final AnnotatedConstruct element, final Class annotation) { - //noinspection ConstantValue not everything is nonnull by default - return element.getAnnotation(annotation) != null; + return annotation(element, annotation) != null; + } + + /** + * The same as {@link AnnotatedConstruct#getAnnotation(Class)} except that + * you don't have to suppress the ConstantValue warning everywhere. + */ + @SuppressWarnings("DataFlowIssue") + static @Nullable T annotation(final AnnotatedConstruct construct, final Class annotation) { + return construct.getAnnotation(annotation); } static boolean isNestedConfig(final TypeElement type) { diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java index a57633a19..e09b6e295 100644 --- a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerationTest.java @@ -27,22 +27,22 @@ class ConfigImplementationGenerationTest { @Test void testBasicCompilation() { - testCompilation("test/BasicConfig"); + testCompilation("structure/BasicConfig"); } @Test void testExtendedCompilation() { - testCompilation("test/ExtendedConfig"); + testCompilation("structure/ExtendedConfig"); } @Test void testMultiLayerCompilation() { - testCompilation("test/MultiLayerConfig"); + testCompilation("structure/MultiLayerConfig"); } @Test - void testConfigurateAnnotationsCompilation() { - testCompilation("test/ConfigurateAnnotations"); + void testAnnotationOthersCompilation() { + testCompilation("test/OtherAnnotations"); } } diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/util/AnnotationOthersAnnotations.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/util/AnnotationOthersAnnotations.java new file mode 100644 index 000000000..b6523675b --- /dev/null +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/util/AnnotationOthersAnnotations.java @@ -0,0 +1,22 @@ +package org.spongepowered.configurate.interfaces.processor.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +public final class AnnotationOthersAnnotations { + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface AnnotationNoField {} + + @Retention(RetentionPolicy.RUNTIME) + public @interface AnnotationNoTarget {} + + @Target({ElementType.METHOD, ElementType.FIELD}) + @Retention(RetentionPolicy.CLASS) + public @interface AnnotationOtherRetention {} + + @Target({ElementType.METHOD, ElementType.FIELD}) + public @interface AnnotationNoRetention {} +} diff --git a/extra/interface/ap/src/test/resources/test/BasicConfig.java b/extra/interface/ap/src/test/resources/structure/BasicConfig.java similarity index 91% rename from extra/interface/ap/src/test/resources/test/BasicConfig.java rename to extra/interface/ap/src/test/resources/structure/BasicConfig.java index 9cfd63bb2..db9dc03f2 100644 --- a/extra/interface/ap/src/test/resources/test/BasicConfig.java +++ b/extra/interface/ap/src/test/resources/structure/BasicConfig.java @@ -1,4 +1,4 @@ -package test; +package structure; import org.spongepowered.configurate.objectmapping.ConfigSerializable; diff --git a/extra/interface/ap/src/test/resources/test/BasicConfigImpl.java b/extra/interface/ap/src/test/resources/structure/BasicConfigImpl.java similarity index 96% rename from extra/interface/ap/src/test/resources/test/BasicConfigImpl.java rename to extra/interface/ap/src/test/resources/structure/BasicConfigImpl.java index 1fe04a5b9..68952ac9f 100644 --- a/extra/interface/ap/src/test/resources/test/BasicConfigImpl.java +++ b/extra/interface/ap/src/test/resources/structure/BasicConfigImpl.java @@ -1,4 +1,4 @@ -package test; +package structure; import java.lang.Override; import java.lang.String; diff --git a/extra/interface/ap/src/test/resources/test/ExtendedConfig.java b/extra/interface/ap/src/test/resources/structure/ExtendedConfig.java similarity index 91% rename from extra/interface/ap/src/test/resources/test/ExtendedConfig.java rename to extra/interface/ap/src/test/resources/structure/ExtendedConfig.java index f2c26a0b5..78ba9a1c3 100644 --- a/extra/interface/ap/src/test/resources/test/ExtendedConfig.java +++ b/extra/interface/ap/src/test/resources/structure/ExtendedConfig.java @@ -1,4 +1,4 @@ -package test; +package structure; import org.spongepowered.configurate.objectmapping.ConfigSerializable; diff --git a/extra/interface/ap/src/test/resources/test/ExtendedConfigImpl.java b/extra/interface/ap/src/test/resources/structure/ExtendedConfigImpl.java similarity index 97% rename from extra/interface/ap/src/test/resources/test/ExtendedConfigImpl.java rename to extra/interface/ap/src/test/resources/structure/ExtendedConfigImpl.java index 7dfab8bf6..a2fc4e63b 100644 --- a/extra/interface/ap/src/test/resources/test/ExtendedConfigImpl.java +++ b/extra/interface/ap/src/test/resources/structure/ExtendedConfigImpl.java @@ -1,4 +1,4 @@ -package test; +package structure; import java.lang.Number; import java.lang.Override; diff --git a/extra/interface/ap/src/test/resources/test/MultiLayerConfig.java b/extra/interface/ap/src/test/resources/structure/MultiLayerConfig.java similarity index 93% rename from extra/interface/ap/src/test/resources/test/MultiLayerConfig.java rename to extra/interface/ap/src/test/resources/structure/MultiLayerConfig.java index 48e3ca220..33de1c0eb 100644 --- a/extra/interface/ap/src/test/resources/test/MultiLayerConfig.java +++ b/extra/interface/ap/src/test/resources/structure/MultiLayerConfig.java @@ -1,4 +1,4 @@ -package test; +package structure; import org.spongepowered.configurate.objectmapping.ConfigSerializable; diff --git a/extra/interface/ap/src/test/resources/structure/MultiLayerConfig.properties b/extra/interface/ap/src/test/resources/structure/MultiLayerConfig.properties new file mode 100644 index 000000000..a437a7fe5 --- /dev/null +++ b/extra/interface/ap/src/test/resources/structure/MultiLayerConfig.properties @@ -0,0 +1,2 @@ +structure.MultiLayerConfig=structure.MultiLayerConfigImpl +structure.MultiLayerConfig$SecondLayer=structure.MultiLayerConfigImpl$SecondLayerImpl diff --git a/extra/interface/ap/src/test/resources/test/MultiLayerConfigImpl.java b/extra/interface/ap/src/test/resources/structure/MultiLayerConfigImpl.java similarity index 97% rename from extra/interface/ap/src/test/resources/test/MultiLayerConfigImpl.java rename to extra/interface/ap/src/test/resources/structure/MultiLayerConfigImpl.java index b38a97b3c..0bc2ef001 100644 --- a/extra/interface/ap/src/test/resources/test/MultiLayerConfigImpl.java +++ b/extra/interface/ap/src/test/resources/structure/MultiLayerConfigImpl.java @@ -1,4 +1,4 @@ -package test; +package structure; import java.lang.Override; import java.lang.String; diff --git a/extra/interface/ap/src/test/resources/test/ConfigurateAnnotationsImpl.java b/extra/interface/ap/src/test/resources/test/ConfigurateAnnotationsImpl.java deleted file mode 100644 index c95ca8d54..000000000 --- a/extra/interface/ap/src/test/resources/test/ConfigurateAnnotationsImpl.java +++ /dev/null @@ -1,31 +0,0 @@ -package test; - -import java.lang.Override; -import java.lang.String; -import org.spongepowered.configurate.objectmapping.ConfigSerializable; -import org.spongepowered.configurate.objectmapping.meta.Comment; -import org.spongepowered.configurate.objectmapping.meta.Matches; -import org.spongepowered.configurate.objectmapping.meta.Required; - -/** - * Automatically generated implementation of the config */ -@ConfigSerializable -final class ConfigurateAnnotationsImpl implements ConfigurateAnnotations { - @Comment(override = true, value = "Hello!") - @Matches(failureMessage = "ohno!", value = "abc") - @Required - private String hello; - - @Comment("Hi!") - private String hi; - - @Override - public String hello() { - return hello; - } - - @Override - public String hi() { - return hi; - } -} diff --git a/extra/interface/ap/src/test/resources/test/MultiLayerConfig.properties b/extra/interface/ap/src/test/resources/test/MultiLayerConfig.properties deleted file mode 100644 index b67d7d678..000000000 --- a/extra/interface/ap/src/test/resources/test/MultiLayerConfig.properties +++ /dev/null @@ -1,2 +0,0 @@ -test.MultiLayerConfig=test.MultiLayerConfigImpl -test.MultiLayerConfig$SecondLayer=test.MultiLayerConfigImpl$SecondLayerImpl diff --git a/extra/interface/ap/src/test/resources/test/ConfigurateAnnotations.java b/extra/interface/ap/src/test/resources/test/OtherAnnotations.java similarity index 51% rename from extra/interface/ap/src/test/resources/test/ConfigurateAnnotations.java rename to extra/interface/ap/src/test/resources/test/OtherAnnotations.java index 11265ebfd..dc7c7ef5b 100644 --- a/extra/interface/ap/src/test/resources/test/ConfigurateAnnotations.java +++ b/extra/interface/ap/src/test/resources/test/OtherAnnotations.java @@ -1,12 +1,13 @@ package test; +import org.spongepowered.configurate.interfaces.processor.util.AnnotationOthersAnnotations; import org.spongepowered.configurate.objectmapping.ConfigSerializable; import org.spongepowered.configurate.objectmapping.meta.Comment; import org.spongepowered.configurate.objectmapping.meta.Matches; import org.spongepowered.configurate.objectmapping.meta.Required; @ConfigSerializable -public interface ConfigurateAnnotations { +public interface OtherAnnotations { @Comment(value = "Hello!", override = true) @Matches(value = "abc", failureMessage = "ohno!") @Required @@ -14,4 +15,16 @@ public interface ConfigurateAnnotations { @Comment("Hi!") String hi(); + + @AnnotationOthersAnnotations.AnnotationNoField + String noField(); + + @AnnotationOthersAnnotations.AnnotationNoTarget + String noTarget(); + + @AnnotationOthersAnnotations.AnnotationOtherRetention + String otherRetention(); + + @AnnotationOthersAnnotations.AnnotationNoRetention + String noRetention(); } diff --git a/extra/interface/ap/src/test/resources/test/OtherAnnotationsImpl.java b/extra/interface/ap/src/test/resources/test/OtherAnnotationsImpl.java new file mode 100644 index 000000000..de0bdb968 --- /dev/null +++ b/extra/interface/ap/src/test/resources/test/OtherAnnotationsImpl.java @@ -0,0 +1,62 @@ +package test; + +import java.lang.Override; +import java.lang.String; + +import org.spongepowered.configurate.interfaces.processor.util.AnnotationOthersAnnotations; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; +import org.spongepowered.configurate.objectmapping.meta.Comment; +import org.spongepowered.configurate.objectmapping.meta.Matches; +import org.spongepowered.configurate.objectmapping.meta.Required; + +/** + * Automatically generated implementation of the config */ +@ConfigSerializable +final class OtherAnnotationsImpl implements OtherAnnotations { + @Comment(value = "Hello!", override = true) + @Matches(value = "abc", failureMessage = "ohno!") + @Required + private String hello; + + @Comment("Hi!") + private String hi; + + private String noField; + + @AnnotationOthersAnnotations.AnnotationNoTarget + private String noTarget; + + private String otherRetention; + + private String noRetention; + + @Override + public String hello() { + return hello; + } + + @Override + public String hi() { + return hi; + } + + @Override + public String noField() { + return noField; + } + + @Override + public String noTarget() { + return noTarget; + } + + @Override + public String otherRetention() { + return otherRetention; + } + + @Override + public String noRetention() { + return noRetention; + } +} From c009449005a9461b1c719e571e25cedfbb363880 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 13 Jan 2024 21:27:39 +0100 Subject: [PATCH 13/35] Made AnnotationDefaults easier to follow --- .../processor/AnnotationDefaults.java | 51 +++++++++---------- .../ConfigImplementationGenerator.java | 19 +++---- ...onfigImplementationGeneratorProcessor.java | 4 -- .../processor/DefaultValueTest.java | 4 +- .../interfaces/processor/TestUtils.java | 3 +- .../{test => }/defaults/CorrectDefaults.java | 2 +- .../defaults/CorrectDefaultsImpl.java | 2 +- .../{test => }/defaults/MultipleDefaults.java | 2 +- .../defaults/MultipleDefaultsImpl.java | 2 +- 9 files changed, 39 insertions(+), 50 deletions(-) rename extra/interface/ap/src/test/resources/{test => }/defaults/CorrectDefaults.java (98%) rename extra/interface/ap/src/test/resources/{test => }/defaults/CorrectDefaultsImpl.java (99%) rename extra/interface/ap/src/test/resources/{test => }/defaults/MultipleDefaults.java (96%) rename extra/interface/ap/src/test/resources/{test => }/defaults/MultipleDefaultsImpl.java (97%) diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationDefaults.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationDefaults.java index fd200c1e0..6e1b72c56 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationDefaults.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationDefaults.java @@ -4,7 +4,6 @@ import com.google.auto.common.MoreTypes; import com.squareup.javapoet.AnnotationSpec; -import com.squareup.javapoet.CodeBlock; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; @@ -41,58 +40,58 @@ public void process( final @Nullable DefaultString defaultString = annotation(element, DefaultString.class); final boolean hasDefault = defaultBoolean != null || defaultDecimal != null || defaultNumeric != null || defaultString != null; - @Nullable Object defaultValue = null; - boolean isString = false; + @Nullable Class annnotationType = null; + @Nullable Object value = null; if (hasDefault) { if (MoreTypes.isTypeOf(Boolean.TYPE, nodeType)) { if (defaultBoolean == null) { throw new IllegalStateException("A default value of the incorrect type was provided for " + element); } - defaultValue = defaultBoolean.value(); - buildAndAddAnnotation(fieldSpec, DefaultBoolean.class, defaultValue, false); + annnotationType = DefaultBoolean.class; + value = defaultBoolean.value(); } else if (Utils.isDecimal(nodeType)) { if (defaultDecimal == null) { throw new IllegalStateException("A default value of the incorrect type was provided for " + element); } - defaultValue = defaultDecimal.value() + (MoreTypes.isTypeOf(Float.TYPE, nodeType) ? "F" : "D"); - buildAndAddAnnotation(fieldSpec, DefaultDecimal.class, defaultValue, false); + annnotationType = DefaultDecimal.class; + value = defaultDecimal.value(); } else if (Utils.isNumeric(nodeType)) { if (defaultNumeric == null) { throw new IllegalStateException("A default value of the incorrect type was provided for " + element); } - defaultValue = defaultNumeric.value(); - if (MoreTypes.isTypeOf(Long.TYPE, nodeType)) { - defaultValue += "L"; - } - buildAndAddAnnotation(fieldSpec, DefaultNumeric.class, defaultValue, false); + annnotationType = DefaultNumeric.class; + value = defaultNumeric.value(); } else if (MoreTypes.isTypeOf(String.class, nodeType)) { if (defaultString == null) { throw new IllegalStateException("A default value of the incorrect type was provided for " + element); } - defaultValue = defaultString.value(); - isString = true; - buildAndAddAnnotation(fieldSpec, DefaultString.class, defaultValue, true); + annnotationType = DefaultString.class; + value = defaultString.value(); } } - if (defaultValue != null) { - fieldSpec.initializer(isString ? "$S" : "$L", defaultValue); + if (annnotationType == null) { + return; + } + + final boolean isString = value instanceof String; + + // special cases are floats and longs, because the default for decimals + // is double and for numerics it's int. + if (MoreTypes.isTypeOf(Float.TYPE, nodeType)) { + value = value + "F"; + } else if (MoreTypes.isTypeOf(Long.TYPE, nodeType)) { + value = value + "L"; } - } - private void buildAndAddAnnotation( - final FieldSpecBuilderTracker fieldSpec, - final Class annotationClass, - final Object value, - final boolean isString - ) { fieldSpec.addAnnotation( - AnnotationSpec.builder(annotationClass) - .addMember("value", CodeBlock.of(isString ? "$S" : "$L", value)) + AnnotationSpec.builder(annnotationType) + .addMember("value", isString ? "$S" : "$L", value) ); + fieldSpec.initializer(isString ? "$S" : "$L", value); } } diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java index 813be9022..29fb29c9b 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java @@ -37,8 +37,6 @@ class ConfigImplementationGenerator { public TypeSpec.Builder generate() { final ClassName className = ClassName.get(this.source); - this.processor.info("Generating implementation for %s", this.source); - final TypeSpec.Builder spec = TypeSpec .classBuilder(className.simpleName() + "Impl") .addSuperinterface(className) @@ -53,7 +51,6 @@ public TypeSpec.Builder generate() { final String qualifiedName = className.reflectionName(); final String qualifiedImplName = qualifiedName.replace("$", "Impl$") + "Impl"; this.processor.generatedClasses().put(qualifiedName, qualifiedImplName); - this.processor.info("Generated implementation for %s", this.source); return spec; } @@ -89,17 +86,13 @@ private void gatherElementSpec( } final boolean excluded = hasAnnotation(element, Exclude.class); - if (element.isDefault()) { - if (excluded) { - // no need to handle them - continue; + if (excluded) { + if (!element.isDefault()) { + this.processor.error( + "Cannot make config due to method %s, which is an excluded method that has no implementation!", + element + ); } - this.processor.info("Overriding implementation for %s as it's not excluded", element); - } else if (excluded) { - this.processor.error( - "Cannot make config due to method %s, which is a method excluded method that has no implementation!", - element - ); continue; } diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java index 5fd5eb8e3..5313c5309 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java @@ -137,10 +137,6 @@ Properties generatedClasses() { return this.mappings; } - void info(final String message, final Object... arguments) { - this.messager.printMessage(Kind.NOTE, String.format(Locale.ROOT, message, arguments)); - } - void error(final String message, final Object... arguments) { this.messager.printMessage(Kind.ERROR, String.format(Locale.ROOT, message, arguments)); } diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/DefaultValueTest.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/DefaultValueTest.java index e4ab29759..1f5dcfeaf 100644 --- a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/DefaultValueTest.java +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/DefaultValueTest.java @@ -8,12 +8,12 @@ public class DefaultValueTest { @Test void testCorrectDefaults() { - testCompilation("test/defaults/CorrectDefaults"); + testCompilation("defaults/CorrectDefaults"); } @Test void testMultipleDefaults() { - testCompilation("test/defaults/MultipleDefaults"); + testCompilation("defaults/MultipleDefaults"); } } diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java index c88962209..9f3f080b7 100644 --- a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java @@ -70,7 +70,8 @@ private static List readOrGenerateMappings(final String sourceResourceNa final URL localMappings = Resources.getResource(sourceResourceName + ".properties"); return Resources.asCharSource(localMappings, StandardCharsets.UTF_8).readLines(); } catch (final IllegalArgumentException ignored) { - System.out.println("Could not find resource " + sourceResourceName + ".properties, generating one"); + // we only support generating simple (not nested) configs, + // for complexer configs we need a mappings file return Collections.singletonList(String.format( "%s=%s", sourceResourceName.replace('/', '.'), diff --git a/extra/interface/ap/src/test/resources/test/defaults/CorrectDefaults.java b/extra/interface/ap/src/test/resources/defaults/CorrectDefaults.java similarity index 98% rename from extra/interface/ap/src/test/resources/test/defaults/CorrectDefaults.java rename to extra/interface/ap/src/test/resources/defaults/CorrectDefaults.java index 3f78049cd..ec063ef84 100644 --- a/extra/interface/ap/src/test/resources/test/defaults/CorrectDefaults.java +++ b/extra/interface/ap/src/test/resources/defaults/CorrectDefaults.java @@ -1,4 +1,4 @@ -package test.defaults; +package defaults; import org.spongepowered.configurate.interfaces.meta.defaults.DefaultBoolean; import org.spongepowered.configurate.interfaces.meta.defaults.DefaultDecimal; diff --git a/extra/interface/ap/src/test/resources/test/defaults/CorrectDefaultsImpl.java b/extra/interface/ap/src/test/resources/defaults/CorrectDefaultsImpl.java similarity index 99% rename from extra/interface/ap/src/test/resources/test/defaults/CorrectDefaultsImpl.java rename to extra/interface/ap/src/test/resources/defaults/CorrectDefaultsImpl.java index 012cdaf35..5b3bf8e62 100644 --- a/extra/interface/ap/src/test/resources/test/defaults/CorrectDefaultsImpl.java +++ b/extra/interface/ap/src/test/resources/defaults/CorrectDefaultsImpl.java @@ -1,4 +1,4 @@ -package test.defaults; +package defaults; import java.lang.Override; import java.lang.String; diff --git a/extra/interface/ap/src/test/resources/test/defaults/MultipleDefaults.java b/extra/interface/ap/src/test/resources/defaults/MultipleDefaults.java similarity index 96% rename from extra/interface/ap/src/test/resources/test/defaults/MultipleDefaults.java rename to extra/interface/ap/src/test/resources/defaults/MultipleDefaults.java index 64c8e23bd..a6d7948e3 100644 --- a/extra/interface/ap/src/test/resources/test/defaults/MultipleDefaults.java +++ b/extra/interface/ap/src/test/resources/defaults/MultipleDefaults.java @@ -1,4 +1,4 @@ -package test.defaults; +package defaults; import org.spongepowered.configurate.interfaces.meta.defaults.DefaultBoolean; import org.spongepowered.configurate.interfaces.meta.defaults.DefaultDecimal; diff --git a/extra/interface/ap/src/test/resources/test/defaults/MultipleDefaultsImpl.java b/extra/interface/ap/src/test/resources/defaults/MultipleDefaultsImpl.java similarity index 97% rename from extra/interface/ap/src/test/resources/test/defaults/MultipleDefaultsImpl.java rename to extra/interface/ap/src/test/resources/defaults/MultipleDefaultsImpl.java index f054c36cd..384241bdc 100644 --- a/extra/interface/ap/src/test/resources/test/defaults/MultipleDefaultsImpl.java +++ b/extra/interface/ap/src/test/resources/defaults/MultipleDefaultsImpl.java @@ -1,4 +1,4 @@ -package test.defaults; +package defaults; import java.lang.Override; import java.lang.String; From a9c0e2fb2daf31839200111e2dfe68aea5a2d1c6 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 14 Jan 2024 02:20:42 +0100 Subject: [PATCH 14/35] Added support for default getters and default setters --- .../processor/AnnotationDefaults.java | 19 ++++ .../ConfigImplementationGenerator.java | 100 +++++++++++++----- .../resources/defaults/CorrectDefaults.java | 11 ++ .../defaults/CorrectDefaultsImpl.java | 23 +++- .../test/resources/structure/BasicConfig.java | 4 + .../resources/structure/BasicConfigImpl.java | 8 ++ 6 files changed, 135 insertions(+), 30 deletions(-) diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationDefaults.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationDefaults.java index 6e1b72c56..33128f7f2 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationDefaults.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationDefaults.java @@ -4,6 +4,7 @@ import com.google.auto.common.MoreTypes; import com.squareup.javapoet.AnnotationSpec; +import javax.lang.model.AnnotatedConstruct; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; @@ -34,6 +35,15 @@ public void process( final TypeMirror nodeType, final FieldSpecBuilderTracker fieldSpec ) throws IllegalStateException { + // there are two types of default values, one using annotations and one using the default value of a default method + + // first, handle default value of a default method getter + if (element.isDefault() && element.getParameters().isEmpty() && hasNoAnnotationDefaults(element)) { + fieldSpec.initializer("$T.super.$L()", element.getEnclosingElement(), element.getSimpleName()); + return; + } + + // if it's not using the default value of a default method, use the annotations final @Nullable DefaultBoolean defaultBoolean = annotation(element, DefaultBoolean.class); final @Nullable DefaultDecimal defaultDecimal = annotation(element, DefaultDecimal.class); final @Nullable DefaultNumeric defaultNumeric = annotation(element, DefaultNumeric.class); @@ -94,4 +104,13 @@ public void process( fieldSpec.initializer(isString ? "$S" : "$L", value); } + static boolean hasNoAnnotationDefaults(final AnnotatedConstruct construct) { + for (Class defaultAnnotation : INSTANCE.processes()) { + if (annotation(construct, defaultAnnotation) != null) { + return false; + } + } + return true; + } + } diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java index 29fb29c9b..420acdd80 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java @@ -110,38 +110,13 @@ private void gatherElementSpec( if (parameters.size() == 1) { // setter final VariableElement parameter = parameters.get(0); - - final MethodSpec.Builder method = MethodSpec.overriding(element) - .addStatement( - "this.$N = $N", - element.getSimpleName(), - parameter.getSimpleName() - ); - - // if it's not void - if (!MoreTypes.isTypeOf(Void.TYPE, nodeType)) { - // the return type can be a parent type of parameter, but it has to be assignable - if (!this.processor.typeUtils.isAssignable(parameter.asType(), nodeType)) { - this.processor.error( - "Cannot create a setter with return type %s for argument type %s. Method: %s", - nodeType, - parameter.asType(), - element - ); - continue; - } - method.addStatement("return this.$N", element.getSimpleName()); + final boolean success = handleSetter(element, simpleName, parameter, nodeType, spec); + if (!success) { + continue; } - - spec.add(simpleName + "#" + parameter.getSimpleName().toString(), method); nodeType = parameter.asType(); } else { - // getter - spec.add( - simpleName, - MethodSpec.overriding(element) - .addStatement("return $N", element.getSimpleName()) - ); + handleGetter(element, simpleName, nodeType, spec); } final FieldSpec.Builder fieldSpec = FieldSpec.builder(TypeName.get(nodeType), simpleName, Modifier.PRIVATE); @@ -158,4 +133,71 @@ private void gatherElementSpec( } } + private boolean handleSetter( + final ExecutableElement element, + final String simpleName, + final VariableElement parameter, + final TypeMirror returnType, + final TypeSpecBuilderTracker spec + ) { + final MethodSpec.Builder method = MethodSpec.overriding(element); + + // we have two main branches of setters, default non-void setters and non-default any setters + if (element.isDefault()) { + if (MoreTypes.isTypeOf(Void.TYPE, returnType)) { + this.processor.error("A default setter cannot have void as return type. Method: " + element); + return false; + } + + method.addStatement( + "this.$N = $T.super.$L($N)", + simpleName, + element.getEnclosingElement(), + simpleName, + parameter.getSimpleName() + ); + } else { + method.addStatement("this.$N = $N", simpleName, parameter.getSimpleName()); + } + + // if it's not void + if (!MoreTypes.isTypeOf(Void.TYPE, returnType)) { + // the return type can be a parent type of parameter, but it has to be assignable + if (!this.processor.typeUtils.isAssignable(parameter.asType(), returnType)) { + this.processor.error( + "Cannot create a setter with return type %s for argument type %s. Method: %s", + returnType, + parameter.asType(), + element + ); + return false; + } + method.addStatement("return this.$N", simpleName); + } + + spec.add(simpleName + "#" + parameter.getSimpleName(), method); + return true; + } + + private void handleGetter( + final ExecutableElement element, + final String simpleName, + final TypeMirror nodeType, + final TypeSpecBuilderTracker spec + ) { + // voids aren't valid + if (MoreTypes.isTypeOf(Void.TYPE, nodeType)) { + this.processor.error( + "Cannot create a getter with return type void for method %s, did you forget to @Exclude this method?", + element + ); + } + + spec.add( + simpleName, + MethodSpec.overriding(element) + .addStatement("return $N", element.getSimpleName()) + ); + } + } diff --git a/extra/interface/ap/src/test/resources/defaults/CorrectDefaults.java b/extra/interface/ap/src/test/resources/defaults/CorrectDefaults.java index ec063ef84..e52b15958 100644 --- a/extra/interface/ap/src/test/resources/defaults/CorrectDefaults.java +++ b/extra/interface/ap/src/test/resources/defaults/CorrectDefaults.java @@ -64,4 +64,15 @@ public interface CorrectDefaults { @DefaultString("Hello world!") String strawberry(); + + @DefaultString("Hi") + void tamarillo(String value); + + default String ugli() { + return "A fruit"; + } + + default int velvetApple() { + return 500; + } } diff --git a/extra/interface/ap/src/test/resources/defaults/CorrectDefaultsImpl.java b/extra/interface/ap/src/test/resources/defaults/CorrectDefaultsImpl.java index 5b3bf8e62..3e6b9486b 100644 --- a/extra/interface/ap/src/test/resources/defaults/CorrectDefaultsImpl.java +++ b/extra/interface/ap/src/test/resources/defaults/CorrectDefaultsImpl.java @@ -12,7 +12,6 @@ * Automatically generated implementation of the config */ @ConfigSerializable final class CorrectDefaultsImpl implements CorrectDefaults { - @DefaultBoolean(false) private boolean apple = false; @@ -70,6 +69,13 @@ final class CorrectDefaultsImpl implements CorrectDefaults { @DefaultString("Hello world!") private String strawberry = "Hello world!"; + @DefaultString("Hi") + private String tamarillo = "Hi"; + + private String ugli = CorrectDefaults.super.ugli(); + + private int velvetApple = CorrectDefaults.super.velvetApple(); + @Override public boolean apple() { return apple; @@ -164,4 +170,19 @@ public String raspberry() { public String strawberry() { return strawberry; } + + @Override + public void tamarillo(String value) { + this.tamarillo = value; + } + + @Override + public String ugli() { + return ugli; + } + + @Override + public int velvetApple() { + return velvetApple; + } } diff --git a/extra/interface/ap/src/test/resources/structure/BasicConfig.java b/extra/interface/ap/src/test/resources/structure/BasicConfig.java index db9dc03f2..483ba767c 100644 --- a/extra/interface/ap/src/test/resources/structure/BasicConfig.java +++ b/extra/interface/ap/src/test/resources/structure/BasicConfig.java @@ -9,4 +9,8 @@ public interface BasicConfig { void hi(String value); String hello(String value); + + default String hey(String value) { + return "Hello"; + } } diff --git a/extra/interface/ap/src/test/resources/structure/BasicConfigImpl.java b/extra/interface/ap/src/test/resources/structure/BasicConfigImpl.java index 68952ac9f..9f850914e 100644 --- a/extra/interface/ap/src/test/resources/structure/BasicConfigImpl.java +++ b/extra/interface/ap/src/test/resources/structure/BasicConfigImpl.java @@ -12,6 +12,8 @@ final class BasicConfigImpl implements BasicConfig { private String hi; + private String hey; + @Override public String hello() { return hello; @@ -27,4 +29,10 @@ public String hello(String value) { this.hello = value; return this.hello; } + + @Override + public String hey(String value) { + this.hey = BasicConfig.super.hey(value); + return this.hey; + } } From e0d9d422419104137e96030fce2b8760e6152a6f Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 14 Jan 2024 02:29:15 +0100 Subject: [PATCH 15/35] Notify users about Hidden limitation. Optimized Hidden constraint --- .../processor/AnnotationHidden.java | 44 ++++++++++++++ .../processor/AnnotationProcessorHandler.java | 1 + .../interfaces/InterfaceConstraints.java | 57 +++++++++++++------ .../configurate/interfaces/meta/Hidden.java | 4 ++ 4 files changed, 88 insertions(+), 18 deletions(-) create mode 100644 extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationHidden.java diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationHidden.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationHidden.java new file mode 100644 index 000000000..a11843c2a --- /dev/null +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationHidden.java @@ -0,0 +1,44 @@ +package org.spongepowered.configurate.interfaces.processor; + +import static org.spongepowered.configurate.interfaces.processor.Utils.hasAnnotation; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; +import org.spongepowered.configurate.interfaces.meta.Hidden; + +import java.lang.annotation.Annotation; +import java.util.Collections; +import java.util.Set; + +final class AnnotationHidden implements AnnotationProcessor { + + static final AnnotationHidden INSTANCE = new AnnotationHidden(); + + private AnnotationHidden() {} + + @Override + public Set> processes() { + // the purpose of this class is to warn people, not to add the annotation. + // AnnotationOthers can do that just fine + return Collections.emptySet(); + } + + @Override + public void process( + final ExecutableElement element, + final TypeMirror nodeType, + final FieldSpecBuilderTracker fieldSpec + ) throws IllegalStateException { + if (!element.isDefault()) { + return; + } + + // throw exception to prevent unexpected behaviour during runtime + if (hasAnnotation(element, Hidden.class) && AnnotationDefaults.hasNoAnnotationDefaults(element)) { + throw new IllegalStateException( + "Due to limitations there is no support for methods that use the default value and Hidden. Method: " + element + ); + } + } + +} diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessorHandler.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessorHandler.java index 13cbe1b56..c1e6911ca 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessorHandler.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessorHandler.java @@ -13,6 +13,7 @@ final class AnnotationProcessorHandler { static { HANDLERS.add(AnnotationDefaults.INSTANCE); + HANDLERS.add(AnnotationHidden.INSTANCE); // always add others as last because it adds all annotations that have not been processed HANDLERS.add(AnnotationOthers.INSTANCE); } diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceConstraints.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceConstraints.java index bf4e69aa2..6d53a625c 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceConstraints.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceConstraints.java @@ -1,9 +1,11 @@ package org.spongepowered.configurate.interfaces; +import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.configurate.interfaces.meta.Hidden; import org.spongepowered.configurate.interfaces.meta.defaults.DefaultBoolean; import org.spongepowered.configurate.interfaces.meta.defaults.DefaultDecimal; import org.spongepowered.configurate.interfaces.meta.defaults.DefaultNumeric; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultString; import org.spongepowered.configurate.interfaces.meta.range.DecimalRange; import org.spongepowered.configurate.interfaces.meta.range.NumericRange; import org.spongepowered.configurate.interfaces.meta.range.StringRange; @@ -77,27 +79,46 @@ private static Constraint.Factory stringRange() { }; } + @SuppressWarnings("ConstantValue") private static Processor.AdvancedFactory hiddenProcessor() { - return (ignored, fieldType, element) -> (value, destination) -> { - // hidden fields are only not part of the config if the value is the default value, so we check that below - if (TypeUtils.isBoolean(fieldType) && element.isAnnotationPresent(DefaultBoolean.class)) { - if (!value.equals(element.getAnnotation(DefaultBoolean.class).value())) { - return; - } - } else if (TypeUtils.isDecimal(fieldType) && element.isAnnotationPresent(DefaultDecimal.class)) { - if (((Number) value).doubleValue() != element.getAnnotation(DefaultDecimal.class).value()) { - return; - } - } else if (TypeUtils.isNumeric(fieldType) && element.isAnnotationPresent(DefaultNumeric.class)) { - if (((Number) value).longValue() != element.getAnnotation(DefaultNumeric.class).value()) { - return; + return (ignored, fieldType, element) -> { + // prefetch everything we can + final @Nullable DefaultBoolean defaultBoolean = element.getAnnotation(DefaultBoolean.class); + final @Nullable DefaultDecimal defaultDecimal = element.getAnnotation(DefaultDecimal.class); + final @Nullable DefaultNumeric defaultNumeric = element.getAnnotation(DefaultNumeric.class); + final @Nullable DefaultString defaultString = element.getAnnotation(DefaultString.class); + final boolean isBoolean = TypeUtils.isBoolean(fieldType); + final boolean isDecimal = TypeUtils.isDecimal(fieldType); + final boolean isNumeric = TypeUtils.isNumeric(fieldType); + final boolean isString = String.class == fieldType; + + // unfortunately default methods cannot be supported in combination with Hidden in Configurate + + return (value, destination) -> { + // hidden fields are only absent from the config if the value is the default value, so we check that below + if (isBoolean && defaultBoolean != null) { + if (!value.equals(defaultBoolean.value())) { + return; + } + } else if (isDecimal && defaultDecimal != null) { + if (((Number) value).doubleValue() != defaultDecimal.value()) { + return; + } + } else if (isNumeric && defaultNumeric != null) { + if (((Number) value).longValue() != defaultNumeric.value()) { + return; + } + } else if (isString && defaultString != null) { + if (!defaultString.value().equals(value)) { + return; + } } - } - // as long as it uses the naming scheme-based resolver parent should be the object holding the field and - // key the field type - //noinspection DataFlowIssue - destination.parent().removeChild(destination.key()); + // as long as it uses the naming scheme-based resolver parent should be the object holding the field and + // key the field type + //noinspection DataFlowIssue + destination.parent().removeChild(destination.key()); + }; }; } diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Hidden.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Hidden.java index dded58966..98ba1a0ce 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Hidden.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Hidden.java @@ -14,6 +14,10 @@ *

Without a default value the annotated node will be read, but will never * be written even if the user explicitly added it to their config.

* + * Note that Hidden doesn't work with default method getters due to a + * limitation, and Hidden will function like it doesn't have a default + * value. + * * @since 4.2.0 */ @Retention(RetentionPolicy.RUNTIME) From 224e87cfdc5eb1c69acd4efa259e95f3730abc2f Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 14 Jan 2024 02:46:19 +0100 Subject: [PATCH 16/35] Exit gracefully on failure --- .../ConfigImplementationGenerator.java | 50 ++++++++++++------- ...onfigImplementationGeneratorProcessor.java | 12 +++-- 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java index 420acdd80..106769b01 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java @@ -15,6 +15,7 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.configurate.interfaces.meta.Exclude; import org.spongepowered.configurate.objectmapping.ConfigSerializable; import org.spongepowered.configurate.objectmapping.meta.PostProcess; @@ -34,7 +35,11 @@ class ConfigImplementationGenerator { this.source = configInterfaceType; } - public TypeSpec.Builder generate() { + /** + * Returns the generated class or null if something went wrong during + * generation. + */ + public TypeSpec.@Nullable Builder generate() { final ClassName className = ClassName.get(this.source); final TypeSpec.Builder spec = TypeSpec @@ -45,7 +50,9 @@ public TypeSpec.Builder generate() { .addJavadoc("Automatically generated implementation of the config"); final TypeSpecBuilderTracker tracker = new TypeSpecBuilderTracker(); - gatherElementSpec(tracker, this.source); + if (!gatherElementSpec(tracker, this.source)) { + return null; + } tracker.writeTo(spec); final String qualifiedName = className.reflectionName(); @@ -55,7 +62,10 @@ public TypeSpec.Builder generate() { return spec; } - private void gatherElementSpec( + /** + * Returns true if successful, otherwise false. + */ + private boolean gatherElementSpec( final TypeSpecBuilderTracker spec, final TypeElement type ) { @@ -65,12 +75,16 @@ private void gatherElementSpec( final ElementKind kind = enclosedElement.getKind(); if (kind == ElementKind.INTERFACE && hasAnnotation(enclosedElement, ConfigSerializable.class)) { - spec.add( - enclosedElement.getSimpleName().toString(), - new ConfigImplementationGenerator(this.processor, (TypeElement) enclosedElement) - .generate() - .addModifiers(Modifier.STATIC) - ); + final TypeSpec.@Nullable Builder generated = + new ConfigImplementationGenerator(this.processor, (TypeElement) enclosedElement) + .generate(); + + // if something went wrong in the child class, the parent can't complete normally either + if (generated == null) { + return false; + } + + spec.add(enclosedElement.getSimpleName().toString(), generated.addModifiers(Modifier.STATIC)); continue; } @@ -87,13 +101,14 @@ private void gatherElementSpec( final boolean excluded = hasAnnotation(element, Exclude.class); if (excluded) { - if (!element.isDefault()) { - this.processor.error( - "Cannot make config due to method %s, which is an excluded method that has no implementation!", - element - ); + if (element.isDefault()) { + continue; } - continue; + this.processor.error( + "Cannot make config due to method %s, which is an excluded method that has no implementation!", + element + ); + return false; } // all methods are either setters or getters past this point @@ -101,7 +116,7 @@ private void gatherElementSpec( final List parameters = element.getParameters(); if (parameters.size() > 1) { this.processor.error("Setters cannot have more than one parameter! Method: " + element); - continue; + return false; } final String simpleName = element.getSimpleName().toString(); @@ -112,7 +127,7 @@ private void gatherElementSpec( final VariableElement parameter = parameters.get(0); final boolean success = handleSetter(element, simpleName, parameter, nodeType, spec); if (!success) { - continue; + return false; } nodeType = parameter.asType(); } else { @@ -131,6 +146,7 @@ private void gatherElementSpec( for (final TypeMirror parent : type.getInterfaces()) { gatherElementSpec(spec, (TypeElement) this.processor.typeUtils.asElement(parent)); } + return true; } private boolean handleSetter( diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java index 5313c5309..4b0cc64b8 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java @@ -42,6 +42,7 @@ import javax.tools.Diagnostic.Kind; import javax.tools.FileObject; import javax.tools.StandardLocation; +import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.configurate.interfaces.Constants; import org.spongepowered.configurate.objectmapping.ConfigSerializable; @@ -104,14 +105,17 @@ public boolean process(final Set ignored, final RoundEnvi } /** - * Generate a class for the given interface and - * returns the name of the generated class. + * Generate a class for the given interface. */ private void processInterface(final TypeElement type) throws IOException { final ClassName className = ClassName.get(type); - final TypeSpec spec = new ConfigImplementationGenerator(this, type).generate().build(); - JavaFile.builder(className.packageName(), spec) + final TypeSpec.@Nullable Builder generated = new ConfigImplementationGenerator(this, type).generate(); + if (generated == null) { + return; + } + + JavaFile.builder(className.packageName(), generated.build()) .build() .writeTo(this.filer); } From b62cab5900f9e315f93da85c8a5d798f3dacd274 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 1 Feb 2024 21:30:32 +0100 Subject: [PATCH 17/35] Add support for Gradle incremental annotation processing --- .../resources/META-INF/gradle/incremental.annotation.processors | 1 + 1 file changed, 1 insertion(+) create mode 100644 extra/interface/ap/src/main/resources/META-INF/gradle/incremental.annotation.processors diff --git a/extra/interface/ap/src/main/resources/META-INF/gradle/incremental.annotation.processors b/extra/interface/ap/src/main/resources/META-INF/gradle/incremental.annotation.processors new file mode 100644 index 000000000..287495b9a --- /dev/null +++ b/extra/interface/ap/src/main/resources/META-INF/gradle/incremental.annotation.processors @@ -0,0 +1 @@ +org.spongepowered.configurate.interfaces.processor.ConfigImplementationGeneratorProcessor,aggregating From 2d89b9d287bf252c815546cb17976d5ee0243c63 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 11 Feb 2024 20:50:52 +0100 Subject: [PATCH 18/35] Added Field annotation --- .../ConfigImplementationGenerator.java | 5 +++++ .../util/AnnotationOthersAnnotations.java | 2 ++ .../interfaces/InterfaceDefaultOptions.java | 2 +- ...onstraints.java => InterfaceMiddleware.java} | 6 +++--- .../configurate/interfaces/meta/Exclude.java | 6 +++--- .../configurate/interfaces/meta/Field.java | 17 +++++++++++++++++ 6 files changed, 31 insertions(+), 7 deletions(-) rename extra/interface/src/main/java/org/spongepowered/configurate/interfaces/{InterfaceConstraints.java => InterfaceMiddleware.java} (97%) create mode 100644 extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Field.java diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java index 106769b01..45cf7148e 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java @@ -17,6 +17,7 @@ import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.configurate.interfaces.meta.Exclude; +import org.spongepowered.configurate.interfaces.meta.Field; import org.spongepowered.configurate.objectmapping.ConfigSerializable; import org.spongepowered.configurate.objectmapping.meta.PostProcess; @@ -136,6 +137,10 @@ private boolean gatherElementSpec( final FieldSpec.Builder fieldSpec = FieldSpec.builder(TypeName.get(nodeType), simpleName, Modifier.PRIVATE); + if (hasAnnotation(element, Field.class)) { + fieldSpec.addModifiers(Modifier.TRANSIENT); + } + //todo add tests for hidden in both ap and interfaces and defaults in interfaces AnnotationProcessorHandler.handle(element, nodeType, fieldSpec); diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/util/AnnotationOthersAnnotations.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/util/AnnotationOthersAnnotations.java index b6523675b..de2c4587b 100644 --- a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/util/AnnotationOthersAnnotations.java +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/util/AnnotationOthersAnnotations.java @@ -6,6 +6,7 @@ import java.lang.annotation.Target; public final class AnnotationOthersAnnotations { + @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AnnotationNoField {} @@ -19,4 +20,5 @@ public final class AnnotationOthersAnnotations { @Target({ElementType.METHOD, ElementType.FIELD}) public @interface AnnotationNoRetention {} + } diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java index 44f61297b..6d0ee3df9 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java @@ -33,7 +33,7 @@ public final class InterfaceDefaultOptions { .serializers( TypeSerializerCollection.builder() .registerAnnotated(InterfaceTypeSerializer::applicable, InterfaceTypeSerializer.INSTANCE) - .registerAnnotatedObjects(InterfaceConstraints.buildObjectMapperWithConstraints()) + .registerAnnotatedObjects(InterfaceMiddleware.buildObjectMapperWithMiddleware()) .registerAll(TypeSerializerCollection.defaults()) .build() ); diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceConstraints.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceMiddleware.java similarity index 97% rename from extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceConstraints.java rename to extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceMiddleware.java index 6d53a625c..3a9c50a7f 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceConstraints.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceMiddleware.java @@ -14,12 +14,12 @@ import org.spongepowered.configurate.objectmapping.meta.Processor; import org.spongepowered.configurate.serialize.SerializationException; -final class InterfaceConstraints { +final class InterfaceMiddleware { - private InterfaceConstraints() { + private InterfaceMiddleware() { } - static ObjectMapper.Factory buildObjectMapperWithConstraints() { + static ObjectMapper.Factory buildObjectMapperWithMiddleware() { return ObjectMapper.factoryBuilder() .addConstraint(DecimalRange.class, Number.class, decimalRange()) .addConstraint(NumericRange.class, Number.class, numericRange()) diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Exclude.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Exclude.java index ac8ae669f..5dc4c871d 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Exclude.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Exclude.java @@ -22,9 +22,9 @@ import java.lang.annotation.Target; /** - * Prevents the annotated method from being handled as a config node. - * This is practically only used for default methods, as normal interface - * methods need to have an implementation. + * Adding this will cause the implementation generator to completely ignore + * this method. This is practically only used for default methods, as normal + * interface methods need to have an implementation. * * @since 4.2.0 */ diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Field.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Field.java new file mode 100644 index 000000000..84b6b58ce --- /dev/null +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Field.java @@ -0,0 +1,17 @@ +package org.spongepowered.configurate.interfaces.meta; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Creates a field which makes the annotated method act as a simple getter / + * setter without being handled as a config node. + * + * @since 4.2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface Field { +} From 40d1c07f12fdb56238b9f6f5bfa9e1c1a4d3dad8 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 11 Feb 2024 20:57:24 +0100 Subject: [PATCH 19/35] Apply spotless --- .../ObjectMapperFactoryImpl.java | 23 +++++++------- .../objectmapping/meta/Processor.java | 2 +- .../processor/AnnotationDefaults.java | 23 ++++++++++++-- .../processor/AnnotationHidden.java | 21 +++++++++++-- .../processor/AnnotationOthers.java | 28 ++++++++++++----- .../processor/AnnotationProcessor.java | 22 +++++++++++-- .../processor/AnnotationProcessorHandler.java | 21 +++++++++++-- .../ConfigImplementationGenerator.java | 31 ++++++++++++++----- ...onfigImplementationGeneratorProcessor.java | 8 +++-- .../processor/FieldSpecBuilderTracker.java | 19 +++++++++++- .../processor/TypeSpecBuilderTracker.java | 1 + .../interfaces/processor/Utils.java | 22 +++++++++++-- .../processor/DefaultValueTest.java | 16 ++++++++++ .../interfaces/processor/TestUtils.java | 19 +++++++++++- .../util/AnnotationOthersAnnotations.java | 16 ++++++++++ .../interfaces/InterfaceDefaultOptions.java | 3 +- .../interfaces/InterfaceMiddleware.java | 16 ++++++++++ .../configurate/interfaces/TypeUtils.java | 16 ++++++++++ .../configurate/interfaces/meta/Field.java | 16 ++++++++++ .../configurate/interfaces/meta/Hidden.java | 16 ++++++++++ .../meta/defaults/DefaultBoolean.java | 16 ++++++++++ .../meta/defaults/DefaultDecimal.java | 16 ++++++++++ .../meta/defaults/DefaultNumeric.java | 16 ++++++++++ .../meta/defaults/DefaultString.java | 16 ++++++++++ .../interfaces/meta/range/DecimalRange.java | 16 ++++++++++ .../interfaces/meta/range/NumericRange.java | 16 ++++++++++ .../interfaces/meta/range/StringRange.java | 16 ++++++++++ .../configurate/interfaces/TypeUtils.java | 16 ++++++++++ 28 files changed, 422 insertions(+), 45 deletions(-) diff --git a/core/src/main/java/org/spongepowered/configurate/objectmapping/ObjectMapperFactoryImpl.java b/core/src/main/java/org/spongepowered/configurate/objectmapping/ObjectMapperFactoryImpl.java index 31e2ad5bc..f95849916 100644 --- a/core/src/main/java/org/spongepowered/configurate/objectmapping/ObjectMapperFactoryImpl.java +++ b/core/src/main/java/org/spongepowered/configurate/objectmapping/ObjectMapperFactoryImpl.java @@ -25,17 +25,6 @@ import static java.util.Objects.requireNonNull; import io.leangen.geantyref.GenericTypeReflector; -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.AnnotatedType; -import java.lang.reflect.Modifier; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.configurate.BasicConfigurationNode; import org.spongepowered.configurate.ConfigurationNode; @@ -53,6 +42,18 @@ import org.spongepowered.configurate.util.NamingScheme; import org.spongepowered.configurate.util.NamingSchemes; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + /** * Factory for a basic {@link ObjectMapper}. */ diff --git a/core/src/main/java/org/spongepowered/configurate/objectmapping/meta/Processor.java b/core/src/main/java/org/spongepowered/configurate/objectmapping/meta/Processor.java index 6f8d33cb9..4ca2f6cf2 100644 --- a/core/src/main/java/org/spongepowered/configurate/objectmapping/meta/Processor.java +++ b/core/src/main/java/org/spongepowered/configurate/objectmapping/meta/Processor.java @@ -16,11 +16,11 @@ */ package org.spongepowered.configurate.objectmapping.meta; -import java.lang.reflect.AnnotatedElement; import org.spongepowered.configurate.CommentedConfigurationNodeIntermediary; import org.spongepowered.configurate.ConfigurationNode; import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Type; import java.util.ResourceBundle; diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationDefaults.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationDefaults.java index 33128f7f2..520b90a21 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationDefaults.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationDefaults.java @@ -1,12 +1,25 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.spongepowered.configurate.interfaces.processor; import static org.spongepowered.configurate.interfaces.processor.Utils.annotation; import com.google.auto.common.MoreTypes; import com.squareup.javapoet.AnnotationSpec; -import javax.lang.model.AnnotatedConstruct; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.configurate.interfaces.meta.defaults.DefaultBoolean; import org.spongepowered.configurate.interfaces.meta.defaults.DefaultDecimal; @@ -18,6 +31,10 @@ import java.util.HashSet; import java.util.Set; +import javax.lang.model.AnnotatedConstruct; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; + final class AnnotationDefaults implements AnnotationProcessor { static final AnnotationDefaults INSTANCE = new AnnotationDefaults(); diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationHidden.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationHidden.java index a11843c2a..9ea1c8b69 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationHidden.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationHidden.java @@ -1,15 +1,32 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.spongepowered.configurate.interfaces.processor; import static org.spongepowered.configurate.interfaces.processor.Utils.hasAnnotation; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeMirror; import org.spongepowered.configurate.interfaces.meta.Hidden; import java.lang.annotation.Annotation; import java.util.Collections; import java.util.Set; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; + final class AnnotationHidden implements AnnotationProcessor { static final AnnotationHidden INSTANCE = new AnnotationHidden(); diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationOthers.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationOthers.java index fa60ad119..f26a71c73 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationOthers.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationOthers.java @@ -1,29 +1,41 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.spongepowered.configurate.interfaces.processor; import static org.spongepowered.configurate.interfaces.processor.Utils.annotation; import com.google.auto.common.MoreElements; - import com.squareup.javapoet.AnnotationSpec; +import org.checkerframework.checker.nullness.qual.Nullable; +import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; - import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; -import java.lang.annotation.Annotation; -import java.util.HashSet; -import java.util.Set; - -import org.checkerframework.checker.nullness.qual.Nullable; - final class AnnotationOthers implements AnnotationProcessor { static final AnnotationOthers INSTANCE = new AnnotationOthers(); diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessor.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessor.java index e034acfb8..e34218a3a 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessor.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessor.java @@ -1,11 +1,27 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.spongepowered.configurate.interfaces.processor; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeMirror; - import java.lang.annotation.Annotation; import java.util.Set; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; + interface AnnotationProcessor { /** diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessorHandler.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessorHandler.java index c1e6911ca..a02ecf686 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessorHandler.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessorHandler.java @@ -1,12 +1,29 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.spongepowered.configurate.interfaces.processor; import com.squareup.javapoet.FieldSpec; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeMirror; import java.util.ArrayList; import java.util.List; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; + final class AnnotationProcessorHandler { private static final List HANDLERS = new ArrayList<>(); diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java index 45cf7148e..9f0fa1cc4 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java @@ -1,3 +1,19 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.spongepowered.configurate.interfaces.processor; import static org.spongepowered.configurate.interfaces.processor.Utils.hasAnnotation; @@ -8,13 +24,6 @@ import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.configurate.interfaces.meta.Exclude; import org.spongepowered.configurate.interfaces.meta.Field; @@ -23,6 +32,14 @@ import java.util.List; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + class ConfigImplementationGenerator { private final ConfigImplementationGeneratorProcessor processor; diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java index 4b0cc64b8..622aa8149 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java @@ -22,12 +22,17 @@ import com.squareup.javapoet.ClassName; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.TypeSpec; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.configurate.interfaces.Constants; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + import java.io.IOException; import java.io.Writer; import java.util.Collections; import java.util.Locale; import java.util.Properties; import java.util.Set; + import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Filer; import javax.annotation.processing.Messager; @@ -42,9 +47,6 @@ import javax.tools.Diagnostic.Kind; import javax.tools.FileObject; import javax.tools.StandardLocation; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.spongepowered.configurate.interfaces.Constants; -import org.spongepowered.configurate.objectmapping.ConfigSerializable; /** * Generates an implementation for a given interface based config, diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/FieldSpecBuilderTracker.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/FieldSpecBuilderTracker.java index bd163965f..22578ee33 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/FieldSpecBuilderTracker.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/FieldSpecBuilderTracker.java @@ -1,15 +1,32 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.spongepowered.configurate.interfaces.processor; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.FieldSpec; -import javax.lang.model.element.TypeElement; import java.lang.annotation.Annotation; import java.util.Collection; import java.util.HashSet; import java.util.Set; +import javax.lang.model.element.TypeElement; + final class FieldSpecBuilderTracker { private final Set processed = new HashSet<>(); diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java index 72794f1b5..2e6380853 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java @@ -20,6 +20,7 @@ import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; + import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/Utils.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/Utils.java index 494b8a02b..21fc5e047 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/Utils.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/Utils.java @@ -1,14 +1,32 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.spongepowered.configurate.interfaces.processor; import com.google.auto.common.MoreTypes; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + import java.lang.annotation.Annotation; + import javax.lang.model.AnnotatedConstruct; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.spongepowered.configurate.objectmapping.ConfigSerializable; final class Utils { diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/DefaultValueTest.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/DefaultValueTest.java index 1f5dcfeaf..7c0b90763 100644 --- a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/DefaultValueTest.java +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/DefaultValueTest.java @@ -1,3 +1,19 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.spongepowered.configurate.interfaces.processor; import static org.spongepowered.configurate.interfaces.processor.TestUtils.testCompilation; diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java index 9f3f080b7..2832dc0cd 100644 --- a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java @@ -1,3 +1,19 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.spongepowered.configurate.interfaces.processor; import static com.google.testing.compile.CompilationSubject.assertThat; @@ -7,7 +23,6 @@ import com.google.common.io.Resources; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; -import javax.tools.StandardLocation; import org.spongepowered.configurate.interfaces.Constants; import java.io.IOException; @@ -18,6 +33,8 @@ import java.util.List; import java.util.stream.Collectors; +import javax.tools.StandardLocation; + final class TestUtils { private TestUtils() { diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/util/AnnotationOthersAnnotations.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/util/AnnotationOthersAnnotations.java index de2c4587b..97c0fb763 100644 --- a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/util/AnnotationOthersAnnotations.java +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/util/AnnotationOthersAnnotations.java @@ -1,3 +1,19 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.spongepowered.configurate.interfaces.processor.util; import java.lang.annotation.ElementType; diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java index 6d0ee3df9..6c6c8cc4d 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java @@ -16,10 +16,11 @@ */ package org.spongepowered.configurate.interfaces; -import java.util.function.UnaryOperator; import org.spongepowered.configurate.ConfigurationOptions; import org.spongepowered.configurate.serialize.TypeSerializerCollection; +import java.util.function.UnaryOperator; + /** * This class has the default {@link ConfigurationOptions} * with {@link InterfaceTypeSerializer} added to the serializers. diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceMiddleware.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceMiddleware.java index 3a9c50a7f..013cb3b48 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceMiddleware.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceMiddleware.java @@ -1,3 +1,19 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.spongepowered.configurate.interfaces; import org.checkerframework.checker.nullness.qual.Nullable; diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/TypeUtils.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/TypeUtils.java index 4d7b55c10..b87b203d4 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/TypeUtils.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/TypeUtils.java @@ -1,3 +1,19 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.spongepowered.configurate.interfaces; import static io.leangen.geantyref.GenericTypeReflector.box; diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Field.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Field.java index 84b6b58ce..e474aaf04 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Field.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Field.java @@ -1,3 +1,19 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.spongepowered.configurate.interfaces.meta; import java.lang.annotation.ElementType; diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Hidden.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Hidden.java index 98ba1a0ce..4ac1c83bc 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Hidden.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/Hidden.java @@ -1,3 +1,19 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.spongepowered.configurate.interfaces.meta; import java.lang.annotation.ElementType; diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultBoolean.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultBoolean.java index 68939e34e..f49b0a333 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultBoolean.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultBoolean.java @@ -1,3 +1,19 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.spongepowered.configurate.interfaces.meta.defaults; import java.lang.annotation.ElementType; diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultDecimal.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultDecimal.java index 1a688ad1d..9efda5816 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultDecimal.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultDecimal.java @@ -1,3 +1,19 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.spongepowered.configurate.interfaces.meta.defaults; import java.lang.annotation.ElementType; diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultNumeric.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultNumeric.java index f9b91783d..c6f47f5cd 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultNumeric.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultNumeric.java @@ -1,3 +1,19 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.spongepowered.configurate.interfaces.meta.defaults; import java.lang.annotation.ElementType; diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultString.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultString.java index 305f2cf09..f99cec6f7 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultString.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/defaults/DefaultString.java @@ -1,3 +1,19 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.spongepowered.configurate.interfaces.meta.defaults; import java.lang.annotation.ElementType; diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/DecimalRange.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/DecimalRange.java index 1bc534853..fad986dc6 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/DecimalRange.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/DecimalRange.java @@ -1,3 +1,19 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.spongepowered.configurate.interfaces.meta.range; import java.lang.annotation.ElementType; diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/NumericRange.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/NumericRange.java index 8e8f2720c..7595680ca 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/NumericRange.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/NumericRange.java @@ -1,3 +1,19 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.spongepowered.configurate.interfaces.meta.range; import java.lang.annotation.ElementType; diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/StringRange.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/StringRange.java index 4a1e13bb8..48ed5a74a 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/StringRange.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/meta/range/StringRange.java @@ -1,3 +1,19 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.spongepowered.configurate.interfaces.meta.range; import java.lang.annotation.ElementType; diff --git a/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TypeUtils.java b/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TypeUtils.java index cd7640236..979c9ba5b 100644 --- a/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TypeUtils.java +++ b/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TypeUtils.java @@ -1,3 +1,19 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.spongepowered.configurate.interfaces; import java.util.Arrays; From ab13924e02bb59f23881db2a8dcbc917d66b2b2b Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 11 Feb 2024 23:07:30 +0100 Subject: [PATCH 20/35] Applied forbiddenApi fixes --- .../configurate/interfaces/processor/TestUtils.java | 4 ++++ .../configurate/interfaces/InterfaceMiddleware.java | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java index 2832dc0cd..c6dc44591 100644 --- a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java @@ -23,6 +23,9 @@ import com.google.common.io.Resources; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; + +import java.util.Locale; + import org.spongepowered.configurate.interfaces.Constants; import java.io.IOException; @@ -90,6 +93,7 @@ private static List readOrGenerateMappings(final String sourceResourceNa // we only support generating simple (not nested) configs, // for complexer configs we need a mappings file return Collections.singletonList(String.format( + Locale.ROOT, "%s=%s", sourceResourceName.replace('/', '.'), targetResourceName.replace('/', '.') diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceMiddleware.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceMiddleware.java index 013cb3b48..9b2381557 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceMiddleware.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceMiddleware.java @@ -30,6 +30,8 @@ import org.spongepowered.configurate.objectmapping.meta.Processor; import org.spongepowered.configurate.serialize.SerializationException; +import java.util.Locale; + final class InterfaceMiddleware { private InterfaceMiddleware() { @@ -54,6 +56,7 @@ private static Constraint.Factory decimalRange() { final double value = number.doubleValue(); if (!(data.from() >= value && data.to() <= value)) { throw new SerializationException(String.format( + Locale.ROOT, "'%s' is not in the allowed range of from: %s, to: %s!", value, data.from(), data.to() )); @@ -71,6 +74,7 @@ private static Constraint.Factory numericRange() { final long value = number.longValue(); if (!(data.from() >= value && data.to() <= value)) { throw new SerializationException(String.format( + Locale.ROOT, "'%s' is not in the allowed range of from: %s, to: %s!", value, data.from(), data.to() )); @@ -88,6 +92,7 @@ private static Constraint.Factory stringRange() { final int length = string.length(); if (!(data.from() >= length && data.to() <= length)) { throw new SerializationException(String.format( + Locale.ROOT, "'%s' is not in the allowed string length range of from: %s, to: %s!", length, data.from(), data.to() )); From c5533d5b278c0c466ba4377753fd874db06c2f3d Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 11 Feb 2024 23:23:21 +0100 Subject: [PATCH 21/35] Renamed error to printError to trick PMD --- .../processor/ConfigImplementationGenerator.java | 10 +++++----- .../ConfigImplementationGeneratorProcessor.java | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java index 9f0fa1cc4..19973aaf6 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java @@ -122,7 +122,7 @@ private boolean gatherElementSpec( if (element.isDefault()) { continue; } - this.processor.error( + this.processor.printError( "Cannot make config due to method %s, which is an excluded method that has no implementation!", element ); @@ -133,7 +133,7 @@ private boolean gatherElementSpec( final List parameters = element.getParameters(); if (parameters.size() > 1) { - this.processor.error("Setters cannot have more than one parameter! Method: " + element); + this.processor.printError("Setters cannot have more than one parameter! Method: " + element); return false; } @@ -183,7 +183,7 @@ private boolean handleSetter( // we have two main branches of setters, default non-void setters and non-default any setters if (element.isDefault()) { if (MoreTypes.isTypeOf(Void.TYPE, returnType)) { - this.processor.error("A default setter cannot have void as return type. Method: " + element); + this.processor.printError("A default setter cannot have void as return type. Method: " + element); return false; } @@ -202,7 +202,7 @@ private boolean handleSetter( if (!MoreTypes.isTypeOf(Void.TYPE, returnType)) { // the return type can be a parent type of parameter, but it has to be assignable if (!this.processor.typeUtils.isAssignable(parameter.asType(), returnType)) { - this.processor.error( + this.processor.printError( "Cannot create a setter with return type %s for argument type %s. Method: %s", returnType, parameter.asType(), @@ -225,7 +225,7 @@ private void handleGetter( ) { // voids aren't valid if (MoreTypes.isTypeOf(Void.TYPE, nodeType)) { - this.processor.error( + this.processor.printError( "Cannot create a getter with return type void for method %s, did you forget to @Exclude this method?", element ); diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java index 622aa8149..ba1dc9f8a 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGeneratorProcessor.java @@ -99,7 +99,7 @@ public boolean process(final Set ignored, final RoundEnvi try { processInterface(typeElement); } catch (final IOException exception) { - error(exception.getMessage()); + printError(exception.getMessage()); } } @@ -143,7 +143,7 @@ Properties generatedClasses() { return this.mappings; } - void error(final String message, final Object... arguments) { + void printError(final String message, final Object... arguments) { this.messager.printMessage(Kind.ERROR, String.format(Locale.ROOT, message, arguments)); } From e9c0dfce9a860d0e5898cfa0e617ac4beb7f6c08 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 11 Feb 2024 23:28:34 +0100 Subject: [PATCH 22/35] spotlessApply --- .../configurate/interfaces/processor/TestUtils.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java index c6dc44591..8d103c44a 100644 --- a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/TestUtils.java @@ -23,9 +23,6 @@ import com.google.common.io.Resources; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; - -import java.util.Locale; - import org.spongepowered.configurate.interfaces.Constants; import java.io.IOException; @@ -34,6 +31,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.stream.Collectors; import javax.tools.StandardLocation; From ebed0c573b7e81625bbd12d2d0d9498981014892 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 11 Feb 2024 23:32:19 +0100 Subject: [PATCH 23/35] Fix pmdTest --- .../configurate/interfaces/processor/DefaultValueTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/DefaultValueTest.java b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/DefaultValueTest.java index 7c0b90763..2e69455ed 100644 --- a/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/DefaultValueTest.java +++ b/extra/interface/ap/src/test/java/org/spongepowered/configurate/interfaces/processor/DefaultValueTest.java @@ -20,7 +20,7 @@ import org.junit.jupiter.api.Test; -public class DefaultValueTest { +class DefaultValueTest { @Test void testCorrectDefaults() { From 27786f0e6b3e7cc96b0087e6ef797d52444e7251 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Mon, 12 Feb 2024 19:34:10 +0100 Subject: [PATCH 24/35] Set core as api dependency --- extra/interface/build.gradle | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/extra/interface/build.gradle b/extra/interface/build.gradle index 865b6291a..5c6236119 100644 --- a/extra/interface/build.gradle +++ b/extra/interface/build.gradle @@ -5,8 +5,6 @@ plugins { description = "Utility classes for generated config interface implementations" dependencies { - compileOnly projects.core - + api projects.core testAnnotationProcessor projects.extra.extraInterface.extraInterfaceAp - testImplementation projects.core } From 2add4c9286bf3ed2548a1ed1a976b6e38250b13e Mon Sep 17 00:00:00 2001 From: Tim203 Date: Mon, 12 Feb 2024 22:30:35 +0100 Subject: [PATCH 25/35] Use superinterface instead of enclosed element Let's say interface Y extends interface X and method 'a' is defined in X. If you use enclosed element on 'a' you'll always get X (even for Y). superinterface is the interface where an impl is generated for. --- .../interfaces/processor/AnnotationDefaults.java | 4 +++- .../interfaces/processor/AnnotationHidden.java | 2 ++ .../interfaces/processor/AnnotationOthers.java | 6 +++++- .../interfaces/processor/AnnotationProcessor.java | 8 +++++++- .../interfaces/processor/AnnotationProcessorHandler.java | 9 +++++++-- .../processor/ConfigImplementationGenerator.java | 6 +++--- 6 files changed, 27 insertions(+), 8 deletions(-) diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationDefaults.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationDefaults.java index 520b90a21..0ae7e3e33 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationDefaults.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationDefaults.java @@ -33,6 +33,7 @@ import javax.lang.model.AnnotatedConstruct; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; final class AnnotationDefaults implements AnnotationProcessor { @@ -48,6 +49,7 @@ public Set> processes() { @Override public void process( + final TypeElement targetInterface, final ExecutableElement element, final TypeMirror nodeType, final FieldSpecBuilderTracker fieldSpec @@ -56,7 +58,7 @@ public void process( // first, handle default value of a default method getter if (element.isDefault() && element.getParameters().isEmpty() && hasNoAnnotationDefaults(element)) { - fieldSpec.initializer("$T.super.$L()", element.getEnclosingElement(), element.getSimpleName()); + fieldSpec.initializer("$T.super.$L()", targetInterface, element.getSimpleName()); return; } diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationHidden.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationHidden.java index 9ea1c8b69..b55cf42b4 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationHidden.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationHidden.java @@ -25,6 +25,7 @@ import java.util.Set; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; final class AnnotationHidden implements AnnotationProcessor { @@ -42,6 +43,7 @@ public Set> processes() { @Override public void process( + final TypeElement targetInterface, final ExecutableElement element, final TypeMirror nodeType, final FieldSpecBuilderTracker fieldSpec diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationOthers.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationOthers.java index f26a71c73..1452f96d6 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationOthers.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationOthers.java @@ -48,7 +48,11 @@ public Set> processes() { } @Override - public void process(final ExecutableElement element, final TypeMirror nodeType, final FieldSpecBuilderTracker fieldSpec) { + public void process( + final TypeElement targetInterface, + final ExecutableElement element, + final TypeMirror nodeType, + final FieldSpecBuilderTracker fieldSpec) { for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) { //noinspection UnstableApiUsage final TypeElement annotationType = MoreElements.asType(annotationMirror.getAnnotationType().asElement()); diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessor.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessor.java index e34218a3a..c4468c704 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessor.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessor.java @@ -20,6 +20,7 @@ import java.util.Set; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; interface AnnotationProcessor { @@ -40,6 +41,11 @@ interface AnnotationProcessor { * @param fieldSpec the builder of the field that is being generated * @throws IllegalStateException when something goes wrong */ - void process(ExecutableElement element, TypeMirror nodeType, FieldSpecBuilderTracker fieldSpec) throws IllegalStateException; + void process( + TypeElement targetInterface, + ExecutableElement element, + TypeMirror nodeType, + FieldSpecBuilderTracker fieldSpec + ) throws IllegalStateException; } diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessorHandler.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessorHandler.java index a02ecf686..b16cd6a30 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessorHandler.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/AnnotationProcessorHandler.java @@ -22,6 +22,7 @@ import java.util.List; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; final class AnnotationProcessorHandler { @@ -37,11 +38,15 @@ final class AnnotationProcessorHandler { private AnnotationProcessorHandler() {} - static void handle(final ExecutableElement element, final TypeMirror nodeType, final FieldSpec.Builder fieldSpec) { + static void handle( + final TypeElement targetInterface, + final ExecutableElement element, + final TypeMirror nodeType, + final FieldSpec.Builder fieldSpec) { final FieldSpecBuilderTracker fieldTracker = new FieldSpecBuilderTracker(fieldSpec); for (AnnotationProcessor handler : HANDLERS) { - handler.process(element, nodeType, fieldTracker); + handler.process(targetInterface, element, nodeType, fieldTracker); fieldTracker.processed(handler.processes()); } } diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java index 19973aaf6..6247e4afa 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java @@ -47,10 +47,10 @@ class ConfigImplementationGenerator { ConfigImplementationGenerator( final ConfigImplementationGeneratorProcessor processor, - final TypeElement configInterfaceType + final TypeElement source ) { this.processor = processor; - this.source = configInterfaceType; + this.source = source; } /** @@ -159,7 +159,7 @@ private boolean gatherElementSpec( } //todo add tests for hidden in both ap and interfaces and defaults in interfaces - AnnotationProcessorHandler.handle(element, nodeType, fieldSpec); + AnnotationProcessorHandler.handle(this.source, element, nodeType, fieldSpec); spec.add(simpleName, fieldSpec); } From c2dfc9649444c7030bf358be102bb44b23a49aee Mon Sep 17 00:00:00 2001 From: Tim203 Date: Mon, 12 Feb 2024 22:33:19 +0100 Subject: [PATCH 26/35] Set a default value for config sections --- .../processor/ConfigImplementationGenerator.java | 14 ++++++++++++++ .../configurate/interfaces/processor/Utils.java | 8 ++++++++ 2 files changed, 22 insertions(+) diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java index 6247e4afa..bee1b4ba6 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java @@ -158,6 +158,20 @@ private boolean gatherElementSpec( fieldSpec.addModifiers(Modifier.TRANSIENT); } + // set a default value for config subsections + final TypeElement nodeTypeElement = Utils.toBoxedTypeElement(nodeType, this.processor.typeUtils); + if (!element.isDefault() && hasAnnotation(nodeTypeElement, ConfigSerializable.class)) { + ClassName configClass = ClassName.get(nodeTypeElement); + if (nodeTypeElement.getKind().isInterface()) { + // first find the generated class for given type + String implName = this.processor.generatedClasses().getProperty(configClass.reflectionName()); + // make it canonical and replace superinterface type with source interface type if present + implName = implName.replace('$', '.').replace(type.getQualifiedName(), this.source.getQualifiedName()); + configClass = ClassName.bestGuess(implName); + } + fieldSpec.initializer("new $T()", configClass); + } + //todo add tests for hidden in both ap and interfaces and defaults in interfaces AnnotationProcessorHandler.handle(this.source, element, nodeType, fieldSpec); diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/Utils.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/Utils.java index 21fc5e047..8f9143499 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/Utils.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/Utils.java @@ -27,6 +27,7 @@ import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; final class Utils { @@ -67,4 +68,11 @@ static boolean isNumeric(final TypeMirror typeMirror) { || MoreTypes.isTypeOf(Long.TYPE, typeMirror); } + public static TypeElement toBoxedTypeElement(final TypeMirror mirror, final Types typeUtils) { + if (mirror.getKind().isPrimitive()) { + return typeUtils.boxedClass(MoreTypes.asPrimitiveType(mirror)); + } + return MoreTypes.asTypeElement(mirror); + } + } From 1af436d471a9589eb644906f5be37a1e61050b0b Mon Sep 17 00:00:00 2001 From: Tim203 Date: Mon, 12 Feb 2024 22:38:20 +0100 Subject: [PATCH 27/35] Update test --- .../ap/src/test/resources/structure/MultiLayerConfigImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extra/interface/ap/src/test/resources/structure/MultiLayerConfigImpl.java b/extra/interface/ap/src/test/resources/structure/MultiLayerConfigImpl.java index 0bc2ef001..15f6056eb 100644 --- a/extra/interface/ap/src/test/resources/structure/MultiLayerConfigImpl.java +++ b/extra/interface/ap/src/test/resources/structure/MultiLayerConfigImpl.java @@ -10,7 +10,7 @@ final class MultiLayerConfigImpl implements MultiLayerConfig { private String test; - private MultiLayerConfig.SecondLayer second; + private MultiLayerConfig.SecondLayer second = new SecondLayerImpl(); @Override public String test() { From 7e45e31a818bd8d153a083f576fa68e99a4fd202 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 13 Feb 2024 22:42:26 +0100 Subject: [PATCH 28/35] Added serialization to InterfaceTypeSerializer --- .../interfaces/InterfaceTypeSerializer.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializer.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializer.java index 73ca8b1fd..c76576dfc 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializer.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializer.java @@ -87,14 +87,23 @@ canonicalName, availableMappings() } @Override + @SuppressWarnings({"rawtypes", "unchecked"}) public void serialize( final Type type, final @Nullable Object obj, final ConfigurationNode node ) throws SerializationException { - throw new SerializationException( - "I can only deserialize stuff. Serialization has to be handled by ObjectMapper!" - ); + // don't determine serialize from type, because that might be incorrect for subsections + if (obj == null) { + node.set(null); + return; + } + + final @Nullable TypeSerializer serializer = node.options().serializers().get(obj.getClass()); + if (serializer == null) { + throw new SerializationException("No serializer found for implementation class " + obj.getClass()); + } + serializer.serialize(obj.getClass(), obj, node); } private String availableMappings() { From 6945a5d313a300843c26645ad72dc90e84a28738 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 26 May 2024 19:06:03 -0400 Subject: [PATCH 29/35] Respect superclasses' declaration of Exclude --- .../ConfigImplementationGenerator.java | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java index bee1b4ba6..e6c89dcdc 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java @@ -30,12 +30,15 @@ import org.spongepowered.configurate.objectmapping.ConfigSerializable; import org.spongepowered.configurate.objectmapping.meta.PostProcess; +import java.util.HashSet; import java.util.List; +import java.util.Set; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; @@ -86,6 +89,19 @@ class ConfigImplementationGenerator { private boolean gatherElementSpec( final TypeSpecBuilderTracker spec, final TypeElement type + ) { + return gatherElementSpec(spec, type, new HashSet<>()); + } + + /** + * Returns true if successful, otherwise false. + * + * @param excludedElements a set of all elements a superclass has annotated with {@link Exclude}. + */ + private boolean gatherElementSpec( + final TypeSpecBuilderTracker spec, + final TypeElement type, + final Set excludedElements ) { // first handle own elements @@ -117,9 +133,16 @@ private boolean gatherElementSpec( continue; } + if (excludedElements.contains(element.getSimpleName())) { + continue; + } final boolean excluded = hasAnnotation(element, Exclude.class); if (excluded) { if (element.isDefault()) { + // Do not add setters to the exclusion list as they will not be serialized anyway. + if (element.getParameters().isEmpty()) { + excludedElements.add(element.getSimpleName()); + } continue; } this.processor.printError( @@ -180,7 +203,7 @@ private boolean gatherElementSpec( // then handle parent elements for (final TypeMirror parent : type.getInterfaces()) { - gatherElementSpec(spec, (TypeElement) this.processor.typeUtils.asElement(parent)); + gatherElementSpec(spec, (TypeElement) this.processor.typeUtils.asElement(parent), excludedElements); } return true; } From edd068591d4202b28146ee5060b94d02d0abfb71 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 26 May 2024 19:06:21 -0400 Subject: [PATCH 30/35] Friendly error if implementation name cannot be found --- .../interfaces/processor/ConfigImplementationGenerator.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java index e6c89dcdc..b45cef942 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java @@ -188,6 +188,10 @@ private boolean gatherElementSpec( if (nodeTypeElement.getKind().isInterface()) { // first find the generated class for given type String implName = this.processor.generatedClasses().getProperty(configClass.reflectionName()); + if (implName == null) { + this.processor.printError("Could not determine an implementation type for method " + element.getSimpleName()); + return false; + } // make it canonical and replace superinterface type with source interface type if present implName = implName.replace('$', '.').replace(type.getQualifiedName(), this.source.getQualifiedName()); configClass = ClassName.bestGuess(implName); From 43477006a55a7f7a2cb53269cd7c9a58e9aa7af2 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 12 Jun 2024 22:05:22 +0200 Subject: [PATCH 31/35] Made it easier to use the interface's default options --- .../interfaces/InterfaceDefaultOptions.java | 21 +++++++------------ .../InterfaceTypeSerializerTest.java | 4 ++-- .../configurate/interfaces/TypeUtils.java | 1 - 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java index 6c6c8cc4d..1c0be717b 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java @@ -19,8 +19,6 @@ import org.spongepowered.configurate.ConfigurationOptions; import org.spongepowered.configurate.serialize.TypeSerializerCollection; -import java.util.function.UnaryOperator; - /** * This class has the default {@link ConfigurationOptions} * with {@link InterfaceTypeSerializer} added to the serializers. @@ -29,15 +27,12 @@ */ public final class InterfaceDefaultOptions { - private static final ConfigurationOptions DEFAULTS = - ConfigurationOptions.defaults() - .serializers( + private static final TypeSerializerCollection DEFAULTS = TypeSerializerCollection.builder() .registerAnnotated(InterfaceTypeSerializer::applicable, InterfaceTypeSerializer.INSTANCE) .registerAnnotatedObjects(InterfaceMiddleware.buildObjectMapperWithMiddleware()) .registerAll(TypeSerializerCollection.defaults()) - .build() - ); + .build(); private InterfaceDefaultOptions() { } @@ -48,21 +43,21 @@ private InterfaceDefaultOptions() { * @return the default ConfigurationOptions with {@link InterfaceTypeSerializer} added to the serializers. * @since 4.2.0 */ - public static ConfigurationOptions get() { - return DEFAULTS; + public static ConfigurationOptions defaults() { + return addTo(ConfigurationOptions.defaults()); } /** * Sets the default configuration options to be used by the resultant loader - * by providing a function which takes the current {@link #get() default options} - * and applies any desired changes. + * by providing a function which takes interface's default serializer + * collection and applies any desired changes. * * @param options to transform the existing default options * @return the default options with the applied changes * @since 4.2.0 */ - public static ConfigurationOptions with(final UnaryOperator options) { - return options.apply(DEFAULTS); + public static ConfigurationOptions addTo(final ConfigurationOptions options) { + return options.serializers(DEFAULTS.childBuilder().registerAll(options.serializers()).build()); } } diff --git a/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializerTest.java b/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializerTest.java index 93c5dbeeb..23e826a1b 100644 --- a/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializerTest.java +++ b/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/InterfaceTypeSerializerTest.java @@ -30,7 +30,7 @@ class InterfaceTypeSerializerTest { @Test void testDeserialization() throws ConfigurateException { - final BasicConfigurationNode node = BasicConfigurationNode.root(InterfaceDefaultOptions.get()); + final BasicConfigurationNode node = BasicConfigurationNode.root(InterfaceDefaultOptions.defaults()); // doesn't deserialize if value is NullValue node.node("hello").set("world"); @@ -41,7 +41,7 @@ void testDeserialization() throws ConfigurateException { @Test void testInnerDeserialization() throws ConfigurateException { - final BasicConfigurationNode node = BasicConfigurationNode.root(InterfaceDefaultOptions.get()); + final BasicConfigurationNode node = BasicConfigurationNode.root(InterfaceDefaultOptions.defaults()); // doesn't deserialize if value is NullValue node.node("hello").set("world"); diff --git a/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TypeUtils.java b/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TypeUtils.java index 979c9ba5b..de682c970 100644 --- a/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TypeUtils.java +++ b/extra/interface/src/test/java/org/spongepowered/configurate/interfaces/TypeUtils.java @@ -33,7 +33,6 @@ static Class configImplementationFor(final Class interfaceCl } } - @SuppressWarnings("UnstableApiUsage") private static String implClassNameFor(final Class interfaceClass) { final String packageName = interfaceClass.getPackage().getName(); // include the package name dot as well From e79e4d8aebe84675dd02122bb2182194055ea170 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 12 Jun 2024 22:30:50 +0200 Subject: [PATCH 32/35] Oops, it's the other way around woo --- .../configurate/interfaces/InterfaceDefaultOptions.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java index 1c0be717b..b6806109a 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java @@ -31,7 +31,6 @@ public final class InterfaceDefaultOptions { TypeSerializerCollection.builder() .registerAnnotated(InterfaceTypeSerializer::applicable, InterfaceTypeSerializer.INSTANCE) .registerAnnotatedObjects(InterfaceMiddleware.buildObjectMapperWithMiddleware()) - .registerAll(TypeSerializerCollection.defaults()) .build(); private InterfaceDefaultOptions() { @@ -57,7 +56,8 @@ public static ConfigurationOptions defaults() { * @since 4.2.0 */ public static ConfigurationOptions addTo(final ConfigurationOptions options) { - return options.serializers(DEFAULTS.childBuilder().registerAll(options.serializers()).build()); + // This creates a new TypeSerializerCollection with 'options' as parent. Child takes priority over parent. + return options.serializers(serializers -> serializers.registerAll(DEFAULTS)); } } From 31c63fd3f551c35afcd2a17124f6f66d67afd78e Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 21 Aug 2024 15:33:29 -0400 Subject: [PATCH 33/35] Superclasses with ConfigSerializable define order --- .../processor/ConfigImplementationGenerator.java | 6 +++++- .../interfaces/processor/TypeSpecBuilderTracker.java | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java index b45cef942..dcb60dc35 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java @@ -104,6 +104,8 @@ private boolean gatherElementSpec( final Set excludedElements ) { // first handle own elements + // If this interface is noted as ConfigSerializable, then its element order should override previous configs + final boolean hasConfigSerializable = hasAnnotation(type, ConfigSerializable.class); for (final Element enclosedElement : type.getEnclosedElements()) { final ElementKind kind = enclosedElement.getKind(); @@ -202,7 +204,9 @@ private boolean gatherElementSpec( //todo add tests for hidden in both ap and interfaces and defaults in interfaces AnnotationProcessorHandler.handle(this.source, element, nodeType, fieldSpec); - spec.add(simpleName, fieldSpec); + // If this is a getter and ConfigSerializable, then it should define where in the config + // this element should go. + spec.add(simpleName, fieldSpec, hasConfigSerializable && element.getParameters().isEmpty()); } // then handle parent elements diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java index 2e6380853..da3cd230a 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/TypeSpecBuilderTracker.java @@ -36,8 +36,9 @@ final class TypeSpecBuilderTracker { private final Map methodSpecs = new LinkedHashMap<>(); private final Map typeSpecs = new LinkedHashMap<>(); - void add(final String fieldIdentifier, final FieldSpec.Builder builder) { - final FieldSpec.Builder existing = this.fieldSpecs.get(fieldIdentifier); + void add(final String fieldIdentifier, final FieldSpec.Builder builder, final boolean override) { + final FieldSpec.Builder existing = override ? this.fieldSpecs.remove(fieldIdentifier) + : this.fieldSpecs.get(fieldIdentifier); if (existing != null) { final FieldSpec existingBuild = existing.build(); final FieldSpec builderBuild = builder.build(); @@ -46,6 +47,9 @@ void add(final String fieldIdentifier, final FieldSpec.Builder builder) { existing.initializer(builderBuild.initializer); } existing.addAnnotations(pickNewAnnotations(existingBuild.annotations, builderBuild.annotations)); + if (override) { + this.fieldSpecs.put(fieldIdentifier, existing); + } return; } this.fieldSpecs.put(fieldIdentifier, builder); From 7543cd45dfd116f5121a23e4b95d43387a733b1f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 24 Aug 2024 18:55:23 -0400 Subject: [PATCH 34/35] Don't try to initialize ConfigSerializable if @Field is marked --- .../interfaces/processor/ConfigImplementationGenerator.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java index dcb60dc35..2283ac939 100644 --- a/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java +++ b/extra/interface/ap/src/main/java/org/spongepowered/configurate/interfaces/processor/ConfigImplementationGenerator.java @@ -179,13 +179,14 @@ private boolean gatherElementSpec( final FieldSpec.Builder fieldSpec = FieldSpec.builder(TypeName.get(nodeType), simpleName, Modifier.PRIVATE); - if (hasAnnotation(element, Field.class)) { + final boolean isField = hasAnnotation(element, Field.class); + if (isField) { fieldSpec.addModifiers(Modifier.TRANSIENT); } // set a default value for config subsections final TypeElement nodeTypeElement = Utils.toBoxedTypeElement(nodeType, this.processor.typeUtils); - if (!element.isDefault() && hasAnnotation(nodeTypeElement, ConfigSerializable.class)) { + if (!isField && !element.isDefault() && hasAnnotation(nodeTypeElement, ConfigSerializable.class)) { ClassName configClass = ClassName.get(nodeTypeElement); if (nodeTypeElement.getKind().isInterface()) { // first find the generated class for given type From 7b4769c394d67e8a564157cbb263c699131f8027 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 10 Sep 2024 15:46:33 -0400 Subject: [PATCH 35/35] InterfaceDefaultOptions#addTo with ObjectMapper modification --- .../interfaces/InterfaceDefaultOptions.java | 23 ++++++++++++++++++- .../interfaces/InterfaceMiddleware.java | 5 ++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java index b6806109a..b99f3d820 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceDefaultOptions.java @@ -17,8 +17,11 @@ package org.spongepowered.configurate.interfaces; import org.spongepowered.configurate.ConfigurationOptions; +import org.spongepowered.configurate.objectmapping.ObjectMapper; import org.spongepowered.configurate.serialize.TypeSerializerCollection; +import java.util.function.Consumer; + /** * This class has the default {@link ConfigurationOptions} * with {@link InterfaceTypeSerializer} added to the serializers. @@ -30,7 +33,7 @@ public final class InterfaceDefaultOptions { private static final TypeSerializerCollection DEFAULTS = TypeSerializerCollection.builder() .registerAnnotated(InterfaceTypeSerializer::applicable, InterfaceTypeSerializer.INSTANCE) - .registerAnnotatedObjects(InterfaceMiddleware.buildObjectMapperWithMiddleware()) + .registerAnnotatedObjects(InterfaceMiddleware.buildObjectMapperWithMiddleware().build()) .build(); private InterfaceDefaultOptions() { @@ -60,4 +63,22 @@ public static ConfigurationOptions addTo(final ConfigurationOptions options) { return options.serializers(serializers -> serializers.registerAll(DEFAULTS)); } + /** + * {@link #addTo(ConfigurationOptions)} with an option to customize the {@link ObjectMapper.Factory}. + * + * @param options to transform the existing default options + * @param objectMapperConsumer to transform the ObjectMapper factory + * @return the default options with the applied changes + * @since 4.2.0 + */ + public static ConfigurationOptions addTo(final ConfigurationOptions options, + final Consumer objectMapperConsumer) { + return options.serializers(serializers -> { + final ObjectMapper.Factory.Builder builder = InterfaceMiddleware.buildObjectMapperWithMiddleware(); + objectMapperConsumer.accept(builder); + serializers.registerAnnotated(InterfaceTypeSerializer::applicable, InterfaceTypeSerializer.INSTANCE) + .registerAnnotatedObjects(builder.build()); + }); + } + } diff --git a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceMiddleware.java b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceMiddleware.java index 9b2381557..6b744188e 100644 --- a/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceMiddleware.java +++ b/extra/interface/src/main/java/org/spongepowered/configurate/interfaces/InterfaceMiddleware.java @@ -37,13 +37,12 @@ final class InterfaceMiddleware { private InterfaceMiddleware() { } - static ObjectMapper.Factory buildObjectMapperWithMiddleware() { + static ObjectMapper.Factory.Builder buildObjectMapperWithMiddleware() { return ObjectMapper.factoryBuilder() .addConstraint(DecimalRange.class, Number.class, decimalRange()) .addConstraint(NumericRange.class, Number.class, numericRange()) .addConstraint(StringRange.class, String.class, stringRange()) - .addProcessor(Hidden.class, hiddenProcessor()) - .build(); + .addProcessor(Hidden.class, hiddenProcessor()); } private static Constraint.Factory decimalRange() {