[config] Initial prototype for compile-time configs, module name changes and legacy compatibility
This commit is contained in:
parent
6e359462d2
commit
6a77d3221a
|
@ -0,0 +1,4 @@
|
|||
#!/bin/zsh
|
||||
cd ~/Downloads
|
||||
java -cp asm-9.3.jar:asm-analysis-9.3.jar:asm-tree-9.3.jar:asm-util-9.3.jar org.objectweb.asm.util.ASMifier /mnt/Projects/Minecraft/LibJF/libjf-config-core-v1/build/classes/java/testmod/io/gitlab/jfronny/libjf/config/test/TestConfigCustom\$SomeCategory.class > /mnt/Projects/Minecraft/LibJF/libjf-config-compiler-plugin/src/main/java/asm/io/gitlab/jfronny/libjf/config/test/TestConfigCustom\$SomeCategoryDump.java
|
||||
java -cp asm-9.3.jar:asm-analysis-9.3.jar:asm-tree-9.3.jar:asm-util-9.3.jar org.objectweb.asm.util.ASMifier /mnt/Projects/Minecraft/LibJF/libjf-config-core-v1/build/classes/java/testmod/io/gitlab/jfronny/libjf/config/test/TestConfigCustom.class > /mnt/Projects/Minecraft/LibJF/libjf-config-compiler-plugin/src/main/java/asm/io/gitlab/jfronny/libjf/config/test/TestConfigCustomDump.java
|
|
@ -7,7 +7,7 @@ maven_group=io.gitlab.jfronny.libjf
|
|||
archive_base_name=libjf
|
||||
|
||||
dev_only_module=libjf-devutil-v0
|
||||
non_mod_project=libjf-config-plugin
|
||||
non_mod_project=libjf-config-compiler-plugin
|
||||
|
||||
modrinth_id=libjf
|
||||
modrinth_optional_dependencies=fabric-api
|
||||
|
@ -16,3 +16,4 @@ curseforge_optional_dependencies=fabric-api
|
|||
|
||||
fabric_version=0.60.0+1.19.2
|
||||
commons_version=2022.7.4+11-13-3
|
||||
bytebuddy_version=1.12.13
|
|
@ -0,0 +1,7 @@
|
|||
archivesBaseName = "libjf-config-reflect-v0"
|
||||
|
||||
dependencies {
|
||||
api project(path: ":libjf-base", configuration: "dev")
|
||||
api project(path: ":libjf-config-core-v1", configuration: "dev")
|
||||
include modImplementation(fabricApi.module("fabric-command-api-v2", "${project.fabric_version}"))
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl;
|
||||
package io.gitlab.jfronny.libjf.config.impl.commands;
|
||||
|
||||
import com.mojang.brigadier.Command;
|
||||
import com.mojang.brigadier.arguments.*;
|
||||
|
@ -8,12 +8,12 @@ import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
|
|||
import io.gitlab.jfronny.commons.throwable.ThrowingRunnable;
|
||||
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
import io.gitlab.jfronny.libjf.config.api.*;
|
||||
import io.gitlab.jfronny.libjf.config.api.type.Type;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.*;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.type.Type;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
import net.minecraft.text.*;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
@ -29,12 +29,12 @@ public class JfConfigCommand implements ModInitializer {
|
|||
LiteralArgumentBuilder<ServerCommandSource> c_config = literal("config")
|
||||
.requires((serverCommandSource) -> serverCommandSource.hasPermissionLevel(4))
|
||||
.executes(context -> {
|
||||
context.getSource().sendFeedback(Text.literal("[libjf-config-v1] Loaded configs for:"), false);
|
||||
ConfigHolder.getInstance().getRegistered().forEach((s, config) -> {
|
||||
context.getSource().sendFeedback(Text.literal("- " + s), false);
|
||||
});
|
||||
return Command.SINGLE_SUCCESS;
|
||||
});
|
||||
context.getSource().sendFeedback(Text.literal("[libjf-config-v1] Loaded configs for:"), false);
|
||||
ConfigHolder.getInstance().getRegistered().forEach((s, config) -> {
|
||||
context.getSource().sendFeedback(Text.literal("- " + s), false);
|
||||
});
|
||||
return Command.SINGLE_SUCCESS;
|
||||
});
|
||||
LiteralArgumentBuilder<ServerCommandSource> c_reload = literal("reload").executes(context -> {
|
||||
ConfigHolder.getInstance().getRegistered().forEach((mod, config) -> config.load());
|
||||
context.getSource().sendFeedback(Text.literal("[libjf-config-v1] Reloaded configs"), true);
|
||||
|
@ -130,6 +130,7 @@ public class JfConfigCommand implements ModInitializer {
|
|||
private <T> ArgumentType<?> getType(EntryInfo<T> info) {
|
||||
Type<T> type = info.getValueType();
|
||||
if (type.isInt()) return IntegerArgumentType.integer((int) info.getMinValue(), (int) info.getMaxValue());
|
||||
else if (type.isLong()) return LongArgumentType.longArg((long) info.getMinValue(), (long) info.getMaxValue());
|
||||
else if (type.isFloat()) return FloatArgumentType.floatArg((float) info.getMinValue(), (float) info.getMaxValue());
|
||||
else if (type.isDouble()) return DoubleArgumentType.doubleArg(info.getMinValue(), info.getMaxValue());
|
||||
else if (type.isString()) return StringArgumentType.greedyString();
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "libjf-config-commands-v1",
|
||||
"name": "LibJF Config Commands",
|
||||
"version": "${version}",
|
||||
"authors": ["JFronny"],
|
||||
"contact": {
|
||||
"website": "https://jfronny.gitlab.io",
|
||||
"repo": "https://gitlab.com/jfmods/libjf"
|
||||
},
|
||||
"license": "MIT",
|
||||
"environment": "server",
|
||||
"entrypoints": {
|
||||
"main": ["io.gitlab.jfronny.libjf.config.impl.commands.JfConfigCommand"]
|
||||
},
|
||||
"depends": {
|
||||
"fabricloader": ">=0.12.0",
|
||||
"minecraft": "*",
|
||||
"fabric-command-api-v2": "*",
|
||||
"libjf-base": ">=${version}",
|
||||
"libjf-config-core-v1": ">=${version}"
|
||||
},
|
||||
"custom": {
|
||||
"modmenu": {
|
||||
"badges": ["library"],
|
||||
"parent": "libjf"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
plugins {
|
||||
id 'java-gradle-plugin'
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
group project.maven_group
|
||||
version rootProject.ext.currentVer
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
name = 'JF Commons'
|
||||
url = 'https://gitlab.com/api/v4/projects/35745143/packages/maven'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(gradleApi())
|
||||
implementation("org.apache.ant:ant:1.10.12")
|
||||
implementation("io.gitlab.jfronny:commons-gson:$rootProject.commons_version")
|
||||
implementation("org.ow2.asm:asm:9.3")
|
||||
implementation("org.ow2.asm:asm-commons:9.3")
|
||||
implementation("org.ow2.asm:asm-util:9.3")
|
||||
implementation(project(":libjf-config-core-v1")) {
|
||||
transitive(false)
|
||||
}
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
simplePlugin {
|
||||
id = "io.gitlab.jfronny.libjf.libjf-config-compiler-plugin"
|
||||
implementationClass = "io.gitlab.jfronny.libjf.config.plugin.ConfigPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
|
||||
if (project.hasProperty("maven")) {
|
||||
maven {
|
||||
url = project.getProperty("maven")
|
||||
name = "dynamic"
|
||||
|
||||
credentials(HttpHeaderCredentials) {
|
||||
name = "Job-Token"
|
||||
value = System.getenv().CI_JOB_TOKEN
|
||||
}
|
||||
authentication {
|
||||
header(HttpHeaderAuthentication)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.publish.dependsOn(tasks.build)
|
||||
rootProject.tasks.deployDebug.dependsOn(tasks.publish)
|
|
@ -0,0 +1,54 @@
|
|||
package io.gitlab.jfronny.libjf.config.plugin;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.plugin.util.ZipCompressor;
|
||||
import org.apache.tools.zip.ZipOutputStream;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.internal.file.copy.CopyAction;
|
||||
import org.gradle.api.internal.file.copy.CopyActionProcessingStream;
|
||||
import org.gradle.api.tasks.WorkResult;
|
||||
import org.gradle.api.tasks.WorkResults;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class ConfigInjectCopyAction implements CopyAction {
|
||||
private final File zipFile;
|
||||
private final ZipCompressor compressor;
|
||||
private final String encoding;
|
||||
private final boolean preserveFileTimestamps;
|
||||
private final String modId;
|
||||
private final Set<Type> knownConfigClasses = new HashSet<>();
|
||||
|
||||
public ConfigInjectCopyAction(File zipFile,
|
||||
ZipCompressor compressor,
|
||||
String encoding,
|
||||
boolean preserveFileTimestamps,
|
||||
String modId) {
|
||||
this.zipFile = zipFile;
|
||||
this.compressor = compressor;
|
||||
this.encoding = encoding;
|
||||
this.preserveFileTimestamps = preserveFileTimestamps;
|
||||
this.modId = modId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorkResult execute(CopyActionProcessingStream stream) {
|
||||
try (ZipOutputStream zipOutStr = compressor.createArchiveOutputStream(zipFile)) {
|
||||
if (encoding != null) {
|
||||
zipOutStr.setEncoding(encoding);
|
||||
}
|
||||
AtomicBoolean fmjFound = new AtomicBoolean(false);
|
||||
stream.process(details -> fmjFound.compareAndSet(false,
|
||||
new StreamAction(zipOutStr, zipFile, preserveFileTimestamps, modId, knownConfigClasses)
|
||||
.processFile(details)
|
||||
));
|
||||
} catch (IOException e) {
|
||||
throw new GradleException("Could not create ZIP " + zipFile, e);
|
||||
}
|
||||
return WorkResults.didWork(true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package io.gitlab.jfronny.libjf.config.plugin;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.plugin.util.GradleVersionUtil;
|
||||
import io.gitlab.jfronny.libjf.config.plugin.util.ZipCompressor;
|
||||
import org.gradle.api.file.DuplicatesStrategy;
|
||||
import org.gradle.api.internal.file.copy.CopyAction;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.Internal;
|
||||
import org.gradle.api.tasks.bundling.Jar;
|
||||
|
||||
public abstract class ConfigInjectTask extends Jar {
|
||||
@Input
|
||||
public abstract Property<String> getModId();
|
||||
private final GradleVersionUtil versionUtil;
|
||||
|
||||
public ConfigInjectTask() {
|
||||
super();
|
||||
setDuplicatesStrategy(DuplicatesStrategy.FAIL);
|
||||
versionUtil = new GradleVersionUtil(getProject().getGradle().getGradleVersion());
|
||||
//TODO ensure this is unneeded
|
||||
// setManifest(new DefaultInheritManifest(getServices().get(FileResolver.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CopyAction createCopyAction() {
|
||||
return new ConfigInjectCopyAction(
|
||||
getArchiveFile().get().getAsFile(),
|
||||
getInternalCompressor(),
|
||||
this.getMetadataCharset(),
|
||||
isPreserveFileTimestamps(),
|
||||
getModId().get()
|
||||
);
|
||||
}
|
||||
|
||||
@Internal
|
||||
protected ZipCompressor getInternalCompressor() {
|
||||
return versionUtil.getInternalCompressor(getEntryCompression(), this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package io.gitlab.jfronny.libjf.config.plugin;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
|
||||
public class ConfigPlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
package io.gitlab.jfronny.libjf.config.plugin;
|
||||
|
||||
import io.gitlab.jfronny.gson.stream.JsonReader;
|
||||
import io.gitlab.jfronny.gson.stream.JsonWriter;
|
||||
import io.gitlab.jfronny.libjf.config.plugin.asm.ConfigInjectClassTransformer;
|
||||
import io.gitlab.jfronny.libjf.config.plugin.fmj.FabricModJsonTransformer;
|
||||
import io.gitlab.jfronny.libjf.config.plugin.util.*;
|
||||
import org.apache.tools.zip.*;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.file.FileCopyDetails;
|
||||
import org.objectweb.asm.*;
|
||||
import org.objectweb.asm.util.CheckClassAdapter;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class StreamAction {
|
||||
private static final long CONSTANT_TIME_FOR_ZIP_ENTRIES = (new GregorianCalendar(1980, 1, 1, 0, 0, 0)).getTimeInMillis();
|
||||
private final ZipOutputStream zipOutStr;
|
||||
private final File zipFile;
|
||||
private final boolean preserveFileTimestamps;
|
||||
private final String modId;
|
||||
private final Set<Type> knownConfigClasses;
|
||||
|
||||
public StreamAction(ZipOutputStream zipOutStr, File zipFile, boolean preserveFileTimestamps, String modId, Set<Type> knownConfigClasses) {
|
||||
this.zipOutStr = zipOutStr;
|
||||
this.zipFile = zipFile;
|
||||
this.preserveFileTimestamps = preserveFileTimestamps;
|
||||
this.modId = modId;
|
||||
this.knownConfigClasses = knownConfigClasses;
|
||||
}
|
||||
|
||||
public boolean processFile(FileCopyDetails details) {
|
||||
try {
|
||||
if (details.isDirectory()) {
|
||||
visitDirectory(details);
|
||||
return false;
|
||||
} else {
|
||||
return visitFile(details);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new GradleException("Could not add " + details + " to ZIP " + zipFile, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void visitDirectory(FileCopyDetails details) throws IOException {
|
||||
String path = details.getRelativePath().getPathString() + "/";
|
||||
ZipEntry archiveEntry = new ZipEntry(path);
|
||||
archiveEntry.setTime(getArchiveTimeFor(details.getLastModified()));
|
||||
archiveEntry.setUnixMode(UnixStat.DIR_FLAG | details.getMode());
|
||||
zipOutStr.putNextEntry(archiveEntry);
|
||||
zipOutStr.closeEntry();
|
||||
}
|
||||
|
||||
private boolean visitFile(FileCopyDetails details) throws IOException {
|
||||
if (details.getPath().endsWith(".jar")) {
|
||||
return processArchive(details);
|
||||
} else if (details.getPath().endsWith(".class")) {
|
||||
processClass(details);
|
||||
} else if (details.getPath().equals("fabric.mod.json")) {
|
||||
processFMJ(details);
|
||||
return true;
|
||||
} else {
|
||||
ZipEntry archiveEntry = new ZipEntry(details.getRelativePath().getPathString());
|
||||
archiveEntry.setTime(getArchiveTimeFor(details.getLastModified()));
|
||||
archiveEntry.setUnixMode(UnixStat.FILE_FLAG | details.getMode());
|
||||
zipOutStr.putNextEntry(archiveEntry);
|
||||
details.copyTo(zipOutStr);
|
||||
zipOutStr.closeEntry();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean processArchive(FileCopyDetails details) throws IOException {
|
||||
try (ZipFile archive = new ZipFile(details.getFile())) {
|
||||
AtomicBoolean fmjFound = new AtomicBoolean(false);
|
||||
EnumerationSpliterator.stream(archive.getEntries())
|
||||
.map(RelativeArchivePath::new)
|
||||
.map(ArchiveFileTreeElement::new)
|
||||
.filter(it -> it.getRelativePath().isFile())
|
||||
.forEach(archiveElement -> {
|
||||
fmjFound.compareAndSet(false, visitArchiveFile(archiveElement, archive));
|
||||
});
|
||||
return fmjFound.get();
|
||||
}
|
||||
}
|
||||
|
||||
private void visitArchiveDirectory(RelativeArchivePath archiveDir) throws IOException {
|
||||
zipOutStr.putNextEntry(archiveDir.entry);
|
||||
zipOutStr.closeEntry();
|
||||
}
|
||||
|
||||
private boolean visitArchiveFile(ArchiveFileTreeElement archiveFile, ZipFile archive) {
|
||||
RelativeArchivePath archiveFilePath = archiveFile.getRelativePath();
|
||||
try {
|
||||
if (archiveFile.isClassFile()) {
|
||||
processClass(archiveFilePath, archive);
|
||||
} else if (archiveFilePath.getPathString().equals("fabric.mod.json")) {
|
||||
processFMJ(archiveFilePath, archive);
|
||||
return true;
|
||||
} else {
|
||||
copyArchiveEntry(archiveFilePath, archive);
|
||||
}
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
throw new GradleException("Could not read archive entry " + archiveFilePath.getPathString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void copyArchiveEntry(RelativeArchivePath archiveFile, ZipFile archive) throws IOException {
|
||||
ZipEntry entry = new ZipEntry(archiveFile.entry.getName());
|
||||
entry.setTime(getArchiveTimeFor(archiveFile.entry.getTime()));
|
||||
RelativeArchivePath path = new RelativeArchivePath(entry);
|
||||
addParentDirectories(path);
|
||||
zipOutStr.putNextEntry(path.entry);
|
||||
try (InputStream is = archive.getInputStream(archiveFile.entry)) {
|
||||
byte[] buffer = new byte[1024];
|
||||
int n;
|
||||
while (-1 != (n = is.read(buffer))) {
|
||||
zipOutStr.write(buffer, 0, n);
|
||||
}
|
||||
}
|
||||
zipOutStr.closeEntry();
|
||||
}
|
||||
|
||||
private void addParentDirectories(RelativeArchivePath file) throws IOException {
|
||||
if (file != null) {
|
||||
addParentDirectories(file.getParent());
|
||||
if (!file.isFile()) {
|
||||
visitArchiveDirectory(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processClass(RelativeArchivePath file, ZipFile archive) throws IOException {
|
||||
ZipEntry zipEntry = new ZipEntry(file.getPathString());
|
||||
addParentDirectories(new RelativeArchivePath(zipEntry));
|
||||
processClass(archive.getInputStream(file.entry), file.getPathString(), file.entry.getTime());
|
||||
}
|
||||
|
||||
private void processClass(FileCopyDetails details) throws IOException {
|
||||
try (InputStream is = new BufferedInputStream(new FileInputStream(details.getFile()))) {
|
||||
processClass(is, details.getPath(), details.getLastModified());
|
||||
}
|
||||
}
|
||||
|
||||
private void processClass(InputStream classInputStream, String path, long lastModified) throws IOException {
|
||||
final ClassReader reader = new ClassReader(classInputStream);
|
||||
final ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
|
||||
reader.accept(new CheckClassAdapter(
|
||||
new ConfigInjectClassTransformer(modId, writer, knownConfigClasses)
|
||||
), ClassReader.EXPAND_FRAMES);
|
||||
ZipEntry archiveEntry = new ZipEntry(path);
|
||||
archiveEntry.setTime(getArchiveTimeFor(lastModified));
|
||||
zipOutStr.putNextEntry(archiveEntry);
|
||||
zipOutStr.write(writer.toByteArray());
|
||||
zipOutStr.closeEntry();
|
||||
}
|
||||
|
||||
private void processFMJ(RelativeArchivePath file, ZipFile archive) throws IOException {
|
||||
ZipEntry zipEntry = new ZipEntry(file.getPathString());
|
||||
addParentDirectories(new RelativeArchivePath(zipEntry));
|
||||
processFMJ(archive.getInputStream(file.entry), file.getPathString(), file.entry.getTime());
|
||||
}
|
||||
|
||||
private void processFMJ(FileCopyDetails details) throws IOException {
|
||||
try (InputStream is = new BufferedInputStream(new FileInputStream(details.getFile()))) {
|
||||
processFMJ(is, details.getPath(), details.getLastModified());
|
||||
}
|
||||
}
|
||||
|
||||
private void processFMJ(InputStream fmj, String path, long lastModified) throws IOException {
|
||||
ZipEntry archiveEntry = new ZipEntry(path);
|
||||
archiveEntry.setTime(getArchiveTimeFor(lastModified));
|
||||
zipOutStr.putNextEntry(archiveEntry);
|
||||
// Leave this closeable open, as everything else will break the writer
|
||||
|
||||
try (JsonReader reader = new JsonReader(new InputStreamReader(fmj));
|
||||
JsonWriter writer = new JsonWriter(new OutputStreamWriter(new DelegatingUncloseableOutputStream(zipOutStr)))) {
|
||||
FabricModJsonTransformer.transform(reader, writer, knownConfigClasses);
|
||||
}
|
||||
zipOutStr.closeEntry();
|
||||
}
|
||||
|
||||
private long getArchiveTimeFor(long timestamp) {
|
||||
return preserveFileTimestamps ? timestamp : CONSTANT_TIME_FOR_ZIP_ENTRIES;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package io.gitlab.jfronny.libjf.config.plugin.asm;
|
||||
|
||||
import org.gradle.api.GradleException;
|
||||
import org.objectweb.asm.AnnotationVisitor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.objectweb.asm.Opcodes.*;
|
||||
|
||||
public class AnnotationMetaGatheringVisitor extends AnnotationVisitor {
|
||||
private final List<String> referencedConfigs;
|
||||
|
||||
public AnnotationMetaGatheringVisitor(AnnotationVisitor annotationVisitor, List<String> referencedConfigs) {
|
||||
super(ASM9, annotationVisitor);
|
||||
this.referencedConfigs = referencedConfigs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitArray(String name) {
|
||||
return switch (name) {
|
||||
case "referencedConfigs" -> new ArrayVisitor<>(super.visitArray(name), referencedConfigs);
|
||||
default -> throw new GradleException("Unknown field in category or JfConfig annotation: " + name);
|
||||
};
|
||||
}
|
||||
|
||||
private static class ArrayVisitor<T> extends AnnotationVisitor {
|
||||
private final List<T> target;
|
||||
|
||||
protected ArrayVisitor(AnnotationVisitor annotationVisitor, List<T> target) {
|
||||
super(ASM9, annotationVisitor);
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(String name, Object value) {
|
||||
super.visit(name, value);
|
||||
target.add((T) value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
package io.gitlab.jfronny.libjf.config.plugin.asm;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.Category;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.JfConfig;
|
||||
import io.gitlab.jfronny.libjf.config.plugin.util.ClInitInjectVisitor;
|
||||
import io.gitlab.jfronny.libjf.config.plugin.util.GeneratorAdapter2;
|
||||
import io.gitlab.jfronny.libjf.config.plugin.asm.value.DiscoveredValue;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.objectweb.asm.*;
|
||||
import org.objectweb.asm.commons.GeneratorAdapter;
|
||||
import org.objectweb.asm.commons.Method;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static io.gitlab.jfronny.libjf.config.plugin.asm.value.KnownTypes.*;
|
||||
import static org.objectweb.asm.Opcodes.*;
|
||||
import static org.objectweb.asm.Type.*;
|
||||
|
||||
public class ConfigInjectClassTransformer extends ClassVisitor {
|
||||
// Field names
|
||||
public static final String PREFIX = "libjf$config$";
|
||||
public static final String INIT_METHOD = PREFIX + "clinit";
|
||||
public static final String REGISTER_METHOD = PREFIX + "register";
|
||||
public static final String BUILDER_ROOT = PREFIX + "root";
|
||||
public static final String CLINIT = "<clinit>";
|
||||
// Transformation metadata
|
||||
public final String modId;
|
||||
public Type current;
|
||||
private final Set<Type> knownConfigClasses;
|
||||
// Transformer state
|
||||
private final List<String> categories = new LinkedList<>();
|
||||
private final List<String> referencedConfigs = new LinkedList<>();
|
||||
private final List<String> presets = new LinkedList<>();
|
||||
private final List<String> verifiers = new LinkedList<>();
|
||||
private final List<DiscoveredValue> values = new LinkedList<>();
|
||||
private boolean initFound = false;
|
||||
private TransformerMode mode = TransformerMode.OTHER;
|
||||
|
||||
public ConfigInjectClassTransformer(String modId, ClassVisitor cw, Set<Type> knownConfigClasses) {
|
||||
super(ASM9, cw);
|
||||
this.modId = modId;
|
||||
this.knownConfigClasses = knownConfigClasses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
super.visit(version, access, name, signature, superName, interfaces);
|
||||
this.current = Type.getType('L' + name + ';');
|
||||
mode = TransformerMode.OTHER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
|
||||
if (descriptor.equals(Type.getDescriptor(Category.class)) && mode == TransformerMode.OTHER) {
|
||||
mode = TransformerMode.CONFIG_CATEGORY;
|
||||
return new AnnotationMetaGatheringVisitor(super.visitAnnotation(descriptor, visible), referencedConfigs);
|
||||
}
|
||||
if (descriptor.equals(Type.getDescriptor(JfConfig.class)) && mode == TransformerMode.OTHER) {
|
||||
mode = TransformerMode.CONFIG_ROOT;
|
||||
knownConfigClasses.add(current);
|
||||
return new AnnotationMetaGatheringVisitor(super.visitAnnotation(descriptor, visible), referencedConfigs);
|
||||
}
|
||||
return super.visitAnnotation(descriptor, visible);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitNestMember(String nestMember) {
|
||||
if (mode != TransformerMode.OTHER) {
|
||||
categories.add(nestMember);
|
||||
}
|
||||
super.visitNestMember(nestMember);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||
if (mode == TransformerMode.CONFIG_ROOT) {
|
||||
if ("<clinit>".equals(name)) {
|
||||
initFound = true;
|
||||
return new ClInitInjectVisitor(
|
||||
super.visitMethod(access, name, descriptor, signature, exceptions),
|
||||
current.getInternalName(),
|
||||
INIT_METHOD,
|
||||
"()V"
|
||||
);
|
||||
}
|
||||
}
|
||||
if (mode != TransformerMode.OTHER) {
|
||||
if (name.startsWith(PREFIX)) {
|
||||
throw new GradleException("This class declares methods generated by this plugin manually. Do not transform classes twice!");
|
||||
}
|
||||
}
|
||||
if ((access & ACC_STATIC) == ACC_STATIC) {
|
||||
// Possibly add verifier or preset
|
||||
return new MethodMetaGatheringVisitor(
|
||||
super.visitMethod(access, name, descriptor, signature, exceptions),
|
||||
name, presets, verifiers);
|
||||
}
|
||||
return super.visitMethod(access, name, descriptor, signature, exceptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
|
||||
if ((access & ACC_STATIC) == ACC_STATIC) {
|
||||
return new FieldMetaGatheringVisitor(super.visitField(access, name, descriptor, signature, value), name, values, Type.getType(descriptor));
|
||||
}
|
||||
return super.visitField(access, name, descriptor, signature, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
if (mode == TransformerMode.CONFIG_ROOT) {
|
||||
if (!initFound) {
|
||||
// Generate <clinit> if missing
|
||||
GeneratorAdapter2 m = method(ACC_PRIVATE | ACC_STATIC, CLINIT, "()V", null, null);
|
||||
m.invokeStatic(current, new Method(INIT_METHOD, "()V"));
|
||||
m.returnValue();
|
||||
m.endMethod();
|
||||
}
|
||||
// Generate main method
|
||||
{
|
||||
GeneratorAdapter2 m = method(ACC_PRIVATE | ACC_STATIC, INIT_METHOD, "()V", null, null);
|
||||
// Migrate paths
|
||||
m.invokeIStatic(CONFIG_HOLDER_TYPE, new Method("getInstance", CONFIG_HOLDER_TYPE, new Type[0]));
|
||||
m.push(modId);
|
||||
m.invokeInterface(CONFIG_HOLDER_TYPE, new Method("migrateFiles", "(Ljava/lang/String;)V"));
|
||||
// Invoke DSL
|
||||
m.push(modId);
|
||||
m.invokeIStatic(DSL_TYPE, new Method("create", DSL_DEFAULTED_TYPE, new Type[]{STRING_TYPE}));
|
||||
m.lambda("apply",
|
||||
getMethodDescriptor(CONFIG_BUILDER_FUNCTION_TYPE),
|
||||
getMethodType(CONFIG_BUILDER_TYPE, CONFIG_BUILDER_TYPE),
|
||||
current.getInternalName(),
|
||||
BUILDER_ROOT);
|
||||
|
||||
m.invokeInterface(DSL_DEFAULTED_TYPE, new Method("config", CONFIG_INSTANCE_TYPE, new Type[]{CONFIG_BUILDER_FUNCTION_TYPE}));
|
||||
m.pop();
|
||||
m.returnValue();
|
||||
m.endMethod();
|
||||
}
|
||||
// Generate register method for impl
|
||||
// This method does nothing but is needed to properly implement the interface
|
||||
// This also ensures the static initializer is called
|
||||
{
|
||||
GeneratorAdapter2 m = method(ACC_PUBLIC | ACC_STATIC, REGISTER_METHOD, getMethodDescriptor(VOID_TYPE, DSL_DEFAULTED_TYPE), null, null);
|
||||
m.returnValue();
|
||||
m.endMethod();
|
||||
}
|
||||
}
|
||||
if (mode != TransformerMode.OTHER) {
|
||||
boolean root = mode == TransformerMode.CONFIG_ROOT;
|
||||
Type builderType = root ? CONFIG_BUILDER_TYPE : CATEGORY_BUILDER_TYPE;
|
||||
GeneratorAdapter2 m = method(ACC_PRIVATE | ACC_STATIC, BUILDER_ROOT, getMethodDescriptor(builderType, builderType), null, null);
|
||||
m.loadArg(0);
|
||||
for (String name : referencedConfigs) {
|
||||
m.push(name);
|
||||
dslInvoke(m, "referenceConfig", CATEGORY_BUILDER_TYPE, STRING_TYPE);
|
||||
}
|
||||
for (String name : categories) {
|
||||
m.push(camelCase(name.substring(current.getInternalName().length())));
|
||||
m.lambda("apply",
|
||||
getMethodDescriptor(CATEGORY_BUILDER_FUNCTION_TYPE),
|
||||
getMethodType(CATEGORY_BUILDER_TYPE, CATEGORY_BUILDER_TYPE),
|
||||
name,
|
||||
BUILDER_ROOT);
|
||||
dslInvoke(m, "category", CATEGORY_BUILDER_TYPE, STRING_TYPE, CATEGORY_BUILDER_FUNCTION_TYPE);
|
||||
}
|
||||
for (String verifier : verifiers) {
|
||||
m.runnable(current.getInternalName(), verifier);
|
||||
dslInvoke(m, "addVerifier", CATEGORY_BUILDER_TYPE, RUNNABLE_TYPE);
|
||||
}
|
||||
for (String preset : presets) {
|
||||
m.push(preset);
|
||||
m.runnable(current.getInternalName(), preset);
|
||||
dslInvoke(m, "addPreset", CATEGORY_BUILDER_TYPE, STRING_TYPE, RUNNABLE_TYPE);
|
||||
}
|
||||
for (DiscoveredValue value : values) {
|
||||
value.generateRegistration(m, this);
|
||||
}
|
||||
m.returnValue();
|
||||
m.endMethod();
|
||||
for (DiscoveredValue value : values) {
|
||||
value.generateLamdaMethods(this);
|
||||
}
|
||||
}
|
||||
super.visitEnd();
|
||||
}
|
||||
|
||||
public void dslInvoke(GeneratorAdapter m, String name, Type returnType, Type... arguments) {
|
||||
boolean root = mode == TransformerMode.CONFIG_ROOT;
|
||||
Type builderType = root ? CONFIG_BUILDER_TYPE : CATEGORY_BUILDER_TYPE;
|
||||
m.invokeInterface(builderType, new Method(name, returnType, arguments));
|
||||
if (root) m.checkCast(CONFIG_BUILDER_TYPE);
|
||||
}
|
||||
|
||||
private String camelCase(String s) {
|
||||
return Character.toLowerCase(s.charAt(0)) + s.substring(1);
|
||||
}
|
||||
|
||||
public GeneratorAdapter2 method(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||
//TODO re-enable to hide generated code
|
||||
//access |= ACC_SYNTHETIC;
|
||||
return new GeneratorAdapter2(super.visitMethod(access, name, descriptor, signature, exceptions), access, name, descriptor);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package io.gitlab.jfronny.libjf.config.plugin.asm;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.Entry;
|
||||
import io.gitlab.jfronny.libjf.config.plugin.asm.value.DiscoveredValue;
|
||||
import org.objectweb.asm.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.objectweb.asm.Opcodes.*;
|
||||
|
||||
public class FieldMetaGatheringVisitor extends FieldVisitor {
|
||||
private final String name;
|
||||
private final List<DiscoveredValue> values;
|
||||
private final Type type;
|
||||
|
||||
protected FieldMetaGatheringVisitor(FieldVisitor fieldVisitor, String name, List<DiscoveredValue> values, Type type) {
|
||||
super(ASM9, fieldVisitor);
|
||||
this.name = name;
|
||||
this.values = values;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
|
||||
if (descriptor.equals(Type.getDescriptor(Entry.class))) {
|
||||
return new MetaGatheringAnnotationVisitor(super.visitAnnotation(descriptor, visible));
|
||||
}
|
||||
return super.visitAnnotation(descriptor, visible);
|
||||
}
|
||||
|
||||
public class MetaGatheringAnnotationVisitor extends AnnotationVisitor {
|
||||
private double min = Double.NEGATIVE_INFINITY;
|
||||
private double max = Double.POSITIVE_INFINITY;
|
||||
protected MetaGatheringAnnotationVisitor(AnnotationVisitor annotationVisitor) {
|
||||
super(ASM9, annotationVisitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(String name, Object value) {
|
||||
super.visit(name, value);
|
||||
switch (name) {
|
||||
case "min" -> {
|
||||
min = (Double)value;
|
||||
}
|
||||
case "max" -> {
|
||||
max = (Double)value;
|
||||
}
|
||||
case "width" -> {}
|
||||
default -> throw new IllegalArgumentException("Unexpected name in @Entry annotation: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
super.visitEnd();
|
||||
values.add(new DiscoveredValue(name, min, max, type));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package io.gitlab.jfronny.libjf.config.plugin.asm;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.Preset;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.Verifier;
|
||||
import org.objectweb.asm.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.objectweb.asm.Opcodes.*;
|
||||
|
||||
public class MethodMetaGatheringVisitor extends MethodVisitor {
|
||||
private final String name;
|
||||
private final List<String> presets;
|
||||
private final List<String> verifiers;
|
||||
protected MethodMetaGatheringVisitor(MethodVisitor methodVisitor, String name, List<String> presets, List<String> verifiers) {
|
||||
super(ASM9, methodVisitor);
|
||||
this.name = name;
|
||||
this.presets = presets;
|
||||
this.verifiers = verifiers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
|
||||
if (descriptor.equals(Type.getDescriptor(Preset.class))) {
|
||||
presets.add(name);
|
||||
}
|
||||
if (descriptor.equals(Type.getDescriptor(Verifier.class))) {
|
||||
verifiers.add(name);
|
||||
}
|
||||
return super.visitAnnotation(descriptor, visible);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package io.gitlab.jfronny.libjf.config.plugin.asm;
|
||||
|
||||
public enum TransformerMode {
|
||||
CONFIG_ROOT, CONFIG_CATEGORY, OTHER
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package io.gitlab.jfronny.libjf.config.plugin.asm.value;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.plugin.asm.ConfigInjectClassTransformer;
|
||||
import io.gitlab.jfronny.libjf.config.plugin.util.GeneratorAdapter2;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.commons.Method;
|
||||
|
||||
import static io.gitlab.jfronny.libjf.config.plugin.asm.value.KnownTypes.*;
|
||||
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
|
||||
import static org.objectweb.asm.Opcodes.ACC_STATIC;
|
||||
import static org.objectweb.asm.Type.*;
|
||||
|
||||
public class DiscoveredValue {
|
||||
private static final String PREFIX = ConfigInjectClassTransformer.PREFIX;
|
||||
|
||||
public final String name;
|
||||
public final double min;
|
||||
public final double max;
|
||||
public final Type aType;
|
||||
public final KnownType type;
|
||||
|
||||
public DiscoveredValue(String name, double min, double max, Type type) {
|
||||
this.name = name;
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.aType = type;
|
||||
this.type = KnownType.of(type);
|
||||
}
|
||||
|
||||
public void generateRegistration(GeneratorAdapter2 m, ConfigInjectClassTransformer t) {
|
||||
if (type != KnownType.OBJECT) {
|
||||
m.push(name);
|
||||
m.getStatic(t.current, name, aType);
|
||||
m.unbox(type.unboxed); // Unbox (as parameter is unboxed) or leave as is (if target is unboxed)
|
||||
}
|
||||
switch (type) {
|
||||
case INT, LONG, FLOAT, DOUBLE -> {
|
||||
m.push(min);
|
||||
m.push(max);
|
||||
m.supplier(t.current.getInternalName(), gName(), gDesc());
|
||||
m.consumer(t.current.getInternalName(), sName(), sDesc());
|
||||
t.dslInvoke(m, "value", CATEGORY_BUILDER_TYPE, STRING_TYPE, type.unboxed, DOUBLE_TYPE, DOUBLE_TYPE, SUPPLIER_TYPE, CONSUMER_TYPE);
|
||||
}
|
||||
case BOOLEAN, STRING -> {
|
||||
m.supplier(t.current.getInternalName(), gName(), gDesc());
|
||||
m.consumer(t.current.getInternalName(), sName(), sDesc());
|
||||
t.dslInvoke(m, "value", CATEGORY_BUILDER_TYPE, STRING_TYPE, type.unboxed, SUPPLIER_TYPE, CONSUMER_TYPE);
|
||||
}
|
||||
case OBJECT -> {
|
||||
System.err.println("WARNING: Attempted to use unsupported type in config. The entry \"" + name + "\" will fall back to reflective runtime access!");
|
||||
m.push(t.current);
|
||||
m.push(name);
|
||||
m.invokeIStatic(ENTRY_INFO_TYPE, new Method("ofField", ENTRY_INFO_TYPE, new Type[]{CLASS_TYPE, STRING_TYPE}));
|
||||
m.invokeInterface(CATEGORY_BUILDER_TYPE, new Method("value", CATEGORY_BUILDER_TYPE, new Type[]{ENTRY_INFO_TYPE}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void generateLamdaMethods(ConfigInjectClassTransformer t) {
|
||||
GeneratorAdapter2 m = t.method(ACC_PRIVATE | ACC_STATIC, gName(), gDesc().getInternalName(), null, null);
|
||||
m.getStatic(t.current, name, aType);
|
||||
m.box(aType); // Box target or leave as is (if target is boxed)
|
||||
m.returnValue();
|
||||
m.endMethod();
|
||||
|
||||
m = t.method(ACC_PRIVATE | ACC_STATIC, sName(), sDesc().getInternalName(), null, null);
|
||||
m.loadArg(0);
|
||||
m.unbox(aType); // Unbox to target type or leave as is (if target is boxed)
|
||||
m.putStatic(t.current, name, aType);
|
||||
m.returnValue();
|
||||
m.endMethod();
|
||||
}
|
||||
|
||||
private String gName() {
|
||||
return PREFIX + "get$" + name;
|
||||
}
|
||||
|
||||
private Type gDesc() {
|
||||
return getMethodType(type.boxed);
|
||||
}
|
||||
|
||||
private String sName() {
|
||||
return PREFIX + "set$" + name;
|
||||
}
|
||||
|
||||
private Type sDesc() {
|
||||
return getMethodType(VOID_TYPE, type.boxed);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package io.gitlab.jfronny.libjf.config.plugin.asm.value;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static io.gitlab.jfronny.libjf.config.plugin.asm.value.KnownTypes.*;
|
||||
import static org.objectweb.asm.Type.*;
|
||||
|
||||
public enum KnownType {
|
||||
INT(INT_BOX_TYPE, INT_TYPE),
|
||||
LONG(LONG_BOX_TYPE, LONG_TYPE),
|
||||
FLOAT(FLOAT_BOX_TYPE, FLOAT_TYPE),
|
||||
DOUBLE(DOUBLE_BOX_TYPE, DOUBLE_TYPE),
|
||||
STRING(STRING_TYPE, STRING_TYPE),
|
||||
BOOLEAN(BOOLEAN_BOX_TYPE, BOOLEAN_TYPE),
|
||||
OBJECT(OBJECT_TYPE, OBJECT_TYPE);
|
||||
|
||||
public final Type boxed;
|
||||
public final Type unboxed;
|
||||
private static final Map<Type, KnownType> t2t = new HashMap<>();
|
||||
|
||||
KnownType(Type boxed, Type unboxed) {
|
||||
this.boxed = boxed;
|
||||
this.unboxed = unboxed;
|
||||
}
|
||||
|
||||
static {
|
||||
for (KnownType value : values()) {
|
||||
if (value != OBJECT) {
|
||||
t2t.put(value.boxed, value);
|
||||
t2t.put(value.unboxed, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static KnownType of(Type type) {
|
||||
return t2t.getOrDefault(type, OBJECT);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package io.gitlab.jfronny.libjf.config.plugin.asm.value;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.*;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.dsl.*;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class KnownTypes {
|
||||
// DSL types
|
||||
public static final Type CONFIG_BUILDER_TYPE = Type.getType(ConfigBuilder.class);
|
||||
public static final Type CONFIG_BUILDER_FUNCTION_TYPE = Type.getType(ConfigBuilder.ConfigBuilderFunction.class);
|
||||
public static final Type CATEGORY_BUILDER_TYPE = Type.getType(CategoryBuilder.class);
|
||||
public static final Type CATEGORY_BUILDER_FUNCTION_TYPE = Type.getType(CategoryBuilder.CategoryBuilderFunction.class);
|
||||
public static final Type DSL_TYPE = Type.getType(DSL.class);
|
||||
public static final Type DSL_DEFAULTED_TYPE = Type.getType(DSL.Defaulted.class);
|
||||
public static final Type CONFIG_HOLDER_TYPE = Type.getType(ConfigHolder.class);
|
||||
public static final Type CONFIG_INSTANCE_TYPE = Type.getType(ConfigInstance.class);
|
||||
public static final Type ENTRY_INFO_TYPE = Type.getType(EntryInfo.class);
|
||||
// Boxes
|
||||
public static final Type INT_BOX_TYPE = Type.getType(Integer.class);
|
||||
public static final Type LONG_BOX_TYPE = Type.getType(Long.class);
|
||||
public static final Type FLOAT_BOX_TYPE = Type.getType(Float.class);
|
||||
public static final Type DOUBLE_BOX_TYPE = Type.getType(Double.class);
|
||||
public static final Type BOOLEAN_BOX_TYPE = Type.getType(Boolean.class);
|
||||
public static final Type CHARACTER_BOX_TYPE = Type.getType(Character.class);
|
||||
public static final Type SHORT_BOX_TYPE = Type.getType(Short.class);
|
||||
public static final Type BYTE_BOX_TYPE = Type.getType(Byte.class);
|
||||
// Additional
|
||||
public static final Type STRING_TYPE = Type.getType(String.class);
|
||||
public static final Type OBJECT_TYPE = Type.getType(Object.class);
|
||||
public static final Type RUNNABLE_TYPE = Type.getType(Runnable.class);
|
||||
public static final Type SUPPLIER_TYPE = Type.getType(Supplier.class);
|
||||
public static final Type CONSUMER_TYPE = Type.getType(Consumer.class);
|
||||
public static final Type NUMBER_TYPE = Type.getType(Number.class);
|
||||
public static final Type CLASS_TYPE = Type.getType(Class.class);
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package io.gitlab.jfronny.libjf.config.plugin.fmj;
|
||||
|
||||
import io.gitlab.jfronny.gson.*;
|
||||
import io.gitlab.jfronny.gson.stream.JsonReader;
|
||||
import io.gitlab.jfronny.gson.stream.JsonWriter;
|
||||
import io.gitlab.jfronny.libjf.config.impl.ConfigHolderImpl;
|
||||
import io.gitlab.jfronny.libjf.config.plugin.asm.ConfigInjectClassTransformer;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class FabricModJsonTransformer {
|
||||
private static final Gson INPUT_GSON = new GsonBuilder().setLenient().create();
|
||||
private static final Gson OUTPUT_GSON = new GsonBuilder().create();
|
||||
private static final String ENTRYPOINTS = "entrypoints";
|
||||
private static final String LIBJF_CONFIG = ConfigHolderImpl.MODULE_ID;
|
||||
public static void transform(JsonReader reader, JsonWriter writer, Set<Type> configClasses) {
|
||||
JsonObject fmj = INPUT_GSON.<JsonElement>fromJson(reader, JsonElement.class).getAsJsonObject();
|
||||
if (!fmj.has(ENTRYPOINTS)) fmj.add(ENTRYPOINTS, new JsonObject());
|
||||
JsonObject entrypoints = fmj.get(ENTRYPOINTS).getAsJsonObject();
|
||||
if (!entrypoints.has(LIBJF_CONFIG)) entrypoints.add(LIBJF_CONFIG, new JsonArray());
|
||||
JsonArray libjfConfig = entrypoints.getAsJsonArray(LIBJF_CONFIG).getAsJsonArray();
|
||||
for (Type klazz : configClasses) {
|
||||
// Remove references to class
|
||||
Iterator<JsonElement> each = libjfConfig.iterator();
|
||||
while(each.hasNext()) {
|
||||
JsonElement element = each.next();
|
||||
if (element.isJsonPrimitive() && element.getAsString().equals(klazz.getClassName())) {
|
||||
each.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Add reference to init method
|
||||
libjfConfig.add(klazz.getClassName() + "::" + ConfigInjectClassTransformer.REGISTER_METHOD);
|
||||
}
|
||||
OUTPUT_GSON.toJson(fmj, writer);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
package io.gitlab.jfronny.libjf.config.plugin.util;
|
||||
|
||||
import org.gradle.api.file.FileTreeElement;
|
||||
import org.gradle.api.file.RelativePath;
|
||||
import org.gradle.api.internal.file.DefaultFileTreeElement;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class ArchiveFileTreeElement implements FileTreeElement {
|
||||
private final RelativeArchivePath archivePath;
|
||||
|
||||
public ArchiveFileTreeElement(RelativeArchivePath archivePath) {
|
||||
this.archivePath = archivePath;
|
||||
}
|
||||
|
||||
public boolean isClassFile() {
|
||||
return archivePath.isClassFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFile() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory() {
|
||||
return archivePath.entry.isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastModified() {
|
||||
return archivePath.entry.getLastModifiedDate().getTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize() {
|
||||
return archivePath.entry.getSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream open() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyTo(OutputStream outputStream) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean copyTo(File file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return archivePath.getPathString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return archivePath.getLastName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RelativeArchivePath getRelativePath() {
|
||||
return archivePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMode() {
|
||||
return archivePath.entry.getUnixMode();
|
||||
}
|
||||
|
||||
public FileTreeElement asFileTreeElement() {
|
||||
return new DefaultFileTreeElement(null, new RelativePath(!isDirectory(), archivePath.getSegments()), null, null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package io.gitlab.jfronny.libjf.config.plugin.util;
|
||||
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
|
||||
import static org.objectweb.asm.Opcodes.*;
|
||||
|
||||
public class ClInitInjectVisitor extends MethodVisitor {
|
||||
private final String owner;
|
||||
private final String name;
|
||||
private final String descriptor;
|
||||
|
||||
public ClInitInjectVisitor(MethodVisitor mw, String owner, String name, String descriptor) {
|
||||
super(ASM9, mw);
|
||||
this.owner = owner;
|
||||
this.name = name;
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInsn(int opcode) {
|
||||
if (opcode == RETURN) {
|
||||
super.visitMethodInsn(INVOKESTATIC, owner, name, descriptor, false);
|
||||
}
|
||||
super.visitInsn(opcode);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package io.gitlab.jfronny.libjf.config.plugin.util;
|
||||
|
||||
import org.apache.tools.zip.Zip64Mode;
|
||||
import org.apache.tools.zip.ZipOutputStream;
|
||||
import org.gradle.api.UncheckedIOException;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class DefaultZipCompressor implements ZipCompressor {
|
||||
private final int entryCompressionMethod;
|
||||
private final Zip64Mode zip64Mode;
|
||||
|
||||
public DefaultZipCompressor(boolean allowZip64Mode, int entryCompressionMethod) {
|
||||
this.entryCompressionMethod = entryCompressionMethod;
|
||||
this.zip64Mode = allowZip64Mode ? Zip64Mode.AsNeeded : Zip64Mode.Never;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZipOutputStream createArchiveOutputStream(File destination) {
|
||||
try {
|
||||
ZipOutputStream zipOutputStream = new ZipOutputStream(destination);
|
||||
zipOutputStream.setUseZip64(zip64Mode);
|
||||
zipOutputStream.setMethod(entryCompressionMethod);
|
||||
return zipOutputStream;
|
||||
} catch (Exception e) {
|
||||
String message = String.format("Unable to create ZIP output stream for file %s.", destination);
|
||||
throw new UncheckedIOException(message, e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package io.gitlab.jfronny.libjf.config.plugin.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class DelegatingUncloseableOutputStream extends OutputStream {
|
||||
private final OutputStream delegate;
|
||||
|
||||
public DelegatingUncloseableOutputStream(OutputStream delegate) {
|
||||
super();
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int i) throws IOException {
|
||||
delegate.write(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b) throws IOException {
|
||||
delegate.write(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
delegate.write(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package io.gitlab.jfronny.libjf.config.plugin.util;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
public class EnumerationSpliterator<T> extends Spliterators.AbstractSpliterator<T> {
|
||||
private final Enumeration<T> enumeration;
|
||||
|
||||
public EnumerationSpliterator(long est, int additionalCharacteristics, Enumeration<T> enumeration) {
|
||||
super(est, additionalCharacteristics);
|
||||
this.enumeration = enumeration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryAdvance(Consumer<? super T> action) {
|
||||
if (enumeration.hasMoreElements()) {
|
||||
action.accept(enumeration.nextElement());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEachRemaining(Consumer<? super T> action) {
|
||||
while (enumeration.hasMoreElements())
|
||||
action.accept(enumeration.nextElement());
|
||||
}
|
||||
|
||||
public static <T> Stream<T> stream(Enumeration<T> enumeration) {
|
||||
EnumerationSpliterator<T> spliterator = new EnumerationSpliterator<>(Long.MAX_VALUE, Spliterator.ORDERED, enumeration);
|
||||
return StreamSupport.stream(spliterator, false);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package io.gitlab.jfronny.libjf.config.plugin.util;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.plugin.asm.value.KnownTypes;
|
||||
import org.objectweb.asm.*;
|
||||
import org.objectweb.asm.commons.GeneratorAdapter;
|
||||
import org.objectweb.asm.commons.Method;
|
||||
|
||||
import java.lang.invoke.LambdaMetafactory;
|
||||
|
||||
import static org.objectweb.asm.Opcodes.ASM9;
|
||||
import static org.objectweb.asm.Opcodes.H_INVOKESTATIC;
|
||||
|
||||
public class GeneratorAdapter2 extends GeneratorAdapter {
|
||||
private static final Handle metafactory = new Handle(
|
||||
H_INVOKESTATIC,
|
||||
Type.getInternalName(LambdaMetafactory.class),
|
||||
"metafactory",
|
||||
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
|
||||
false
|
||||
);
|
||||
|
||||
public GeneratorAdapter2(MethodVisitor methodVisitor, int access, String name, String descriptor) {
|
||||
super(ASM9, methodVisitor, access, name, descriptor);
|
||||
}
|
||||
|
||||
public void lambda(String lambdaMethodName, String lambdaType, Type lambdaDescriptor, Type implDescriptor, String implOwner, String implName) {
|
||||
invokeDynamic(lambdaMethodName,
|
||||
lambdaType,
|
||||
metafactory,
|
||||
lambdaDescriptor,
|
||||
new Handle(H_INVOKESTATIC, implOwner, implName, implDescriptor.getInternalName(), false),
|
||||
implDescriptor);
|
||||
}
|
||||
|
||||
public void lambda(String lambdaMethodName, String lambdaType, Type descriptor, String implOwner, String implName) {
|
||||
lambda(lambdaMethodName, lambdaType, descriptor, descriptor, implOwner, implName);
|
||||
}
|
||||
|
||||
public void runnable(String implOwner, String implName) {
|
||||
lambda("run",
|
||||
"()Ljava/lang/Runnable;",
|
||||
Type.getType("()V"),
|
||||
implOwner,
|
||||
implName);
|
||||
}
|
||||
|
||||
public void supplier(String implOwner, String implName, Type implDescriptor) {
|
||||
lambda("get",
|
||||
"()Ljava/util/function/Supplier;",
|
||||
Type.getType("()Ljava/lang/Object;"),
|
||||
implDescriptor,
|
||||
implOwner,
|
||||
implName);
|
||||
}
|
||||
|
||||
public void consumer(String implOwner, String implName, Type implDescriptor) {
|
||||
lambda("accept",
|
||||
"()Ljava/util/function/Consumer;",
|
||||
Type.getType("(Ljava/lang/Object;)V"),
|
||||
implDescriptor,
|
||||
implOwner,
|
||||
implName);
|
||||
}
|
||||
|
||||
public void box(Type type) {
|
||||
Type boxedType;
|
||||
switch (type.getSort()) {
|
||||
case Type.VOID:
|
||||
return;
|
||||
case Type.CHAR:
|
||||
boxedType = KnownTypes.CHARACTER_BOX_TYPE;
|
||||
break;
|
||||
case Type.BOOLEAN:
|
||||
boxedType = KnownTypes.BOOLEAN_BOX_TYPE;
|
||||
break;
|
||||
case Type.DOUBLE:
|
||||
boxedType = KnownTypes.DOUBLE_BOX_TYPE;
|
||||
break;
|
||||
case Type.FLOAT:
|
||||
boxedType = KnownTypes.FLOAT_BOX_TYPE;
|
||||
break;
|
||||
case Type.LONG:
|
||||
boxedType = KnownTypes.LONG_BOX_TYPE;
|
||||
break;
|
||||
case Type.INT:
|
||||
boxedType = KnownTypes.INT_BOX_TYPE;
|
||||
break;
|
||||
case Type.SHORT:
|
||||
boxedType = KnownTypes.SHORT_BOX_TYPE;
|
||||
break;
|
||||
case Type.BYTE:
|
||||
boxedType = KnownTypes.BYTE_BOX_TYPE;
|
||||
break;
|
||||
default:
|
||||
boxedType = null;
|
||||
break;
|
||||
}
|
||||
if (boxedType == null) {
|
||||
checkCast(type);
|
||||
} else {
|
||||
checkCast(boxedType);
|
||||
invokeStatic(boxedType, new Method("valueOf", boxedType, new Type[]{type}));
|
||||
}
|
||||
}
|
||||
|
||||
public void invokeIStatic(Type owner, Method method) {
|
||||
invokeInsn(Opcodes.INVOKESTATIC, owner, method, true);
|
||||
}
|
||||
|
||||
// Taken from GeneratorAdapter
|
||||
private void invokeInsn(final int opcode, final Type type, final Method method, final boolean isInterface) {
|
||||
String owner = type.getSort() == Type.ARRAY ? type.getDescriptor() : type.getInternalName();
|
||||
mv.visitMethodInsn(opcode, owner, method.getName(), method.getDescriptor(), isInterface);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package io.gitlab.jfronny.libjf.config.plugin.util;
|
||||
|
||||
import org.apache.tools.zip.ZipOutputStream;
|
||||
import org.gradle.api.tasks.bundling.Jar;
|
||||
import org.gradle.api.tasks.bundling.ZipEntryCompression;
|
||||
import org.gradle.util.GradleVersion;
|
||||
|
||||
public class GradleVersionUtil {
|
||||
private final GradleVersion version;
|
||||
|
||||
public GradleVersionUtil(String version) {
|
||||
this.version = GradleVersion.version(version);
|
||||
}
|
||||
|
||||
public ZipCompressor getInternalCompressor(ZipEntryCompression entryCompression, Jar jar) {
|
||||
return switch (entryCompression) {
|
||||
case DEFLATED -> new DefaultZipCompressor(jar.isZip64(), ZipOutputStream.DEFLATED);
|
||||
case STORED -> new DefaultZipCompressor(jar.isZip64(), ZipOutputStream.STORED);
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package io.gitlab.jfronny.libjf.config.plugin.util;
|
||||
|
||||
import org.apache.tools.zip.ZipEntry;
|
||||
import org.gradle.api.file.RelativePath;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class RelativeArchivePath extends RelativePath {
|
||||
public ZipEntry entry;
|
||||
|
||||
public RelativeArchivePath(ZipEntry entry) {
|
||||
super(!entry.isDirectory(), entry.getName().split("/"));
|
||||
this.entry = entry;
|
||||
}
|
||||
|
||||
public boolean isClassFile() {
|
||||
return getLastName().endsWith(".class");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RelativeArchivePath getParent() {
|
||||
List<String> segments = Arrays.asList(getSegments());
|
||||
if (segments.size() == 1) {
|
||||
return null;
|
||||
} else {
|
||||
//Parent is always a directory so add / to the end of the path
|
||||
String path = String.join("/", segments.subList(0, segments.size() - 1)) + "/";
|
||||
return new RelativeArchivePath(new ZipEntry(path));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package io.gitlab.jfronny.libjf.config.plugin.util;
|
||||
|
||||
|
||||
import org.apache.tools.zip.ZipOutputStream;
|
||||
import org.gradle.api.internal.file.archive.compression.ArchiveOutputStreamFactory;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public interface ZipCompressor extends ArchiveOutputStreamFactory {
|
||||
ZipOutputStream createArchiveOutputStream(File destination);
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
archivesBaseName = "libjf-config-core-v1"
|
||||
|
||||
dependencies {
|
||||
api project(path: ":libjf-base", configuration: "dev")
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package io.gitlab.jfronny.libjf.config.api;
|
||||
package io.gitlab.jfronny.libjf.config.api.v1;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
|
@ -8,4 +8,5 @@ import java.lang.annotation.Target;
|
|||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface Category {
|
||||
String[] referencedConfigs() default {};
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package io.gitlab.jfronny.libjf.config.api;
|
||||
package io.gitlab.jfronny.libjf.config.api.v1;
|
||||
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package io.gitlab.jfronny.libjf.config.api;
|
||||
package io.gitlab.jfronny.libjf.config.api.v1;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.impl.ConfigHolderImpl;
|
||||
|
||||
|
@ -15,4 +15,5 @@ public interface ConfigHolder {
|
|||
ConfigInstance get(Path configPath);
|
||||
boolean isRegistered(String modId);
|
||||
boolean isRegistered(Path configPath);
|
||||
void migrateFiles(String modId);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package io.gitlab.jfronny.libjf.config.api;
|
||||
package io.gitlab.jfronny.libjf.config.api.v1;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
|
@ -1,4 +1,4 @@
|
|||
package io.gitlab.jfronny.libjf.config.api;
|
||||
package io.gitlab.jfronny.libjf.config.api.v1;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
|
@ -0,0 +1,90 @@
|
|||
package io.gitlab.jfronny.libjf.config.api.v1;
|
||||
|
||||
import io.gitlab.jfronny.gson.JsonElement;
|
||||
import io.gitlab.jfronny.gson.stream.JsonWriter;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.type.Type;
|
||||
import io.gitlab.jfronny.libjf.config.impl.dsl.DslEntryInfo;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public interface EntryInfo<T> {
|
||||
static EntryInfo<?> ofField(Field field) {
|
||||
return DslEntryInfo.ofField(field);
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
static EntryInfo<?> ofField(Class<?> klazz, String name) {
|
||||
try {
|
||||
return ofField(klazz.getField(name));
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of this entry
|
||||
* @return This entry's name
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* @return Get the default value of this entry
|
||||
*/
|
||||
T getDefault();
|
||||
|
||||
/**
|
||||
* Gets the current value
|
||||
* @return The current value
|
||||
*/
|
||||
T getValue() throws IllegalAccessException;
|
||||
|
||||
/**
|
||||
* Set the current value to the parameter
|
||||
* @param value The value to use
|
||||
*/
|
||||
void setValue(T value) throws IllegalAccessException;
|
||||
|
||||
/**
|
||||
* Get the value type of this entry. Will use the class definition, not the current value
|
||||
* @return The type of this entry
|
||||
*/
|
||||
Type<T> getValueType();
|
||||
|
||||
/**
|
||||
* Ensure the current value is within expected bounds.
|
||||
*/
|
||||
void fix();
|
||||
|
||||
/**
|
||||
* Set this entry's value to that of the element
|
||||
* @param element The element to read from
|
||||
*/
|
||||
void loadFromJson(JsonElement element) throws IllegalAccessException;
|
||||
|
||||
/**
|
||||
* Write the currently cached value to the writer
|
||||
* @param writer The writer to write to
|
||||
*/
|
||||
void writeTo(JsonWriter writer, String translationPrefix) throws IOException, IllegalAccessException;
|
||||
|
||||
/**
|
||||
* @return Get the width for this entry
|
||||
*/
|
||||
int getWidth();
|
||||
|
||||
/**
|
||||
* @return Get the minimum value of this entry
|
||||
*/
|
||||
double getMinValue();
|
||||
|
||||
/**
|
||||
* @return Get the maximum value for this entry
|
||||
*/
|
||||
double getMaxValue();
|
||||
|
||||
default void reset() throws IllegalAccessException {
|
||||
setValue(getDefault());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package io.gitlab.jfronny.libjf.config.api.v1;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface JfConfig {
|
||||
String[] referencedConfigs() default {};
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package io.gitlab.jfronny.libjf.config.api.v1;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.dsl.DSL;
|
||||
|
||||
public interface JfCustomConfig {
|
||||
void register(DSL.Defaulted dsl);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package io.gitlab.jfronny.libjf.config.api;
|
||||
package io.gitlab.jfronny.libjf.config.api.v1;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
|
@ -1,4 +1,4 @@
|
|||
package io.gitlab.jfronny.libjf.config.api;
|
||||
package io.gitlab.jfronny.libjf.config.api.v1;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
|
@ -1,7 +1,9 @@
|
|||
package io.gitlab.jfronny.libjf.config.api.dsl;
|
||||
package io.gitlab.jfronny.libjf.config.api.v1.dsl;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.*;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.*;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.type.Type;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
@ -9,17 +11,21 @@ public interface CategoryBuilder<Builder extends CategoryBuilder<Builder>> {
|
|||
Builder setTranslationPrefix(String translationPrefix);
|
||||
String getTranslationPrefix();
|
||||
Builder addPreset(String id, Consumer<ConfigCategory> action);
|
||||
Builder addPreset(String id, Runnable preset);
|
||||
Builder addVerifier(Consumer<ConfigCategory> verifier);
|
||||
Builder addVerifier(Runnable verifier);
|
||||
Builder referenceConfig(String id);
|
||||
Builder referenceConfig(ConfigInstance config);
|
||||
Builder category(String id, CategoryBuilderFunction builder);
|
||||
Builder value(String id, int def, double min, double max, Supplier<Integer> get, Consumer<Integer> set);
|
||||
Builder value(String id, long def, double min, double max, Supplier<Long> get, Consumer<Long> set);
|
||||
Builder value(String id, float def, double min, double max, Supplier<Float> get, Consumer<Float> set);
|
||||
Builder value(String id, double def, double min, double max, Supplier<Double> get, Consumer<Double> set);
|
||||
Builder value(String id, String def, Supplier<String> get, Consumer<String> set);
|
||||
Builder value(String id, boolean def, Supplier<Boolean> get, Consumer<Boolean> set);
|
||||
Builder value(String id, String def, String[] options, Supplier<String> get, Consumer<String> set);
|
||||
<T extends Enum<T>> Builder value(String id, T def, Class<T> klazz, Supplier<T> get, Consumer<T> set);
|
||||
<T> Builder value(String id, T def, double min, double max, Type<T> type, int width, Supplier<T> get, Consumer<T> set);
|
||||
<T> Builder value(EntryInfo<T> entry);
|
||||
|
||||
String getId();
|
|
@ -1,6 +1,6 @@
|
|||
package io.gitlab.jfronny.libjf.config.api.dsl;
|
||||
package io.gitlab.jfronny.libjf.config.api.v1.dsl;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.ConfigInstance;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Consumer;
|
|
@ -1,7 +1,7 @@
|
|||
package io.gitlab.jfronny.libjf.config.api.dsl;
|
||||
package io.gitlab.jfronny.libjf.config.api.v1.dsl;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
|
||||
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.ConfigHolder;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.ConfigInstance;
|
||||
import io.gitlab.jfronny.libjf.config.impl.dsl.DSLImpl;
|
||||
|
||||
public interface DSL {
|
|
@ -1,10 +1,11 @@
|
|||
package io.gitlab.jfronny.libjf.config.api.type;
|
||||
package io.gitlab.jfronny.libjf.config.api.v1.type;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public sealed interface Type<T> {
|
||||
static Type<?> ofClass(java.lang.reflect.Type klazz) {
|
||||
if (klazz == int.class || klazz == Integer.class) return TInt.INSTANCE;
|
||||
else if (klazz == long.class || klazz == Long.class) return TLong.INSTANCE;
|
||||
else if (klazz == float.class || klazz == Float.class) return TFloat.INSTANCE;
|
||||
else if (klazz == double.class || klazz == Double.class) return TDouble.INSTANCE;
|
||||
else if (klazz == String.class) return TString.INSTANCE;
|
||||
|
@ -16,6 +17,9 @@ public sealed interface Type<T> {
|
|||
default boolean isInt() {
|
||||
return false;
|
||||
}
|
||||
default boolean isLong() {
|
||||
return false;
|
||||
}
|
||||
default boolean isFloat() {
|
||||
return false;
|
||||
}
|
||||
|
@ -55,6 +59,25 @@ public sealed interface Type<T> {
|
|||
}
|
||||
}
|
||||
|
||||
final class TLong implements Type<Long> {
|
||||
public static TLong INSTANCE = new TLong();
|
||||
private TLong() {}
|
||||
@Override
|
||||
public boolean isLong() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Long> asClass() {
|
||||
return Long.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Long";
|
||||
}
|
||||
}
|
||||
|
||||
final class TFloat implements Type<Float> {
|
||||
public static TFloat INSTANCE = new TFloat();
|
||||
private TFloat() {}
|
|
@ -0,0 +1,72 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl;
|
||||
|
||||
import io.gitlab.jfronny.commons.serialize.gson.api.GsonHolder;
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.Category;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.JfConfig;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.dsl.CategoryBuilder;
|
||||
import io.gitlab.jfronny.libjf.gson.FabricLoaderGsonGenerator;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.fabricmc.loader.api.metadata.CustomValue;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import static io.gitlab.jfronny.libjf.config.impl.ConfigHolderImpl.MODULE_ID;
|
||||
|
||||
public class AuxiliaryMetadata {
|
||||
public static AuxiliaryMetadata of(Category category) {
|
||||
AuxiliaryMetadata meta = new AuxiliaryMetadata();
|
||||
if (category != null) {
|
||||
meta.referencedConfigs = List.of(category.referencedConfigs());
|
||||
}
|
||||
return meta.sanitize();
|
||||
}
|
||||
|
||||
public static AuxiliaryMetadata of(JfConfig config) {
|
||||
AuxiliaryMetadata meta = new AuxiliaryMetadata();
|
||||
if (config != null) {
|
||||
meta.referencedConfigs = List.of(config.referencedConfigs());
|
||||
}
|
||||
return meta.sanitize();
|
||||
}
|
||||
|
||||
public static @Nullable AuxiliaryMetadata forMod(String modId) {
|
||||
var metaRef = new Object() {
|
||||
AuxiliaryMetadata meta = null;
|
||||
};
|
||||
FabricLoader.getInstance().getModContainer(modId).ifPresent(container -> {
|
||||
CustomValue cv = container.getMetadata().getCustomValue(MODULE_ID);
|
||||
if (cv == null) {
|
||||
cv = container.getMetadata().getCustomValue("libjf");
|
||||
if (cv != null) {
|
||||
cv = cv.getAsObject().get("config");
|
||||
}
|
||||
}
|
||||
if (cv != null) metaRef.meta = GsonHolder.getGson().fromJson(FabricLoaderGsonGenerator.toGson(cv), AuxiliaryMetadata.class);
|
||||
});
|
||||
return metaRef.meta;
|
||||
}
|
||||
|
||||
public List<String> referencedConfigs;
|
||||
|
||||
public void applyTo(CategoryBuilder<?> builder) {
|
||||
if (referencedConfigs != null) referencedConfigs.forEach(builder::referenceConfig);
|
||||
}
|
||||
|
||||
public AuxiliaryMetadata sanitize() {
|
||||
if (referencedConfigs == null) referencedConfigs = List.of();
|
||||
else referencedConfigs = List.copyOf(referencedConfigs);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AuxiliaryMetadata merge(AuxiliaryMetadata other) {
|
||||
if (other == null) return this;
|
||||
AuxiliaryMetadata meta = new AuxiliaryMetadata();
|
||||
meta.referencedConfigs = new LinkedList<>();
|
||||
meta.referencedConfigs.addAll(this.referencedConfigs);
|
||||
meta.referencedConfigs.addAll(other.referencedConfigs);
|
||||
return meta.sanitize();
|
||||
}
|
||||
}
|
|
@ -2,10 +2,13 @@ package io.gitlab.jfronny.libjf.config.impl;
|
|||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
|
||||
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.ConfigHolder;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.ConfigInstance;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
@ -16,7 +19,6 @@ public class ConfigHolderImpl implements ConfigHolder {
|
|||
private ConfigHolderImpl() {
|
||||
}
|
||||
public static final String MODULE_ID = "libjf:config";
|
||||
public static final String CUSTOM_ID = MODULE_ID + "_custom";
|
||||
private final Map<String, ConfigInstance> configs = new HashMap<>();
|
||||
private final Map<Path, ConfigInstance> configsByPath = new HashMap<>();
|
||||
|
||||
|
@ -54,4 +56,32 @@ public class ConfigHolderImpl implements ConfigHolder {
|
|||
public boolean isRegistered(Path configPath) {
|
||||
return configsByPath.containsKey(configPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void migrateFiles(String modId) {
|
||||
Path cfg = FabricLoader.getInstance().getConfigDir();
|
||||
Path target = cfg.resolve(modId + ".json5");
|
||||
if (Files.exists(target)) return;
|
||||
if (Files.exists(cfg.resolve(modId + ".json"))) {
|
||||
try {
|
||||
Files.move(cfg.resolve(modId + ".json"), target);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
FabricLoader.getInstance().getModContainer(modId).ifPresent(mod -> {
|
||||
try {
|
||||
for (String id : mod.getMetadata().getProvides()) {
|
||||
if (Files.exists(cfg.resolve(id + ".json"))) {
|
||||
Files.move(cfg.resolve(id + ".json"), target);
|
||||
return;
|
||||
}
|
||||
if (Files.exists(cfg.resolve(id + ".json5"))) {
|
||||
Files.move(cfg.resolve(id + ".json5"), target);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ package io.gitlab.jfronny.libjf.config.impl;
|
|||
|
||||
import io.gitlab.jfronny.commons.throwable.*;
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.ConfigHolder;
|
||||
import io.gitlab.jfronny.libjf.coprocess.ThreadCoProcess;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.dsl;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.*;
|
||||
import io.gitlab.jfronny.libjf.config.api.dsl.CategoryBuilder;
|
||||
import io.gitlab.jfronny.libjf.config.api.type.Type;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.*;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.dsl.CategoryBuilder;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.type.Type;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
@ -47,6 +46,11 @@ public class CategoryBuilderImpl<Builder extends CategoryBuilderImpl<Builder>> i
|
|||
return asBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder addPreset(String id, Runnable preset) {
|
||||
return addPreset(id, cfg -> preset.run());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder addVerifier(Consumer<ConfigCategory> verifier) {
|
||||
checkBuilt();
|
||||
|
@ -54,6 +58,11 @@ public class CategoryBuilderImpl<Builder extends CategoryBuilderImpl<Builder>> i
|
|||
return asBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder addVerifier(Runnable verifier) {
|
||||
return addVerifier(cfg -> verifier.run());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder referenceConfig(String id) {
|
||||
checkBuilt();
|
||||
|
@ -82,6 +91,13 @@ public class CategoryBuilderImpl<Builder extends CategoryBuilderImpl<Builder>> i
|
|||
return asBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder value(String id, long def, double min, double max, Supplier<Long> get, Consumer<Long> set) {
|
||||
checkBuilt();
|
||||
entries.add(new DslEntryInfo<>(id, def, get::get, set::accept, Type.TLong.INSTANCE, 100, min, max));
|
||||
return asBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder value(String id, float def, double min, double max, Supplier<Float> get, Consumer<Float> set) {
|
||||
checkBuilt();
|
||||
|
@ -124,6 +140,13 @@ public class CategoryBuilderImpl<Builder extends CategoryBuilderImpl<Builder>> i
|
|||
return asBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Builder value(String id, T def, double min, double max, Type<T> type, int width, Supplier<T> get, Consumer<T> set) {
|
||||
checkBuilt();
|
||||
entries.add(new DslEntryInfo<>(id, def, get::get, set::accept, type, width, min, max));
|
||||
return asBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Builder value(EntryInfo<T> entry) {
|
||||
checkBuilt();
|
|
@ -0,0 +1,133 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.dsl;
|
||||
|
||||
import io.gitlab.jfronny.commons.serialize.gson.api.GsonHolder;
|
||||
import io.gitlab.jfronny.gson.*;
|
||||
import io.gitlab.jfronny.gson.stream.JsonWriter;
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.*;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.dsl.CategoryBuilder;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.dsl.ConfigBuilder;
|
||||
import io.gitlab.jfronny.libjf.config.impl.JfConfigWatchService;
|
||||
import io.gitlab.jfronny.libjf.config.impl.entrypoint.JfConfigSafe;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ConfigBuilderImpl extends CategoryBuilderImpl<ConfigBuilderImpl> implements ConfigBuilder<ConfigBuilderImpl> {
|
||||
public DslConfigInstance built;
|
||||
public Consumer<ConfigInstance> load;
|
||||
public Consumer<ConfigInstance> write;
|
||||
public Path path;
|
||||
|
||||
public ConfigBuilderImpl(String id) {
|
||||
super(id, "");
|
||||
load = c -> {
|
||||
c.getFilePath().ifPresent(path -> {
|
||||
if (Files.exists(path)) {
|
||||
try (BufferedReader br = Files.newBufferedReader(path)) {
|
||||
JsonElement element = JsonParser.parseReader(br);
|
||||
if (element.isJsonObject()) loadFrom(element.getAsJsonObject(), c);
|
||||
else LibJf.LOGGER.error("Invalid config: Not a JSON object for " + id);
|
||||
} catch (Exception e) {
|
||||
LibJf.LOGGER.error("Could not read config for " + id, e);
|
||||
}
|
||||
}
|
||||
c.write();
|
||||
});
|
||||
};
|
||||
write = c -> {
|
||||
c.getFilePath().ifPresent(path -> JfConfigWatchService.lock(path, () -> {
|
||||
try (BufferedWriter bw = Files.newBufferedWriter(path);
|
||||
JsonWriter jw = GsonHolder.getGson().newJsonWriter(bw)) {
|
||||
writeTo(jw, c);
|
||||
} catch (Exception e) {
|
||||
LibJf.LOGGER.error("Could not write config", e);
|
||||
}
|
||||
}));
|
||||
};
|
||||
path = FabricLoader.getInstance().getConfigDir().resolve(id + ".json5");
|
||||
}
|
||||
|
||||
private static void loadFrom(JsonObject source, ConfigCategory category) {
|
||||
for (EntryInfo<?> entry : category.getEntries()) {
|
||||
if (source.has(entry.getName())) {
|
||||
try {
|
||||
entry.loadFromJson(source.get(entry.getName()));
|
||||
} catch (IllegalAccessException e) {
|
||||
LibJf.LOGGER.error("Could not set config entry value of " + entry.getName(), e);
|
||||
}
|
||||
} else LibJf.LOGGER.error("Config does not contain entry for " + entry.getName());
|
||||
}
|
||||
for (Map.Entry<String, ConfigCategory> entry : category.getCategories().entrySet()) {
|
||||
if (source.has(entry.getKey())) {
|
||||
JsonElement el = source.get(entry.getKey());
|
||||
if (el.isJsonObject()) loadFrom(el.getAsJsonObject(), entry.getValue());
|
||||
else LibJf.LOGGER.error("Config category is not a JSON object, skipping");
|
||||
} else LibJf.LOGGER.error("Config does not contain entry for subcategory " + entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeTo(JsonWriter writer, ConfigCategory category) throws IOException {
|
||||
category.fix();
|
||||
writer.beginObject();
|
||||
String val;
|
||||
for (EntryInfo<?> entry : category.getEntries()) {
|
||||
try {
|
||||
entry.writeTo(writer, category.getTranslationPrefix());
|
||||
} catch (IllegalAccessException e) {
|
||||
LibJf.LOGGER.error("Could not write entry", e);
|
||||
}
|
||||
}
|
||||
for (Map.Entry<String, ConfigCategory> entry : category.getCategories().entrySet()) {
|
||||
if ((val = JfConfigSafe.TRANSLATION_SUPPLIER.apply(category.getTranslationPrefix() + entry.getKey() + ".title")) != null)
|
||||
writer.comment(val);
|
||||
writer.name(entry.getKey());
|
||||
writeTo(writer, entry.getValue());
|
||||
}
|
||||
writer.endObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigBuilderImpl setLoadMethod(Consumer<ConfigInstance> load) {
|
||||
checkBuilt();
|
||||
this.load = load;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConfigBuilderImpl setWriteMethod(Consumer<ConfigInstance> write) {
|
||||
checkBuilt();
|
||||
this.write = write;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConfigBuilderImpl setPath(Path path) {
|
||||
checkBuilt();
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DslConfigInstance build() {
|
||||
markBuilt();
|
||||
built = new DslConfigInstance(id,
|
||||
translationPrefix,
|
||||
entries,
|
||||
presets,
|
||||
() -> referencedConfigs.stream().map(Supplier::get).toList(),
|
||||
categories.stream().collect(Collectors.toMap(CategoryBuilder::getId, b -> b.build(() -> built))),
|
||||
() -> built,
|
||||
verifiers,
|
||||
load,
|
||||
write,
|
||||
path);
|
||||
built.load();
|
||||
return built;
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.dsl;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
|
||||
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
|
||||
import io.gitlab.jfronny.libjf.config.api.dsl.ConfigBuilder;
|
||||
import io.gitlab.jfronny.libjf.config.api.dsl.DSL;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.ConfigHolder;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.ConfigInstance;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.dsl.ConfigBuilder;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.dsl.DSL;
|
||||
|
||||
public class DSLImpl implements DSL {
|
||||
@Override
|
|
@ -1,6 +1,6 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.dsl;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.*;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
|
@ -1,6 +1,6 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.dsl;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.*;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.*;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.file.Path;
|
|
@ -6,11 +6,13 @@ import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
|
|||
import io.gitlab.jfronny.gson.JsonElement;
|
||||
import io.gitlab.jfronny.gson.stream.JsonWriter;
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
import io.gitlab.jfronny.libjf.config.api.EntryInfo;
|
||||
import io.gitlab.jfronny.libjf.config.api.type.Type;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.Entry;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.EntryInfo;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.type.Type;
|
||||
import io.gitlab.jfronny.libjf.config.impl.entrypoint.JfConfigSafe;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -51,6 +53,26 @@ public class DslEntryInfo<T> implements EntryInfo<T> {
|
|||
this(name, def, get, set, type, 100, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
|
||||
}
|
||||
|
||||
public static DslEntryInfo<Object> ofField(Field field) {
|
||||
Entry entry = field.getAnnotation(Entry.class);
|
||||
Object defaultValue = null;
|
||||
try {
|
||||
defaultValue = field.get(null);
|
||||
} catch (IllegalAccessException ignored) {
|
||||
}
|
||||
//noinspection unchecked,rawtypes
|
||||
return new DslEntryInfo<Object>(
|
||||
field.getName(),
|
||||
defaultValue,
|
||||
() -> field.get(null),
|
||||
v -> field.set(null, v),
|
||||
(Type) Type.ofClass(field.getGenericType()),
|
||||
entry == null ? 100 : entry.width(),
|
||||
entry == null ? Double.NEGATIVE_INFINITY : entry.min(),
|
||||
entry == null ? Double.POSITIVE_INFINITY : entry.max()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
|
@ -119,6 +141,10 @@ public class DslEntryInfo<T> implements EntryInfo<T> {
|
|||
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber()) {
|
||||
setUnchecked(element.getAsNumber().intValue());
|
||||
}
|
||||
} else if (type.isLong()) {
|
||||
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber()) {
|
||||
setUnchecked(element.getAsNumber().longValue());
|
||||
}
|
||||
} else if (type.isDouble()) {
|
||||
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber()) {
|
||||
setUnchecked(element.getAsNumber().doubleValue());
|
|
@ -1,21 +1,27 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.entrypoint;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.JfCustomConfig;
|
||||
import io.gitlab.jfronny.libjf.config.api.dsl.DSL;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.JfCustomConfig;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.dsl.DSL;
|
||||
import io.gitlab.jfronny.libjf.config.impl.ConfigHolderImpl;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.fabricmc.loader.api.entrypoint.EntrypointContainer;
|
||||
import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint;
|
||||
import net.minecraft.util.Language;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class JfConfigSafe implements PreLaunchEntrypoint {
|
||||
public static Function<String, String> TRANSLATION_SUPPLIER = s -> null;
|
||||
public static final Set<String> REGISTERED_MODS = new HashSet<>();
|
||||
@Override
|
||||
public void onPreLaunch() {
|
||||
for (EntrypointContainer<JfCustomConfig> custom : FabricLoader.getInstance().getEntrypointContainers(ConfigHolderImpl.CUSTOM_ID, JfCustomConfig.class)) {
|
||||
custom.getEntrypoint().register(DSL.create(custom.getProvider().getMetadata().getId()));
|
||||
for (EntrypointContainer<Object> custom : FabricLoader.getInstance().getEntrypointContainers(ConfigHolderImpl.MODULE_ID, Object.class)) {
|
||||
if (!REGISTERED_MODS.contains(custom.getProvider().getMetadata().getId()) && custom.getEntrypoint() instanceof JfCustomConfig cfg) {
|
||||
REGISTERED_MODS.add(custom.getProvider().getMetadata().getId());
|
||||
cfg.register(DSL.create(custom.getProvider().getMetadata().getId()));
|
||||
}
|
||||
}
|
||||
TRANSLATION_SUPPLIER = s -> {
|
||||
String translated = Language.getInstance().get(s);
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "libjf-config-v1",
|
||||
"id": "libjf-config-core-v1",
|
||||
"name": "LibJF Config",
|
||||
"version": "${version}",
|
||||
"authors": ["JFronny"],
|
||||
|
@ -11,19 +11,13 @@
|
|||
"license": "MIT",
|
||||
"environment": "*",
|
||||
"entrypoints": {
|
||||
"modmenu": ["io.gitlab.jfronny.libjf.config.impl.client.ModMenu"],
|
||||
"client": ["io.gitlab.jfronny.libjf.config.impl.client.JfConfigClient"],
|
||||
"preLaunch": ["io.gitlab.jfronny.libjf.config.impl.entrypoint.JfConfigSafe"],
|
||||
"main": ["io.gitlab.jfronny.libjf.config.impl.JfConfigCommand"],
|
||||
"libjf:coprocess": ["io.gitlab.jfronny.libjf.config.impl.JfConfigWatchService"]
|
||||
},
|
||||
"depends": {
|
||||
"fabricloader": ">=0.12.0",
|
||||
"minecraft": "*",
|
||||
"fabric-resource-loader-v0": "*",
|
||||
"fabric-command-api-v2": "*",
|
||||
"libjf-base": ">=${version}",
|
||||
"libjf-unsafe-v0": ">=${version}"
|
||||
"libjf-base": ">=${version}"
|
||||
},
|
||||
"custom": {
|
||||
"modmenu": {
|
|
@ -0,0 +1,63 @@
|
|||
package io.gitlab.jfronny.libjf.config.test;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.*;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.dsl.*;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@JfConfig(referencedConfigs = {"Yes", "No", "Yes"})
|
||||
public class TestConfigCustom implements JfCustomConfig {
|
||||
// User-created
|
||||
@Entry
|
||||
public static Integer someValue = 10;
|
||||
@Entry public static String someOther = "Yes";
|
||||
|
||||
@Category
|
||||
public static class SomeCategory {
|
||||
@Entry public static boolean someBool = true;
|
||||
@Entry public static List<String> someObject = new LinkedList<>();
|
||||
|
||||
@Verifier
|
||||
public static void exampleVerifier() {
|
||||
|
||||
}
|
||||
|
||||
@Preset
|
||||
public static void examplePreset() {
|
||||
|
||||
}
|
||||
|
||||
// Generated
|
||||
private static CategoryBuilder<?> libjf$config$root(CategoryBuilder<?> builder) {
|
||||
return builder
|
||||
.value("someBool", someBool, () -> SomeCategory.someBool, v -> SomeCategory.someBool = v)
|
||||
.value(EntryInfo.ofField(SomeCategory.class, "someObject"))
|
||||
.addVerifier(SomeCategory::exampleVerifier)
|
||||
.addPreset("examplePreset", SomeCategory::examplePreset);
|
||||
}
|
||||
}
|
||||
|
||||
// Generated
|
||||
@Override
|
||||
public void register(DSL.Defaulted dsl) {
|
||||
// Here to ensure the static initializer is called
|
||||
}
|
||||
|
||||
static {
|
||||
libjf$config$clinit();
|
||||
}
|
||||
|
||||
private static void libjf$config$clinit() {
|
||||
ConfigHolder.getInstance().migrateFiles("libjf-config-v1-testmod");
|
||||
DSL.create("libjf-config-v1-testmod").config(TestConfigCustom::libjf$config$root);
|
||||
}
|
||||
|
||||
private static ConfigBuilder<?> libjf$config$root(ConfigBuilder<?> builder) {
|
||||
return builder
|
||||
.referenceConfig("libjf-config-reflect-v0-testmod")
|
||||
.value("someValue", someValue, -50, 50, () -> someValue, v -> someValue = v)
|
||||
.value("someOther", someOther, () -> someOther, v -> someOther = v)
|
||||
.category("someCategory", SomeCategory::libjf$config$root);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
archivesBaseName = "libjf-config-legacy-shim"
|
||||
|
||||
dependencies {
|
||||
api project(path: ":libjf-base", configuration: "dev")
|
||||
api project(path: ":libjf-config-core-v1", configuration: "dev")
|
||||
api project(path: ":libjf-config-commands-v1", configuration: "dev")
|
||||
api project(path: ":libjf-config-reflect-v1", configuration: "dev")
|
||||
api project(path: ":libjf-config-ui-tiny-v1", configuration: "dev")
|
||||
api project(path: ":libjf-unsafe-v0", configuration: "dev")
|
||||
}
|
|
@ -4,6 +4,7 @@ import net.minecraft.client.font.TextRenderer;
|
|||
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||
import net.minecraft.client.gui.widget.ClickableWidget;
|
||||
|
||||
@Deprecated
|
||||
public interface WidgetFactory {
|
||||
Widget build(int screenWidth, TextRenderer textRenderer, ButtonWidget done);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package io.gitlab.jfronny.libjf.config.api;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Deprecated
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface Category {
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package io.gitlab.jfronny.libjf.config.api;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.impl.legacy.ConfigHolderImpl;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
@Deprecated
|
||||
public interface ConfigHolder {
|
||||
static ConfigHolder getInstance() {
|
||||
return new ConfigHolderImpl(io.gitlab.jfronny.libjf.config.api.v1.ConfigHolder.getInstance());
|
||||
}
|
||||
void register(String modId, Class<?> config);
|
||||
Map<String, ConfigInstance> getRegistered();
|
||||
ConfigInstance get(Class<?> configClass);
|
||||
ConfigInstance get(String modId);
|
||||
ConfigInstance get(Path configPath);
|
||||
boolean isRegistered(Class<?> configClass);
|
||||
boolean isRegistered(String modId);
|
||||
boolean isRegistered(Path configPath);
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package io.gitlab.jfronny.libjf.config.api;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Deprecated
|
||||
public interface ConfigInstance {
|
||||
static ConfigInstance get(Class<?> configClass) {
|
||||
return ConfigHolder.getInstance().get(configClass);
|
||||
}
|
||||
static ConfigInstance get(String modId) {
|
||||
return ConfigHolder.getInstance().get(modId);
|
||||
}
|
||||
void load();
|
||||
void write();
|
||||
String getId();
|
||||
String getCategoryPath();
|
||||
default String getTranslationPrefix() {
|
||||
return getId() + ".jfconfig." + getCategoryPath();
|
||||
}
|
||||
List<EntryInfo<?>> getEntries();
|
||||
Map<String, Runnable> getPresets();
|
||||
default List<ConfigInstance> getReferencedConfigs() {
|
||||
return List.of();
|
||||
}
|
||||
Map<String, ConfigInstance> getCategories();
|
||||
default void fix() {
|
||||
for (EntryInfo<?> entry : getEntries()) {
|
||||
entry.fix();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package io.gitlab.jfronny.libjf.config.api;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Deprecated
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface Entry {
|
||||
int width() default 100;
|
||||
double min() default Double.NEGATIVE_INFINITY;
|
||||
double max() default Double.POSITIVE_INFINITY;
|
||||
}
|
|
@ -2,17 +2,11 @@ package io.gitlab.jfronny.libjf.config.api;
|
|||
|
||||
import io.gitlab.jfronny.gson.JsonElement;
|
||||
import io.gitlab.jfronny.gson.stream.JsonWriter;
|
||||
import io.gitlab.jfronny.libjf.config.api.type.Type;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Deprecated
|
||||
public interface EntryInfo<T> {
|
||||
/**
|
||||
* Get the name of this entry
|
||||
* @return This entry's name
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* @return Get the default value of this entry
|
||||
*/
|
||||
|
@ -34,13 +28,19 @@ public interface EntryInfo<T> {
|
|||
* Get the value type of this entry. Will use the class definition, not the current value
|
||||
* @return The type of this entry
|
||||
*/
|
||||
Type<T> getValueType();
|
||||
Class<T> getValueType();
|
||||
|
||||
/**
|
||||
* Ensure the current value is within expected bounds.
|
||||
*/
|
||||
void fix();
|
||||
|
||||
/**
|
||||
* Get the name of this entry
|
||||
* @return This entry's name
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Set this entry's value to that of the element
|
||||
* @param element The element to read from
|
||||
|
@ -67,8 +67,4 @@ public interface EntryInfo<T> {
|
|||
* @return Get the maximum value for this entry
|
||||
*/
|
||||
double getMaxValue();
|
||||
|
||||
default void reset() throws IllegalAccessException {
|
||||
setValue(getDefault());
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
package io.gitlab.jfronny.libjf.config.api;
|
||||
|
||||
@Deprecated
|
||||
public interface JfConfig {
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package io.gitlab.jfronny.libjf.config.api;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Deprecated
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface Preset {
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package io.gitlab.jfronny.libjf.config.api;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Deprecated
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface Verifier {
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.legacy;
|
||||
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
import io.gitlab.jfronny.libjf.config.api.*;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.ConfigCategory;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.dsl.CategoryBuilder;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.dsl.DSL;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.type.Type;
|
||||
import io.gitlab.jfronny.libjf.config.impl.AuxiliaryMetadata;
|
||||
import io.gitlab.jfronny.libjf.config.impl.dsl.DslEntryInfo;
|
||||
import io.gitlab.jfronny.libjf.config.impl.reflect.ReflectiveConfigBuilderImpl;
|
||||
import net.fabricmc.loader.api.ModContainer;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Deprecated
|
||||
public record ConfigHolderImpl(io.gitlab.jfronny.libjf.config.api.v1.ConfigHolder base) implements ConfigHolder {
|
||||
private static final Map<Class<?>, io.gitlab.jfronny.libjf.config.api.v1.ConfigInstance> klazzToInstance = new HashMap<>();
|
||||
@Override
|
||||
public void register(String modId, Class<?> config) {
|
||||
AtomicReference<ModContainer> mc = new AtomicReference<>();
|
||||
base.migrateFiles(modId);
|
||||
klazzToInstance.put(config, DSL.create(modId).register(builder -> {
|
||||
Optional.ofNullable(AuxiliaryMetadata.forMod(modId)).ifPresent(meta -> meta.applyTo(builder));
|
||||
return applyCategory(builder, config);
|
||||
}));
|
||||
}
|
||||
|
||||
private <T extends CategoryBuilder<?>> T applyCategory(T builder, Class<?> klazz) {
|
||||
for (Field field : klazz.getFields()) {
|
||||
if (field.isAnnotationPresent(Entry.class)) {
|
||||
Entry entry = field.getAnnotation(Entry.class);
|
||||
Object defaultValue = null;
|
||||
try {
|
||||
defaultValue = field.get(null);
|
||||
} catch (IllegalAccessException ignored) {
|
||||
}
|
||||
//noinspection unchecked,rawtypes
|
||||
builder.value(new DslEntryInfo<Object>(
|
||||
field.getName(),
|
||||
defaultValue,
|
||||
() -> field.get(null),
|
||||
v -> field.set(null, v),
|
||||
(Type) Type.ofClass(field.getGenericType()),
|
||||
entry.width(),
|
||||
entry.min(),
|
||||
entry.max()
|
||||
));
|
||||
}
|
||||
}
|
||||
builder.addPreset(ReflectiveConfigBuilderImpl.CONFIG_PRESET_DEFAULT, ConfigCategory::reset);
|
||||
for (Method method : klazz.getMethods()) {
|
||||
if (method.isAnnotationPresent(Preset.class)) {
|
||||
builder.addPreset(builder.getTranslationPrefix() + method.getName(), c -> {
|
||||
try {
|
||||
method.invoke(null);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
LibJf.LOGGER.error("Could not apply preset", e);
|
||||
}
|
||||
});
|
||||
} else if (method.isAnnotationPresent(Verifier.class)) {
|
||||
builder.addVerifier(c -> {
|
||||
try {
|
||||
method.invoke(null);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
LibJf.LOGGER.error("Could not run verifier", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
for (Class<?> category : klazz.getClasses()) {
|
||||
if (category.isAnnotationPresent(Category.class)) {
|
||||
String name = category.getSimpleName();
|
||||
name = Character.toLowerCase(name.charAt(0)) + name.substring(1); // camelCase
|
||||
builder.category(name, categoryBuilder -> applyCategory(categoryBuilder, category));
|
||||
}
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ConfigInstance> getRegistered() {
|
||||
return base.getRegistered().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, s -> ConfigInstanceImpl.of(s.getValue())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigInstance get(Class<?> configClass) {
|
||||
return ConfigInstanceImpl.of(klazzToInstance.get(configClass));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigInstance get(String modId) {
|
||||
return ConfigInstanceImpl.of(base.get(modId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigInstance get(Path configPath) {
|
||||
return ConfigInstanceImpl.of(base.get(configPath));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(Class<?> configClass) {
|
||||
return klazzToInstance.containsKey(configClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String modId) {
|
||||
return base.isRegistered(modId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(Path configPath) {
|
||||
return base.isRegistered(configPath);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.legacy;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
|
||||
import io.gitlab.jfronny.libjf.config.api.EntryInfo;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.ConfigCategory;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Deprecated
|
||||
public record ConfigInstanceImpl(io.gitlab.jfronny.libjf.config.api.v1.ConfigInstance root, ConfigCategory category) implements ConfigInstance {
|
||||
public ConfigInstanceImpl(io.gitlab.jfronny.libjf.config.api.v1.ConfigInstance instance) {
|
||||
this(Objects.requireNonNull(instance), instance);
|
||||
}
|
||||
|
||||
public static ConfigInstanceImpl of(io.gitlab.jfronny.libjf.config.api.v1.ConfigInstance instance) {
|
||||
return instance == null ? null : new ConfigInstanceImpl(instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
root.load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write() {
|
||||
root.write();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return category.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategoryPath() {
|
||||
return category.getCategoryPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTranslationPrefix() {
|
||||
return category.getTranslationPrefix();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EntryInfo<?>> getEntries() {
|
||||
return category.getEntries().stream().<EntryInfo<?>>map(EntryInfoImpl::new).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Runnable> getPresets() {
|
||||
return category.getPresets();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ConfigInstance> getReferencedConfigs() {
|
||||
return category.getReferencedConfigs().stream().<ConfigInstance>map(r -> new ConfigInstanceImpl(root, r)).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ConfigInstance> getCategories() {
|
||||
return category.getCategories().entrySet().stream().collect(Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
s -> new ConfigInstanceImpl(root, s.getValue())
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fix() {
|
||||
category.fix();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.legacy;
|
||||
|
||||
import io.gitlab.jfronny.gson.JsonElement;
|
||||
import io.gitlab.jfronny.gson.stream.JsonWriter;
|
||||
import io.gitlab.jfronny.libjf.config.api.EntryInfo;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Deprecated
|
||||
public record EntryInfoImpl<T>(io.gitlab.jfronny.libjf.config.api.v1.EntryInfo<T> base) implements EntryInfo {
|
||||
@Override
|
||||
public Object getDefault() {
|
||||
return base.getDefault();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() throws IllegalAccessException {
|
||||
return base.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(Object value) throws IllegalAccessException {
|
||||
base.setValue((T) value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getValueType() {
|
||||
return base.getValueType().asClass() instanceof Class<?> k ? k : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fix() {
|
||||
base.fix();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return base.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadFromJson(JsonElement element) throws IllegalAccessException {
|
||||
base.loadFromJson(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(JsonWriter writer, String translationPrefix) throws IOException, IllegalAccessException {
|
||||
base.writeTo(writer, translationPrefix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return base.getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMinValue() {
|
||||
return base.getMinValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMaxValue() {
|
||||
return base.getMaxValue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.legacy;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
|
||||
import io.gitlab.jfronny.libjf.config.api.JfConfig;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.JfCustomConfig;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.dsl.DSL;
|
||||
import io.gitlab.jfronny.libjf.unsafe.DynamicEntry;
|
||||
|
||||
@Deprecated
|
||||
public class LegacyRegistrationHook implements JfCustomConfig {
|
||||
@Override
|
||||
public void register(DSL.Defaulted dsl) {
|
||||
DynamicEntry.execute("libjf:config", Object.class, s -> {
|
||||
if (s.instance().getClass().isAnnotationPresent(io.gitlab.jfronny.libjf.config.api.v1.JfConfig.class)) return;
|
||||
if (s.instance() instanceof JfCustomConfig) return;
|
||||
if (!(s.instance() instanceof JfConfig)) return;
|
||||
ConfigHolder.getInstance().register(s.modId(), s.instance().getClass());
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "libjf-config-legacy-shim",
|
||||
"name": "LibJF Config Legacy Shim",
|
||||
"version": "${version}",
|
||||
"provides": ["libjf-config-v0"],
|
||||
"authors": ["JFronny"],
|
||||
"contact": {
|
||||
"website": "https://jfronny.gitlab.io",
|
||||
"repo": "https://gitlab.com/jfmods/libjf"
|
||||
},
|
||||
"license": "MIT",
|
||||
"environment": "*",
|
||||
"entrypoints": {
|
||||
"libjf:config": [
|
||||
"io.gitlab.jfronny.libjf.config.impl.legacy.LegacyRegistrationHook"
|
||||
]
|
||||
},
|
||||
"depends": {
|
||||
"fabricloader": ">=0.12.0",
|
||||
"minecraft": "*",
|
||||
"libjf-config-core-v1": ">=${version}",
|
||||
"libjf-config-reflect-v1": ">=${version}",
|
||||
"libjf-unsafe-v0": ">=${version}"
|
||||
},
|
||||
"custom": {
|
||||
"modmenu": {
|
||||
"badges": ["library"],
|
||||
"parent": "libjf"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package io.gitlab.jfronny.libjf.config.test.reflect;
|
||||
package io.gitlab.jfronny.libjf.config.test.legacy;
|
||||
|
||||
import io.gitlab.jfronny.commons.serialize.gson.api.Ignore;
|
||||
import io.gitlab.jfronny.libjf.config.api.*;
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"libjf-config-legacy-shim-testmod.jfconfig.title": "JfConfig example",
|
||||
"libjf-config-legacy-shim-testmod.jfconfig.disablePacks": "Disable resource packs",
|
||||
"libjf-config-legacy-shim-testmod.jfconfig.intTest": "Int Test",
|
||||
"libjf-config-legacy-shim-testmod.jfconfig.decimalTest": "Decimal Test",
|
||||
"libjf-config-legacy-shim-testmod.jfconfig.dieStr": "String Test",
|
||||
"libjf-config-legacy-shim-testmod.jfconfig.gsonOnlyStr.tooltip": "George",
|
||||
"libjf-config-legacy-shim-testmod.jfconfig.enumTest": "Enum Test",
|
||||
"libjf-config-legacy-shim-testmod.jfconfig.enumTest.tooltip": "Enum Test Tooltip",
|
||||
"libjf-config-legacy-shim-testmod.jfconfig.enum.Test.Test": "Test",
|
||||
"libjf-config-legacy-shim-testmod.jfconfig.enum.Test.ER": "ER",
|
||||
"libjf-config-legacy-shim-testmod.jfconfig.moskau": "Moskau"
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "libjf-config-legacy-shim-testmod",
|
||||
"version": "1.0",
|
||||
"environment": "*",
|
||||
"entrypoints": {
|
||||
"libjf:config": [
|
||||
"io.gitlab.jfronny.libjf.config.test.legacy.TestConfig"
|
||||
]
|
||||
},
|
||||
"depends": {
|
||||
"libjf-config-v0": "*"
|
||||
},
|
||||
"custom": {
|
||||
"libjf": {
|
||||
"config": {
|
||||
"referencedConfigs": ["libjf-web-v0"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,190 +0,0 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.reflect;
|
||||
|
||||
import io.gitlab.jfronny.commons.serialize.gson.api.GsonHolder;
|
||||
import io.gitlab.jfronny.gson.*;
|
||||
import io.gitlab.jfronny.gson.stream.JsonWriter;
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
import io.gitlab.jfronny.libjf.config.api.*;
|
||||
import io.gitlab.jfronny.libjf.config.api.dsl.*;
|
||||
import io.gitlab.jfronny.libjf.config.api.type.Type;
|
||||
import io.gitlab.jfronny.libjf.config.impl.*;
|
||||
import io.gitlab.jfronny.libjf.config.impl.dsl.DslEntryInfo;
|
||||
import io.gitlab.jfronny.libjf.config.impl.entrypoint.JfConfigSafe;
|
||||
import io.gitlab.jfronny.libjf.gson.FabricLoaderGsonGenerator;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.fabricmc.loader.api.ModContainer;
|
||||
import net.fabricmc.loader.api.metadata.CustomValue;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
import static io.gitlab.jfronny.libjf.config.impl.ConfigHolderImpl.MODULE_ID;
|
||||
|
||||
public class ConfigProvider {
|
||||
public static final String CONFIG_PRESET_DEFAULT = "libjf-config-v1.default";
|
||||
public static <T extends JfConfig> void register(String id, Class<T> klazz) {
|
||||
Optional<ModContainer> container = FabricLoader.getInstance().getModContainer(id);
|
||||
List<String> previousNames = new LinkedList<>();
|
||||
var metaRef = new Object() {
|
||||
AuxiliaryMetadata meta = new AuxiliaryMetadata();
|
||||
};
|
||||
if (container.isPresent()) {
|
||||
CustomValue cv = container.get().getMetadata().getCustomValue(MODULE_ID);
|
||||
if (cv == null) {
|
||||
cv = container.get().getMetadata().getCustomValue("libjf");
|
||||
if (cv != null) {
|
||||
cv = cv.getAsObject().get("config");
|
||||
}
|
||||
}
|
||||
if (cv != null) metaRef.meta = GsonHolder.getGson().fromJson(FabricLoaderGsonGenerator.toGson(cv), AuxiliaryMetadata.class);
|
||||
previousNames.addAll(container.get().getMetadata().getProvides());
|
||||
}
|
||||
else {
|
||||
LibJf.LOGGER.warn("Attempted to register config for a mod that is not installed: " + id);
|
||||
}
|
||||
previousNames.add(id);
|
||||
|
||||
Path cfg = FabricLoader.getInstance().getConfigDir();
|
||||
Path path = cfg.resolve(id + ".json5");
|
||||
if (!Files.exists(path)) {
|
||||
try {
|
||||
for (String s : previousNames) {
|
||||
Path previousPath = cfg.resolve(s + ".json");
|
||||
if (Files.exists(previousPath)) {
|
||||
Files.move(previousPath, path);
|
||||
break;
|
||||
}
|
||||
previousPath = cfg.resolve(s + ".json5");
|
||||
if (Files.exists(previousPath)) {
|
||||
Files.move(previousPath, path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LibJf.LOGGER.error("Could not upgrade config directory", e);
|
||||
}
|
||||
}
|
||||
|
||||
DSL.create(id).register(builder -> (ConfigBuilder<?>) applyCategory(builder
|
||||
.setLoadMethod(c -> {
|
||||
if (Files.exists(path)) {
|
||||
try (BufferedReader br = Files.newBufferedReader(path)) {
|
||||
JsonElement element = JsonParser.parseReader(br);
|
||||
if (element.isJsonObject()) loadFrom(element.getAsJsonObject(), c);
|
||||
else LibJf.LOGGER.error("Invalid config: Not a JSON object for " + id);
|
||||
} catch (Exception e) {
|
||||
LibJf.LOGGER.error("Could not read config for " + id, e);
|
||||
}
|
||||
}
|
||||
c.write();
|
||||
})
|
||||
.setWriteMethod(c -> JfConfigWatchService.lock(path, () -> {
|
||||
try (BufferedWriter bw = Files.newBufferedWriter(path);
|
||||
JsonWriter jw = GsonHolder.getGson().newJsonWriter(bw)) {
|
||||
writeTo(jw, c);
|
||||
} catch (Exception e) {
|
||||
LibJf.LOGGER.error("Could not write config", e);
|
||||
}
|
||||
}))
|
||||
.setPath(path),
|
||||
klazz, id, metaRef.meta));
|
||||
}
|
||||
|
||||
private static CategoryBuilder<?> applyCategory(CategoryBuilder<?> builder, Class<?> configClass, String modId, AuxiliaryMetadata meta) {
|
||||
if (meta.referencedConfigs != null) meta.referencedConfigs.forEach(builder::referenceConfig);
|
||||
for (Field field : configClass.getFields()) {
|
||||
if (field.isAnnotationPresent(Entry.class)) {
|
||||
Entry entry = field.getAnnotation(Entry.class);
|
||||
Object defaultValue = null;
|
||||
try {
|
||||
defaultValue = field.get(null);
|
||||
} catch (IllegalAccessException ignored) {
|
||||
}
|
||||
//noinspection unchecked,rawtypes
|
||||
builder.value(new DslEntryInfo<Object>(
|
||||
field.getName(),
|
||||
defaultValue,
|
||||
() -> field.get(null),
|
||||
v -> field.set(null, v),
|
||||
(Type) Type.ofClass(field.getGenericType()),
|
||||
entry == null ? 100 : entry.width(),
|
||||
entry == null ? Double.NEGATIVE_INFINITY : entry.min(),
|
||||
entry == null ? Double.POSITIVE_INFINITY : entry.max()
|
||||
));
|
||||
}
|
||||
}
|
||||
builder.addPreset(CONFIG_PRESET_DEFAULT, ConfigCategory::reset);
|
||||
for (Method method : configClass.getMethods()) {
|
||||
if (method.isAnnotationPresent(Preset.class)) {
|
||||
builder.addPreset(builder.getTranslationPrefix() + method.getName(), c -> {
|
||||
try {
|
||||
method.invoke(null);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
LibJf.LOGGER.error("Could not apply preset", e);
|
||||
}
|
||||
});
|
||||
} else if (method.isAnnotationPresent(Verifier.class)) {
|
||||
builder.addVerifier(c -> {
|
||||
try {
|
||||
method.invoke(null);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
LibJf.LOGGER.error("Could not run verifier", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (Class<?> categoryClass : configClass.getClasses()) {
|
||||
if (categoryClass.isAnnotationPresent(Category.class)) {
|
||||
String name = categoryClass.getSimpleName();
|
||||
name = Character.toLowerCase(name.charAt(0)) + name.substring(1); // camelCase
|
||||
//TODO allow custom auxiliary metadata
|
||||
builder.category(name, builder1 -> applyCategory(builder1, categoryClass, modId, new AuxiliaryMetadata()));
|
||||
}
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static void loadFrom(JsonObject source, ConfigCategory category) {
|
||||
for (EntryInfo<?> entry : category.getEntries()) {
|
||||
if (source.has(entry.getName())) {
|
||||
try {
|
||||
entry.loadFromJson(source.get(entry.getName()));
|
||||
} catch (IllegalAccessException e) {
|
||||
LibJf.LOGGER.error("Could not set config entry value of " + entry.getName(), e);
|
||||
}
|
||||
} else LibJf.LOGGER.error("Config does not contain entry for " + entry.getName());
|
||||
}
|
||||
for (Map.Entry<String, ConfigCategory> entry : category.getCategories().entrySet()) {
|
||||
if (source.has(entry.getKey())) {
|
||||
JsonElement el = source.get(entry.getKey());
|
||||
if (el.isJsonObject()) loadFrom(el.getAsJsonObject(), entry.getValue());
|
||||
else LibJf.LOGGER.error("Config category is not a JSON object, skipping");
|
||||
} else LibJf.LOGGER.error("Config does not contain entry for subcategory " + entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeTo(JsonWriter writer, ConfigCategory category) throws IOException {
|
||||
category.fix();
|
||||
writer.beginObject();
|
||||
String val;
|
||||
for (EntryInfo<?> entry : category.getEntries()) {
|
||||
try {
|
||||
entry.writeTo(writer, category.getTranslationPrefix());
|
||||
} catch (IllegalAccessException e) {
|
||||
LibJf.LOGGER.error("Could not write entry", e);
|
||||
}
|
||||
}
|
||||
for (Map.Entry<String, ConfigCategory> entry : category.getCategories().entrySet()) {
|
||||
if ((val = JfConfigSafe.TRANSLATION_SUPPLIER.apply(category.getTranslationPrefix() + entry.getKey() + ".title")) != null)
|
||||
writer.comment(val);
|
||||
writer.name(entry.getKey());
|
||||
writeTo(writer, entry.getValue());
|
||||
}
|
||||
writer.endObject();
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.reflect.entrypoint;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
|
||||
import io.gitlab.jfronny.libjf.config.api.JfConfig;
|
||||
import io.gitlab.jfronny.libjf.config.impl.ConfigHolderImpl;
|
||||
import io.gitlab.jfronny.libjf.config.impl.reflect.ConfigProvider;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.fabricmc.loader.api.entrypoint.EntrypointContainer;
|
||||
import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint;
|
||||
|
||||
public class JfConfigReflectSafe implements PreLaunchEntrypoint {
|
||||
@Override
|
||||
public void onPreLaunch() {
|
||||
for (EntrypointContainer<JfConfig> config : FabricLoader.getInstance().getEntrypointContainers(ConfigHolderImpl.MODULE_ID, JfConfig.class)) {
|
||||
registerIfMissing(config.getProvider().getMetadata().getId(), config.getEntrypoint().getClass());
|
||||
}
|
||||
}
|
||||
|
||||
public static void registerIfMissing(String modId, Class<? extends JfConfig> klazz) {
|
||||
if (!ConfigHolder.getInstance().isRegistered(modId)) {
|
||||
ConfigProvider.register(modId, klazz);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
archivesBaseName = "libjf-config-reflect-v0"
|
||||
archivesBaseName = "libjf-config-reflect-v1"
|
||||
|
||||
dependencies {
|
||||
api project(path: ":libjf-base", configuration: "dev")
|
||||
api project(path: ":libjf-unsafe-v0", configuration: "dev")
|
||||
api project(path: ":libjf-config-v1", configuration: "dev")
|
||||
api project(path: ":libjf-config-core-v1", configuration: "dev")
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package io.gitlab.jfronny.libjf.config.api.v1.reflect;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.dsl.ConfigBuilder;
|
||||
import io.gitlab.jfronny.libjf.config.impl.reflect.ReflectiveConfigBuilderImpl;
|
||||
|
||||
public interface ReflectiveConfigBuilder extends ConfigBuilder.ConfigBuilderFunction {
|
||||
static ReflectiveConfigBuilder of(String id, Class<?> klazz) {
|
||||
return new ReflectiveConfigBuilderImpl(id, klazz);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.reflect;
|
||||
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.*;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.dsl.CategoryBuilder;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.dsl.ConfigBuilder;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.reflect.ReflectiveConfigBuilder;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.type.Type;
|
||||
import io.gitlab.jfronny.libjf.config.impl.AuxiliaryMetadata;
|
||||
import io.gitlab.jfronny.libjf.config.impl.dsl.DslEntryInfo;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
import java.util.Objects;
|
||||
|
||||
public class ReflectiveConfigBuilderImpl implements ReflectiveConfigBuilder {
|
||||
public static final String CONFIG_PRESET_DEFAULT = "libjf-config-v1.default";
|
||||
private final AuxiliaryMetadata rootMeta;
|
||||
private final Class<?> rootClass;
|
||||
|
||||
public ReflectiveConfigBuilderImpl(String id, Class<?> klazz) {
|
||||
this.rootClass = Objects.requireNonNull(klazz);
|
||||
this.rootMeta = AuxiliaryMetadata.of(klazz.getAnnotation(JfConfig.class))
|
||||
.merge(AuxiliaryMetadata.forMod(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigBuilder<?> apply(ConfigBuilder<?> builder) {
|
||||
return applyCategory(builder, rootClass, rootMeta);
|
||||
}
|
||||
|
||||
private static <T extends CategoryBuilder<?>> T applyCategory(T builder, Class<?> configClass, AuxiliaryMetadata meta) {
|
||||
meta.applyTo(builder);
|
||||
for (Field field : configClass.getFields()) {
|
||||
if (field.isAnnotationPresent(Entry.class)) {
|
||||
builder.value(DslEntryInfo.ofField(field));
|
||||
}
|
||||
}
|
||||
builder.addPreset(CONFIG_PRESET_DEFAULT, ConfigCategory::reset);
|
||||
for (Method method : configClass.getMethods()) {
|
||||
if (method.isAnnotationPresent(Preset.class)) {
|
||||
builder.addPreset(builder.getTranslationPrefix() + method.getName(), c -> {
|
||||
try {
|
||||
method.invoke(null);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
LibJf.LOGGER.error("Could not apply preset", e);
|
||||
}
|
||||
});
|
||||
} else if (method.isAnnotationPresent(Verifier.class)) {
|
||||
builder.addVerifier(c -> {
|
||||
try {
|
||||
method.invoke(null);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
LibJf.LOGGER.error("Could not run verifier", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (Class<?> categoryClass : configClass.getClasses()) {
|
||||
if (categoryClass.isAnnotationPresent(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))));
|
||||
}
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.reflect.entrypoint;
|
||||
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.*;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.dsl.DSL;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.reflect.ReflectiveConfigBuilder;
|
||||
import io.gitlab.jfronny.libjf.config.impl.ConfigHolderImpl;
|
||||
import io.gitlab.jfronny.libjf.config.impl.entrypoint.JfConfigSafe;
|
||||
import io.gitlab.jfronny.libjf.config.impl.reflect.ReflectiveConfigBuilderImpl;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.fabricmc.loader.api.entrypoint.EntrypointContainer;
|
||||
import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint;
|
||||
|
||||
public class JfConfigReflectSafe implements PreLaunchEntrypoint {
|
||||
@Override
|
||||
public void onPreLaunch() {
|
||||
for (EntrypointContainer<Object> config : FabricLoader.getInstance().getEntrypointContainers(ConfigHolderImpl.MODULE_ID, Object.class)) {
|
||||
registerIfMissing(config.getProvider().getMetadata().getId(), config.getEntrypoint());
|
||||
}
|
||||
}
|
||||
|
||||
public static void registerIfMissing(String modId, Object config) {
|
||||
if (!JfConfigSafe.REGISTERED_MODS.contains(modId)) {
|
||||
JfConfigSafe.REGISTERED_MODS.add(modId);
|
||||
ConfigHolder.getInstance().migrateFiles(modId);
|
||||
if (config instanceof JfCustomConfig cfg) {
|
||||
cfg.register(DSL.create(modId));
|
||||
} else {
|
||||
Class<?> klazz = config.getClass();
|
||||
if (klazz.isAnnotationPresent(JfConfig.class)) {
|
||||
DSL.create(modId).register(ReflectiveConfigBuilder.of(modId, klazz));
|
||||
} else {
|
||||
LibJf.LOGGER.error("Attempted to register improper config for mod " + modId + " (missing @JfConfig annotation or JfCustomConfig interface)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,6 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.reflect.entrypoint;
|
||||
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
import io.gitlab.jfronny.libjf.config.api.JfConfig;
|
||||
import io.gitlab.jfronny.libjf.config.api.JfCustomConfig;
|
||||
import io.gitlab.jfronny.libjf.config.api.dsl.DSL;
|
||||
import io.gitlab.jfronny.libjf.config.impl.ConfigHolderImpl;
|
||||
import io.gitlab.jfronny.libjf.unsafe.DynamicEntry;
|
||||
import io.gitlab.jfronny.libjf.unsafe.UltraEarlyInit;
|
||||
|
@ -11,11 +8,8 @@ import io.gitlab.jfronny.libjf.unsafe.UltraEarlyInit;
|
|||
public class JfConfigUnsafe implements UltraEarlyInit {
|
||||
@Override
|
||||
public void init() {
|
||||
DynamicEntry.execute(ConfigHolderImpl.MODULE_ID, JfConfig.class,
|
||||
s -> JfConfigReflectSafe.registerIfMissing(s.modId(), s.instance().getClass())
|
||||
);
|
||||
DynamicEntry.execute(ConfigHolderImpl.CUSTOM_ID, JfCustomConfig.class,
|
||||
s -> s.instance().register(DSL.create(s.modId()))
|
||||
DynamicEntry.execute(ConfigHolderImpl.MODULE_ID, Object.class,
|
||||
s -> JfConfigReflectSafe.registerIfMissing(s.modId(), s.instance())
|
||||
);
|
||||
LibJf.LOGGER.info("Finished LibJF config entrypoint");
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "libjf-config-reflect-v0",
|
||||
"id": "libjf-config-reflect-v1",
|
||||
"name": "LibJF Config Reflect",
|
||||
"version": "${version}",
|
||||
"authors": ["JFronny"],
|
||||
|
@ -21,11 +21,9 @@
|
|||
"depends": {
|
||||
"fabricloader": ">=0.12.0",
|
||||
"minecraft": "*",
|
||||
"fabric-resource-loader-v0": "*",
|
||||
"fabric-command-api-v2": "*",
|
||||
"libjf-base": ">=${version}",
|
||||
"libjf-unsafe-v0": ">=${version}",
|
||||
"libjf-config-v1": ">=${version}"
|
||||
"libjf-config-core-v1": ">=${version}"
|
||||
},
|
||||
"custom": {
|
||||
"modmenu": {
|
|
@ -0,0 +1,58 @@
|
|||
package io.gitlab.jfronny.libjf.config.test.reflect;
|
||||
|
||||
import io.gitlab.jfronny.commons.serialize.gson.api.Ignore;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@JfConfig(referencedConfigs = {"libjf-web-v0"})
|
||||
public class TestConfig {
|
||||
@Entry
|
||||
public static boolean disablePacks = false;
|
||||
@Entry public static Boolean disablePacks2 = false;
|
||||
@Entry public static int intTest = 20;
|
||||
@Entry(min = -6) public static float floatTest = -5;
|
||||
@Entry(max = 21) public static double doubleTest = 20;
|
||||
@Entry public static String dieStr = "lolz";
|
||||
@Entry @Ignore
|
||||
public static String guiOnlyStr = "lolz";
|
||||
public static String gsonOnlyStr = "lolz";
|
||||
@Entry public static Test enumTest = Test.Test;
|
||||
@Entry public static List<String> stringList;
|
||||
|
||||
@Preset
|
||||
public static void moskau() {
|
||||
disablePacks = true;
|
||||
disablePacks2 = true;
|
||||
intTest = -5;
|
||||
floatTest = -6;
|
||||
doubleTest = 4;
|
||||
dieStr = "Moskau";
|
||||
}
|
||||
|
||||
@Verifier
|
||||
public static void setIntTestIfDisable() {
|
||||
if (disablePacks) intTest = 0;
|
||||
}
|
||||
|
||||
@Verifier
|
||||
public static void stringListVerifier() {
|
||||
if (stringList == null) stringList = new ArrayList<>(List.of("Obama"));
|
||||
}
|
||||
|
||||
public enum Test {
|
||||
Test, ER
|
||||
}
|
||||
|
||||
@Category
|
||||
public static class Subcategory {
|
||||
@Entry public static boolean boolInSub = false;
|
||||
@Entry public static int intIbSub = 15;
|
||||
|
||||
@Category
|
||||
public static class Inception {
|
||||
@Entry public static Test yesEnum = Test.ER;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,12 +7,5 @@
|
|||
"libjf:config": [
|
||||
"io.gitlab.jfronny.libjf.config.test.reflect.TestConfig"
|
||||
]
|
||||
},
|
||||
"custom": {
|
||||
"libjf": {
|
||||
"config": {
|
||||
"referencedConfigs": ["libjf-web-v0"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
archivesBaseName = "libjf-config-v1"
|
||||
archivesBaseName = "libjf-config-ui-tiny-v1"
|
||||
|
||||
dependencies {
|
||||
api project(path: ":libjf-base", configuration: "dev")
|
||||
api project(path: ":libjf-config-core-v1", configuration: "dev")
|
||||
include fabricApi.module("fabric-resource-loader-v0", "${project.fabric_version}")
|
||||
include modImplementation(fabricApi.module("fabric-command-api-v2", "${project.fabric_version}"))
|
||||
modCompileOnly("com.terraformersmc:modmenu:4.0.5")
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package io.gitlab.jfronny.libjf.config.api.v1.ui.tiny;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.ConfigInstance;
|
||||
import io.gitlab.jfronny.libjf.config.impl.ui.tiny.TinyConfigScreen;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
|
||||
public interface ConfigScreen {
|
||||
static Screen create(ConfigInstance config, Screen parent) {
|
||||
return new TinyConfigScreen(config, parent);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package io.gitlab.jfronny.libjf.config.api.v1.ui.tiny;
|
||||
|
||||
import net.minecraft.client.font.TextRenderer;
|
||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||
import net.minecraft.client.gui.widget.ClickableWidget;
|
||||
|
||||
public interface WidgetFactory {
|
||||
Widget build(int screenWidth, TextRenderer textRenderer, ButtonWidget done);
|
||||
|
||||
record Widget(Runnable updateControls, ClickableWidget control) {
|
||||
}
|
||||
}
|
|
@ -1,16 +1,15 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.client;
|
||||
package io.gitlab.jfronny.libjf.config.impl.ui.tiny;
|
||||
|
||||
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
|
||||
import com.terraformersmc.modmenu.api.ModMenuApi;
|
||||
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
|
||||
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
import io.gitlab.jfronny.libjf.config.impl.client.gui.TinyConfigScreen;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.ConfigHolder;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.ConfigInstance;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ModMenu implements ModMenuApi {
|
||||
public class ModMenuAdapter implements ModMenuApi {
|
||||
@Override
|
||||
public Map<String, ConfigScreenFactory<?>> getProvidedConfigScreenFactories() {
|
||||
Map<String, ConfigScreenFactory<?>> factories = new HashMap<>();
|
|
@ -1,9 +1,13 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.client.gui;
|
||||
package io.gitlab.jfronny.libjf.config.impl.ui.tiny;
|
||||
|
||||
import io.gitlab.jfronny.commons.throwable.Try;
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
import io.gitlab.jfronny.libjf.config.api.*;
|
||||
import io.gitlab.jfronny.libjf.config.impl.client.gui.presets.PresetsScreen;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.ui.tiny.WidgetFactory;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.ConfigCategory;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.ConfigInstance;
|
||||
import io.gitlab.jfronny.libjf.config.impl.ui.tiny.entry.EntryInfoWidgetBuilder;
|
||||
import io.gitlab.jfronny.libjf.config.impl.ui.tiny.entry.EntryListWidget;
|
||||
import io.gitlab.jfronny.libjf.config.impl.ui.tiny.presets.PresetsScreen;
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
|
@ -29,7 +33,7 @@ public class TinyConfigScreen extends Screen {
|
|||
private final Screen parent;
|
||||
private final ConfigCategory config;
|
||||
private final List<WidgetState<?>> widgets;
|
||||
private MidnightConfigListWidget list;
|
||||
private EntryListWidget list;
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
|
@ -53,7 +57,7 @@ public class TinyConfigScreen extends Screen {
|
|||
Objects.requireNonNull(client).setScreen(parent);
|
||||
}));
|
||||
|
||||
this.list = new MidnightConfigListWidget(this.client, this.width, this.height, 32, this.height - 32, 25);
|
||||
this.list = new EntryListWidget(this.client, this.width, this.height, 32, this.height - 32, 25);
|
||||
this.addSelectableChild(this.list);
|
||||
for (Map.Entry<String, ConfigCategory> entry : config.getCategories().entrySet()) {
|
||||
this.list.addReference(width / 2,
|
|
@ -1,9 +1,9 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.client.gui;
|
||||
package io.gitlab.jfronny.libjf.config.impl.ui.tiny;
|
||||
|
||||
import io.gitlab.jfronny.commons.tuple.Tuple;
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
import io.gitlab.jfronny.libjf.config.api.EntryInfo;
|
||||
import io.gitlab.jfronny.libjf.config.api.WidgetFactory;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.EntryInfo;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.ui.tiny.WidgetFactory;
|
||||
import net.minecraft.client.gui.widget.TextFieldWidget;
|
||||
import net.minecraft.text.Text;
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.client.gui;
|
||||
package io.gitlab.jfronny.libjf.config.impl.ui.tiny.entry;
|
||||
|
||||
import io.gitlab.jfronny.commons.throwable.Try;
|
||||
import io.gitlab.jfronny.commons.tuple.Tuple;
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
import io.gitlab.jfronny.libjf.config.api.*;
|
||||
import io.gitlab.jfronny.libjf.config.api.type.Type;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.ConfigCategory;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.EntryInfo;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.type.Type;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.ui.tiny.WidgetFactory;
|
||||
import io.gitlab.jfronny.libjf.config.impl.ui.tiny.WidgetState;
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||
|
@ -13,7 +16,6 @@ import net.minecraft.text.Text;
|
|||
import net.minecraft.util.Formatting;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
|
@ -37,6 +39,7 @@ public class EntryInfoWidgetBuilder {
|
|||
WidgetFactory factory;
|
||||
|
||||
if (type.isInt()) factory = textField(info, state, INTEGER_ONLY, Integer::parseInt, true, info.getMinValue(), info.getMaxValue());
|
||||
else if (type.isLong()) factory = textField(info, state, INTEGER_ONLY, Long::parseLong, true, info.getMinValue(), info.getMaxValue());
|
||||
else if (type.isFloat()) factory = textField(info, state, DECIMAL_ONLY, Float::parseFloat, false, info.getMinValue(), info.getMaxValue());
|
||||
else if (type.isDouble()) factory = textField(info, state, DECIMAL_ONLY, Double::parseDouble, false, info.getMinValue(), info.getMaxValue());
|
||||
else if (type.isString()) factory = textField(info, state, null, String::length, true, Math.min(info.getMinValue(),0), Math.max(info.getMaxValue(),1));
|
|
@ -1,4 +1,4 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.client.gui;
|
||||
package io.gitlab.jfronny.libjf.config.impl.ui.tiny.entry;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
|
@ -19,10 +19,10 @@ import java.util.function.BooleanSupplier;
|
|||
import java.util.function.Supplier;
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
public class MidnightConfigListWidget extends ElementListWidget<MidnightConfigListWidget.ConfigListEntryAbstract> {
|
||||
public class EntryListWidget extends ElementListWidget<EntryListWidget.ConfigEntry> {
|
||||
TextRenderer textRenderer;
|
||||
|
||||
public MidnightConfigListWidget(MinecraftClient minecraftClient, int i, int j, int k, int l, int m) {
|
||||
public EntryListWidget(MinecraftClient minecraftClient, int i, int j, int k, int l, int m) {
|
||||
super(minecraftClient, i, j, k, l, m);
|
||||
this.centerListVertically = false;
|
||||
textRenderer = minecraftClient.textRenderer;
|
||||
|
@ -38,7 +38,7 @@ public class MidnightConfigListWidget extends ElementListWidget<MidnightConfigLi
|
|||
}
|
||||
|
||||
public void addReference(int centerX, Text text, Supplier<Screen> targetScreen) {
|
||||
this.addEntry(new ConfigListReferenceEntry(centerX, text, targetScreen));
|
||||
this.addEntry(new ConfigReferenceEntry(centerX, text, targetScreen));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -47,7 +47,7 @@ public class MidnightConfigListWidget extends ElementListWidget<MidnightConfigLi
|
|||
}
|
||||
|
||||
public Optional<Text> getHoveredEntryTitle(double mouseY) {
|
||||
for (ConfigListEntryAbstract abstractEntry : this.children()) {
|
||||
for (ConfigEntry abstractEntry : this.children()) {
|
||||
if (abstractEntry instanceof ConfigScreenEntry entry
|
||||
&& entry.button.visible
|
||||
&& mouseY >= entry.button.y && mouseY < entry.button.y + itemHeight) {
|
||||
|
@ -58,14 +58,14 @@ public class MidnightConfigListWidget extends ElementListWidget<MidnightConfigLi
|
|||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
public static abstract class ConfigListEntryAbstract extends Entry<ConfigListEntryAbstract> {
|
||||
public static abstract class ConfigEntry extends Entry<ConfigEntry> {
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
public static class ConfigListReferenceEntry extends ConfigListEntryAbstract {
|
||||
public static class ConfigReferenceEntry extends ConfigEntry {
|
||||
private final ClickableWidget button;
|
||||
|
||||
public ConfigListReferenceEntry(int centerX, Text text, Supplier<Screen> targetScreen) {
|
||||
public ConfigReferenceEntry(int centerX, Text text, Supplier<Screen> targetScreen) {
|
||||
this.button = new ButtonWidget(centerX - 154, 0, 308, 20, text, btn -> MinecraftClient.getInstance().setScreen(targetScreen.get()));
|
||||
}
|
||||
|
||||
|
@ -87,7 +87,7 @@ public class MidnightConfigListWidget extends ElementListWidget<MidnightConfigLi
|
|||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
public static class ConfigScreenEntry extends ConfigListEntryAbstract {
|
||||
public static class ConfigScreenEntry extends ConfigEntry {
|
||||
private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
|
||||
public final ClickableWidget button;
|
||||
private final BooleanSupplier resetVisible;
|
|
@ -1,4 +1,4 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.client.gui.presets;
|
||||
package io.gitlab.jfronny.libjf.config.impl.ui.tiny.presets;
|
||||
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.Element;
|
|
@ -1,8 +1,7 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.client.gui.presets;
|
||||
package io.gitlab.jfronny.libjf.config.impl.ui.tiny.presets;
|
||||
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
import io.gitlab.jfronny.libjf.config.api.ConfigCategory;
|
||||
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.ConfigCategory;
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.minecraft.client.MinecraftClient;
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "libjf-config-ui-tiny-v1",
|
||||
"name": "LibJF Config UI: Tiny",
|
||||
"version": "${version}",
|
||||
"authors": ["JFronny"],
|
||||
"contact": {
|
||||
"website": "https://jfronny.gitlab.io",
|
||||
"repo": "https://gitlab.com/jfmods/libjf"
|
||||
},
|
||||
"license": "MIT",
|
||||
"environment": "client",
|
||||
"entrypoints": {
|
||||
"modmenu": ["io.gitlab.jfronny.libjf.config.impl.ui.tiny.ModMenuAdapter"]
|
||||
},
|
||||
"depends": {
|
||||
"fabricloader": ">=0.12.0",
|
||||
"minecraft": "*",
|
||||
"fabric-resource-loader-v0": "*",
|
||||
"libjf-base": ">=${version}",
|
||||
"libjf-config-core-v1": ">=${version}"
|
||||
},
|
||||
"custom": {
|
||||
"modmenu": {
|
||||
"badges": ["library"],
|
||||
"parent": "libjf"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.client;
|
||||
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
|
||||
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
|
||||
public class JfConfigClient implements ClientModInitializer {
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
for (ConfigInstance config : ConfigHolder.getInstance().getRegistered().values()) {
|
||||
LibJf.LOGGER.info("Registering config UI for " + config.getId());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package io.gitlab.jfronny.libjf.config.api;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.dsl.DSL;
|
||||
|
||||
public interface JfCustomConfig {
|
||||
void register(DSL.Defaulted dsl);
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class AuxiliaryMetadata {
|
||||
public List<String> referencedConfigs;
|
||||
|
||||
public AuxiliaryMetadata sanitize() {
|
||||
if (referencedConfigs == null) referencedConfigs = List.of();
|
||||
else referencedConfigs = List.copyOf(referencedConfigs);
|
||||
return this;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue