feat(config): Config tweakers
This commit is contained in:
parent
e4da2eadd8
commit
5fc774c53c
|
@ -5,11 +5,22 @@ import com.squareup.javapoet.TypeName;
|
|||
|
||||
import javax.lang.model.element.TypeElement;
|
||||
|
||||
public record ConfigClass(TypeElement classElement, ClassName className, TypeName typeName, ClassName generatedClassName, String[] referencedConfigs) {
|
||||
public static ConfigClass of(TypeElement element, String[] referencedConfigs, boolean hasManifold) {
|
||||
public record ConfigClass(
|
||||
TypeElement classElement,
|
||||
ClassName className,
|
||||
TypeName typeName,
|
||||
ClassName generatedClassName,
|
||||
String[] referencedConfigs,
|
||||
TypeName tweakerName
|
||||
) {
|
||||
public static ConfigClass of(TypeElement element, String[] referencedConfigs, TypeName tweakerName, boolean hasManifold) {
|
||||
ClassName className = ClassName.get(element);
|
||||
String pkg = hasManifold ? "gsoncompile.extensions." + className.packageName() + "." + className.simpleNames().get(0) : className.packageName();
|
||||
ClassName generatedClassName = ClassName.get(pkg, "JFC_" + className.simpleNames().get(0), className.simpleNames().subList(1, className.simpleNames().size()).toArray(String[]::new));
|
||||
return new ConfigClass(element, ClassName.get(element), TypeName.get(element.asType()), generatedClassName, referencedConfigs);
|
||||
return new ConfigClass(element, ClassName.get(element), TypeName.get(element.asType()), generatedClassName, referencedConfigs, tweakerName);
|
||||
}
|
||||
|
||||
public boolean hasTweaker() {
|
||||
return ConfigProcessor.isUsable(tweakerName);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,7 @@ package io.gitlab.jfronny.libjf.config.plugin;
|
|||
|
||||
import com.squareup.javapoet.*;
|
||||
import io.gitlab.jfronny.commons.StringFormatter;
|
||||
import io.gitlab.jfronny.commons.throwable.Coerce;
|
||||
import io.gitlab.jfronny.gson.compile.processor.core.*;
|
||||
import io.gitlab.jfronny.gson.compile.processor.core.value.ElementException;
|
||||
import io.gitlab.jfronny.gson.reflect.TypeToken;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.*;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.dsl.DSL;
|
||||
|
@ -13,13 +11,13 @@ import io.gitlab.jfronny.libjf.config.api.v1.type.Type;
|
|||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.type.*;
|
||||
import javax.lang.model.util.ElementFilter;
|
||||
import javax.tools.Diagnostic;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SupportedSourceVersion(SourceVersion.RELEASE_17)
|
||||
|
@ -44,11 +42,11 @@ public class ConfigProcessor extends AbstractProcessor2 {
|
|||
annotations.stream().flatMap(s -> roundEnvironment.getElementsAnnotatedWith(s).stream()).forEach(element -> {
|
||||
JfConfig jc = element.getAnnotation(JfConfig.class);
|
||||
if (jc != null) {
|
||||
toGenerate.add(ConfigClass.of((TypeElement) element, jc.referencedConfigs(), hasManifold));
|
||||
toGenerate.add(ConfigClass.of((TypeElement) element, jc.referencedConfigs(), getTypeName(jc::tweaker), hasManifold));
|
||||
}
|
||||
});
|
||||
// Generate adapters
|
||||
toGenerate.forEach(Coerce.<ConfigClass, ElementException>consumer(toProcess -> process(toProcess, toGenerate)).addHandler(ElementException.class, e -> e.printMessage(message)));
|
||||
toGenerate.forEach(toProcess -> process(toProcess, toGenerate));
|
||||
for (var entry : seen.keySet().stream().collect(Collectors.groupingBy(ClassName::packageName)).entrySet()) {
|
||||
Map<List<String>, TypeSpec.Builder> known = entry.getValue().stream()
|
||||
.collect(Collectors.toMap(
|
||||
|
@ -108,7 +106,7 @@ public class ConfigProcessor extends AbstractProcessor2 {
|
|||
return null;
|
||||
}
|
||||
|
||||
private void process(ConfigClass toProcess, Set<ConfigClass> other) throws ElementException {
|
||||
private void process(ConfigClass toProcess, Set<ConfigClass> other) {
|
||||
if (seen.containsKey(toProcess.generatedClassName())) return; // Don't process the same class more than once
|
||||
|
||||
TypeSpec.Builder spec = TypeSpec.classBuilder(toProcess.generatedClassName().simpleName())
|
||||
|
@ -120,10 +118,14 @@ public class ConfigProcessor extends AbstractProcessor2 {
|
|||
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
code.addStatement("$T.getInstance().migrateFiles($S)", ConfigHolder.class, modId);
|
||||
code.add("INSTANCE = $T.create($S).register(builder0 -> builder0", DSL.class, modId).indent();
|
||||
code.add("INSTANCE = $T.create($S).register(builder0 -> ", DSL.class, modId);
|
||||
if (toProcess.hasTweaker()) code.add("$T.tweak(", toProcess.tweakerName());
|
||||
code.add("builder0").indent();
|
||||
for (String s : toProcess.referencedConfigs()) code.add("\n.referenceConfig($S)", s);
|
||||
process(toProcess.classElement(), code, new AtomicInteger(0));
|
||||
code.unindent().add("\n);\n");
|
||||
code.unindent().add("\n");
|
||||
if (toProcess.hasTweaker()) code.add(")");
|
||||
code.add(");\n");
|
||||
|
||||
spec.addField(FieldSpec.builder(double.class, "Infinity", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL).initializer("$T.POSITIVE_INFINITY", Double.class).build());
|
||||
spec.addStaticBlock(code.build());
|
||||
|
@ -138,12 +140,17 @@ public class ConfigProcessor extends AbstractProcessor2 {
|
|||
for (TypeElement klazz : ElementFilter.typesIn(source.getEnclosedElements())) {
|
||||
Category v = klazz.getAnnotation(Category.class);
|
||||
if (v != null) {
|
||||
TypeName tweaker = getTypeName(v::tweaker);
|
||||
String name = klazz.getSimpleName().toString();
|
||||
name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
|
||||
code.add("\n.category($S, builder$L -> builder$L", name, i.incrementAndGet(), i.get()).indent();
|
||||
code.add("\n.category($S, builder$L -> ", name, i.incrementAndGet());
|
||||
if (isUsable(tweaker)) code.add("$T.tweak(", tweaker);
|
||||
code.add("builder$L", i.get()).indent();
|
||||
for (String s : v.referencedConfigs()) code.add("\n.referencedConfigs($S)", s);
|
||||
process(klazz, code, i);
|
||||
code.unindent().add("\n)");
|
||||
code.unindent().add("\n");
|
||||
if (isUsable(tweaker)) code.add(")");
|
||||
code.add(")");
|
||||
}
|
||||
}
|
||||
for (VariableElement field : ElementFilter.fieldsIn(source.getEnclosedElements())) {
|
||||
|
@ -193,4 +200,18 @@ public class ConfigProcessor extends AbstractProcessor2 {
|
|||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
private TypeName getTypeName(Supplier<Class<?>> annotationSource) {
|
||||
try {
|
||||
TypeName name = ClassName.get(annotationSource.get());
|
||||
message.printWarning("Expected access to class on mirrored annotation to throw MirroredTypeException as specified in spec, but that didn't happen. This is unsupported");
|
||||
return name;
|
||||
} catch (MirroredTypeException e) {
|
||||
return TypeName.get(e.getTypeMirror());
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isUsable(TypeName name) {
|
||||
return name != null && !Objects.equals(TypeName.VOID, name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,5 +8,12 @@ import java.lang.annotation.*;
|
|||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface Category {
|
||||
/**
|
||||
* @return Other configs that should be shown to users by their ID
|
||||
*/
|
||||
String[] referencedConfigs() default {};
|
||||
/**
|
||||
* @return A class for modifying the config with the ConfigBuilder API. Must implement the static method {@code CategoryBuilder tweak(CategoryBuilder builder)}
|
||||
*/
|
||||
Class<?> tweaker() default void.class;
|
||||
}
|
||||
|
|
|
@ -8,5 +8,13 @@ import java.lang.annotation.*;
|
|||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface JfConfig {
|
||||
/**
|
||||
* @return Other configs that should be shown to users by their ID
|
||||
*/
|
||||
String[] referencedConfigs() default {};
|
||||
|
||||
/**
|
||||
* @return A class for modifying the config with the ConfigBuilder API. Must implement the static method {@code ConfigBuilder tweak(ConfigBuilder builder)}
|
||||
*/
|
||||
Class<?> tweaker() default void.class;
|
||||
}
|
||||
|
|
|
@ -13,23 +13,28 @@ import io.gitlab.jfronny.libjf.config.impl.dsl.DslEntryInfo;
|
|||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class ReflectiveConfigBuilderImpl implements ReflectiveConfigBuilder {
|
||||
private final AuxiliaryMetadata rootMeta;
|
||||
private final Class<?> rootClass;
|
||||
private final Class<?> rootTweaker;
|
||||
private final String id;
|
||||
|
||||
public ReflectiveConfigBuilderImpl(String id, Class<?> klazz) {
|
||||
this.id = id;
|
||||
this.rootClass = Objects.requireNonNull(klazz);
|
||||
this.rootMeta = AuxiliaryMetadata.of(klazz.getAnnotation(JfConfig.class))
|
||||
.merge(AuxiliaryMetadata.forMod(id));
|
||||
JfConfig annotation = klazz.getAnnotation(JfConfig.class);
|
||||
rootTweaker = annotation.tweaker();
|
||||
this.rootMeta = AuxiliaryMetadata.of(annotation).merge(AuxiliaryMetadata.forMod(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigBuilder<?> apply(ConfigBuilder<?> builder) {
|
||||
return applyCategory(builder, rootClass, rootMeta);
|
||||
return applyCategory(builder, rootClass, findTweaker(rootTweaker, ConfigBuilder.class), rootMeta);
|
||||
}
|
||||
|
||||
private static <T extends CategoryBuilder<?>> T applyCategory(T builder, Class<?> configClass, AuxiliaryMetadata meta) {
|
||||
private <T extends CategoryBuilder<?>> T applyCategory(T builder, Class<?> configClass, Function<T, T> tweaker, AuxiliaryMetadata meta) {
|
||||
meta.applyTo(builder);
|
||||
for (Field field : configClass.getFields()) {
|
||||
if (field.isAnnotationPresent(Entry.class)) {
|
||||
|
@ -50,13 +55,26 @@ public class ReflectiveConfigBuilderImpl implements ReflectiveConfigBuilder {
|
|||
|
||||
for (Class<?> categoryClass : configClass.getClasses()) {
|
||||
if (categoryClass.isAnnotationPresent(Category.class)) {
|
||||
Category annotation = categoryClass.getAnnotation(Category.class);
|
||||
String name = categoryClass.getSimpleName();
|
||||
name = Character.toLowerCase(name.charAt(0)) + name.substring(1); // camelCase
|
||||
builder.category(name, builder1 -> applyCategory(builder1, categoryClass, AuxiliaryMetadata.of(categoryClass.getAnnotation(Category.class))));
|
||||
var categoryTweaker = findTweaker(annotation.tweaker(), CategoryBuilder.class);
|
||||
builder.category(name, builder1 -> applyCategory(builder1, categoryClass, categoryTweaker, AuxiliaryMetadata.of(categoryClass.getAnnotation(Category.class))));
|
||||
}
|
||||
}
|
||||
|
||||
return builder;
|
||||
return tweaker.apply(builder);
|
||||
}
|
||||
|
||||
private <T> Function<T, T> findTweaker(Class<?> targetClass, Class<T> tweakedClass) {
|
||||
try {
|
||||
return Objects.equals(targetClass, void.class)
|
||||
? Function.identity()
|
||||
: Reflect.staticFunction(targetClass, "tweak", tweakedClass, tweakedClass);
|
||||
} catch (Throwable t) {
|
||||
LibJf.LOGGER.error("Could not find tweaker " + targetClass + " for mod " + id, t);
|
||||
return Function.identity();
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> ThrowingConsumer<T, Throwable> staticToConsumer(Class<?> klazz, Method method) throws Throwable {
|
||||
|
|
|
@ -6,7 +6,7 @@ import io.gitlab.jfronny.libjf.config.api.v1.*;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@JfConfig(referencedConfigs = {"libjf-web-v0"})
|
||||
@JfConfig(referencedConfigs = {"libjf-web-v0"}, tweaker = TestConfigTweaker.class)
|
||||
public class TestConfig {
|
||||
@Entry public static boolean disablePacks = false;
|
||||
@Entry public static Boolean disablePacks2 = false;
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package io.gitlab.jfronny.libjf.config.test.reflect;
|
||||
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.dsl.ConfigBuilder;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class TestConfigTweaker {
|
||||
public static ConfigBuilder<?> tweak(ConfigBuilder<?> builder) {
|
||||
if (!Objects.equals("libjf-config-reflect-v1-testmod", builder.getId())) throw new IllegalStateException("No!");
|
||||
LibJf.LOGGER.info("Called config tweaker");
|
||||
return builder;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue