diff --git a/.gitmodules b/.gitmodules index 7a926a8..5738737 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "MockSkript"] path = MockSkript - url = https://github.com/SkEditorPlus/MockSkript + url = https://github.com/SkEditorTeam/MockSkript diff --git a/MockSkript b/MockSkript index 6ed8c0b..2409974 160000 --- a/MockSkript +++ b/MockSkript @@ -1 +1 @@ -Subproject commit 6ed8c0b68f094988097de514aaf8b7db6046b521 +Subproject commit 24099747a3fe90b56a623866e7f1cd12c68b5575 diff --git a/MockSkriptBridge/build.gradle.kts b/MockSkriptBridge/build.gradle.kts index 0ea30d7..2b9994c 100644 --- a/MockSkriptBridge/build.gradle.kts +++ b/MockSkriptBridge/build.gradle.kts @@ -1,34 +1,14 @@ plugins { - id("java") id("net.minecrell.plugin-yml.bukkit") version "0.6.0" } -repositories { - mavenCentral() - maven("https://repo.papermc.io/repository/maven-public/") -} - dependencies { compileOnly(project(":api")) compileOnly(project(":MockSkript", "shadow")) { exclude("*", "*") } - compileOnly("org.projectlombok:lombok:1.18.30") - annotationProcessor("org.projectlombok:lombok:1.18.30") -} - -java { - toolchain.languageVersion.set(JavaLanguageVersion.of(17)) -} - -tasks { - compileJava { - options.encoding = Charsets.UTF_8.name() - options.release.set(17) - dependsOn(clean) - } - - jar.get().archiveVersion = "" + compileOnly("org.projectlombok:lombok:1.18.32") + annotationProcessor("org.projectlombok:lombok:1.18.32") } bukkit { diff --git a/MockSkriptBridge/src/main/java/me/glicz/skanalyzer/bridge/AnalyzerCommandSender.java b/MockSkriptBridge/src/main/java/me/glicz/skanalyzer/bridge/AnalyzerCommandSender.java deleted file mode 100644 index ad3d6c6..0000000 --- a/MockSkriptBridge/src/main/java/me/glicz/skanalyzer/bridge/AnalyzerCommandSender.java +++ /dev/null @@ -1,80 +0,0 @@ -package me.glicz.skanalyzer.bridge; - -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import lombok.RequiredArgsConstructor; -import me.glicz.skanalyzer.ScriptAnalyzeResult; -import me.glicz.skanalyzer.SkAnalyzer; -import me.glicz.skanalyzer.error.ScriptError; -import me.glicz.skanalyzer.structure.ScriptStructure; -import me.glicz.skanalyzer.structure.data.StructureData; -import org.bukkit.command.MessageCommandSender; -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; - -@RequiredArgsConstructor -public class AnalyzerCommandSender implements MessageCommandSender { - private final List messages = new ArrayList<>(); - private final Gson gson = new Gson(); - private final SkAnalyzer skAnalyzer; - - @Override - public void sendMessage(@NotNull String message) { - messages.add(message); - } - - public ScriptAnalyzeResult finish(File file, ScriptStructure structure) { - JsonObject jsonObject = new JsonObject(); - JsonObject fileObject = new JsonObject(); - - List scriptErrors = new ArrayList<>(); - - messages.forEach(message -> { - JsonObject error = gson.fromJson(message, JsonObject.class); - - JsonArray errors = fileObject.getAsJsonArray("errors"); - if (errors == null) { - errors = new JsonArray(); - fileObject.add("errors", errors); - } - errors.add(error); - - scriptErrors.add(new ScriptError( - error.get("line").getAsInt(), - error.get("message").getAsString(), - Level.parse(error.get("level").getAsString()) - )); - }); - - parseStructureDataList(fileObject, "commands", structure.commandDataList()); - parseStructureDataList(fileObject, "events", structure.eventDataList()); - parseStructureDataList(fileObject, "functions", structure.functionDataList()); - fileObject.add("options", gson.toJsonTree(structure.options())); - - jsonObject.add(getCanonicalPath(file).replace('\\', '/'), fileObject); - - skAnalyzer.getLogger().info(jsonObject); - - return new ScriptAnalyzeResult(jsonObject.toString(), scriptErrors, structure); - } - - private String getCanonicalPath(File file) { - try { - return file.getCanonicalPath(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private void parseStructureDataList(JsonObject jsonObject, String type, List structureDataList) { - JsonArray structuresArray = new JsonArray(); - structureDataList.forEach(structureData -> structuresArray.add(gson.toJsonTree(structureData))); - jsonObject.add(type, structuresArray); - } -} diff --git a/MockSkriptBridge/src/main/java/me/glicz/skanalyzer/bridge/MockSkriptBridgeImpl.java b/MockSkriptBridge/src/main/java/me/glicz/skanalyzer/bridge/MockSkriptBridgeImpl.java index 959e24a..9a15e0d 100644 --- a/MockSkriptBridge/src/main/java/me/glicz/skanalyzer/bridge/MockSkriptBridgeImpl.java +++ b/MockSkriptBridge/src/main/java/me/glicz/skanalyzer/bridge/MockSkriptBridgeImpl.java @@ -2,19 +2,22 @@ import ch.njol.skript.ScriptLoader; import ch.njol.skript.Skript; -import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.command.ScriptCommand; import ch.njol.skript.hooks.VaultHook; import ch.njol.skript.hooks.regions.RegionsPlugin; import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.SkriptEventInfo; import ch.njol.skript.lang.function.Signature; -import ch.njol.skript.log.RedirectingLogHandler; -import ch.njol.skript.structures.*; +import ch.njol.skript.structures.StructCommand; +import ch.njol.skript.structures.StructEvent; +import ch.njol.skript.structures.StructFunction; +import ch.njol.skript.structures.StructOptions; import me.glicz.skanalyzer.AnalyzerFlag; -import me.glicz.skanalyzer.ScriptAnalyzeResult; import me.glicz.skanalyzer.SkAnalyzer; -import me.glicz.skanalyzer.bridge.util.ReflectionUtil; +import me.glicz.skanalyzer.bridge.log.CachingLogHandler; +import me.glicz.skanalyzer.bridge.util.FilesUtil; +import me.glicz.skanalyzer.result.ScriptAnalyzeResult; +import me.glicz.skanalyzer.result.ScriptAnalyzeResults; import me.glicz.skanalyzer.structure.ScriptStructure; import me.glicz.skanalyzer.structure.data.CommandData; import me.glicz.skanalyzer.structure.data.EventData; @@ -27,9 +30,13 @@ import java.nio.file.InvalidPathException; import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.Function; import java.util.stream.Collectors; public class MockSkriptBridgeImpl extends MockSkriptBridge { + private Executor mainThreadExecutor; + public MockSkriptBridgeImpl(SkAnalyzer skAnalyzer) { super(skAnalyzer); } @@ -39,6 +46,11 @@ public void onLoad() { parseFlags(); } + @Override + public void onEnable() { + mainThreadExecutor = getServer().getScheduler().getMainThreadExecutor(this); + } + public void parseFlags() { if (skAnalyzer.getFlags().contains(AnalyzerFlag.FORCE_VAULT_HOOK)) { try { @@ -63,23 +75,68 @@ public void parseFlags() { } @Override - public CompletableFuture parseScript(String path) { + public CompletableFuture parseScript(String path, boolean load) { File file = new File(path); - if (!file.exists() || !file.getName().endsWith(".sk")) { + if (!file.exists() || (!file.getName().endsWith(".sk") && !(file.isDirectory() && load))) { skAnalyzer.getLogger().error("Invalid file path"); return CompletableFuture.failedFuture(new InvalidPathException(path, "Provided file doesn't end with '.sk'")); } - AnalyzerCommandSender sender = new AnalyzerCommandSender(skAnalyzer); - RedirectingLogHandler logHandler = new RedirectingLogHandler(sender, null).start(); - return ScriptLoader.loadScripts(file, logHandler, false) - .handle((info, throwable) -> { - if (throwable != null) { - skAnalyzer.getLogger().error("Something went wrong while trying to parse '%s'".formatted(path), throwable); - return CompletableFuture.failedFuture(new RuntimeException(throwable)); - } - return CompletableFuture.completedFuture(info); - }) - .thenApply(info -> sender.finish(file, handleParsedScript(file))); + + Set files = FilesUtil.listScripts(file); + return CompletableFuture.supplyAsync( + () -> { + CachingLogHandler logHandler = new CachingLogHandler().start(); + return ScriptLoader.loadScripts(files, logHandler) + .handle((info, throwable) -> { + if (throwable != null) { + skAnalyzer.getLogger().error("Something went wrong while trying to parse '%s'".formatted(path), throwable); + throw new RuntimeException(throwable); + } + return info; + }) + .thenApply(info -> { + ScriptAnalyzeResults results = new ScriptAnalyzeResults(buildAnalyzeResults(files, logHandler)); + if (!load) { + unloadScript(path); + } + return results; + }) + .join(); + }, + mainThreadExecutor + ); + } + + @Override + public boolean unloadScript(String path) { + File file = new File(path); + if (!file.exists() || !file.getName().endsWith(".sk")) { + skAnalyzer.getLogger().error("Invalid file path"); + return false; + } + + Script script = ScriptLoader.getScript(file); + if (script != null) { + ScriptLoader.unloadScript(script); + return true; + } + return false; + } + + @Override + public void unloadAllScripts() { + ScriptLoader.unloadScripts(ScriptLoader.getLoadedScripts()); + } + + private Map buildAnalyzeResults(Set files, CachingLogHandler logHandler) { + return files.stream().collect(Collectors.toMap( + Function.identity(), + file -> new ScriptAnalyzeResult( + file, + logHandler.scriptErrors(file), + handleParsedScript(file) + ) + )); } private ScriptStructure handleParsedScript(File file) { @@ -92,15 +149,14 @@ private ScriptStructure handleParsedScript(File file) { if (script != null) { script.getStructures().forEach(structure -> { if (structure instanceof StructCommand command) { - ScriptCommand scriptCommand = ReflectionUtil.getScriptCommand(command); + ScriptCommand scriptCommand = command.scriptCommand; if (scriptCommand == null) return; commandDataList.add(handleCommand(command, scriptCommand)); } else if (structure instanceof StructEvent event) { - SkriptEventInfo eventInfo = ReflectionUtil.getEventInfo(event.getSkriptEvent()); - if (eventInfo == null) return; + SkriptEventInfo eventInfo = event.getSkriptEvent().skriptEventInfo; eventDataList.add(handleEvent(event.getSkriptEvent(), eventInfo)); } else if (structure instanceof StructFunction function) { - Signature signature = ReflectionUtil.getFunctionSignature(function); + Signature signature = function.signature; if (signature == null) return; functionDataList.add(handleFunction(function, signature)); } @@ -110,8 +166,6 @@ private ScriptStructure handleParsedScript(File file) { if (optionsData != null) { options.putAll(optionsData.getOptions()); } - - ScriptLoader.unloadScript(script); } return new ScriptStructure(commandDataList, eventDataList, functionDataList, options); @@ -122,18 +176,12 @@ private CommandData handleCommand(StructCommand command, ScriptCommand scriptCom command.getEntryContainer().getSource().getLine(), scriptCommand.getName(), scriptCommand.getAliases(), - StringUtils.defaultIfEmpty(ReflectionUtil.getCommandPermission(scriptCommand), null), - StringUtils.defaultIfEmpty(ReflectionUtil.getCommandDescription(scriptCommand), null), + StringUtils.defaultIfEmpty(scriptCommand.permission, null), + StringUtils.defaultIfEmpty(scriptCommand.description, null), scriptCommand.getPrefix(), - StringUtils.defaultIfEmpty(ReflectionUtil.getCommandUsage(scriptCommand), null), + StringUtils.defaultIfEmpty(scriptCommand.usage, null), scriptCommand.getArguments().stream() - .map(argument -> { - ClassInfo argumentType = ReflectionUtil.getArgumentType(argument); - if (argumentType != null) - return argumentType.getCodeName(); - return null; - }) - .filter(Objects::nonNull) + .map(argument -> argument.type.getCodeName()) .toList() ); } @@ -141,7 +189,7 @@ private CommandData handleCommand(StructCommand command, ScriptCommand scriptCom private EventData handleEvent(SkriptEvent event, SkriptEventInfo eventInfo) { return new EventData( event.getEntryContainer().getSource().getLine(), - ReflectionUtil.getEventExpression(event), + event.expr, Objects.requireNonNullElse(eventInfo.getDocumentationID(), eventInfo.getId()), event.getEventPriority() ); @@ -149,8 +197,9 @@ private EventData handleEvent(SkriptEvent event, SkriptEventInfo eventInfo) { private FunctionData handleFunction(StructFunction function, Signature signature) { String returnType = null; - if (signature.getReturnType() != null) + if (signature.getReturnType() != null) { returnType = signature.getReturnType().getCodeName(); + } return new FunctionData( function.getEntryContainer().getSource().getLine(), diff --git a/MockSkriptBridge/src/main/java/me/glicz/skanalyzer/bridge/log/CachingLogHandler.java b/MockSkriptBridge/src/main/java/me/glicz/skanalyzer/bridge/log/CachingLogHandler.java new file mode 100644 index 0000000..06c9f17 --- /dev/null +++ b/MockSkriptBridge/src/main/java/me/glicz/skanalyzer/bridge/log/CachingLogHandler.java @@ -0,0 +1,40 @@ +package me.glicz.skanalyzer.bridge.log; + +import ch.njol.skript.log.LogEntry; +import ch.njol.skript.log.LogHandler; +import ch.njol.skript.log.SkriptLogger; +import me.glicz.skanalyzer.error.ScriptError; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Unmodifiable; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class CachingLogHandler extends LogHandler { + private final Map> scriptErrors = new HashMap<>(); + + public @Unmodifiable List scriptErrors(File file) { + if (scriptErrors.containsKey(file)) { + return List.copyOf(scriptErrors.get(file)); + } + return List.of(); + } + + @Override + public @NotNull LogResult log(@NotNull LogEntry entry) { + if (entry.node != null) { + this.scriptErrors.computeIfAbsent(entry.node.getConfig().getFile(), file -> new ArrayList<>()) + .add(new ScriptError(entry.node.getLine(), entry.message, entry.level)); + } + + return LogResult.CACHED; + } + + @Override + public @NotNull CachingLogHandler start() { + return SkriptLogger.startLogHandler(this); + } +} diff --git a/MockSkriptBridge/src/main/java/me/glicz/skanalyzer/bridge/util/FilesUtil.java b/MockSkriptBridge/src/main/java/me/glicz/skanalyzer/bridge/util/FilesUtil.java new file mode 100644 index 0000000..ae79812 --- /dev/null +++ b/MockSkriptBridge/src/main/java/me/glicz/skanalyzer/bridge/util/FilesUtil.java @@ -0,0 +1,34 @@ +package me.glicz.skanalyzer.bridge.util; + +import lombok.experimental.UtilityClass; + +import java.io.File; +import java.util.HashSet; +import java.util.Set; + +@UtilityClass +public class FilesUtil { + public static Set listScripts(File directory) { + if (!directory.isDirectory()) { + return Set.of(directory); + } + return listFiles(directory, ".sk"); + } + + private static Set listFiles(File directory, String extension) { + Set scripts = new HashSet<>(); + + for (File file : directory.listFiles()) { + if (file.isDirectory()) { + scripts.addAll(listFiles(file, extension)); + continue; + } + + if (file.getName().endsWith(extension)) { + scripts.add(file); + } + } + + return scripts; + } +} diff --git a/MockSkriptBridge/src/main/java/me/glicz/skanalyzer/bridge/util/ReflectionUtil.java b/MockSkriptBridge/src/main/java/me/glicz/skanalyzer/bridge/util/ReflectionUtil.java deleted file mode 100644 index aa6b966..0000000 --- a/MockSkriptBridge/src/main/java/me/glicz/skanalyzer/bridge/util/ReflectionUtil.java +++ /dev/null @@ -1,166 +0,0 @@ -package me.glicz.skanalyzer.bridge.util; - -import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.command.Argument; -import ch.njol.skript.command.ScriptCommand; -import ch.njol.skript.lang.SkriptEvent; -import ch.njol.skript.lang.SkriptEventInfo; -import ch.njol.skript.lang.function.Signature; -import ch.njol.skript.structures.StructCommand; -import ch.njol.skript.structures.StructFunction; -import lombok.experimental.UtilityClass; - -import java.lang.reflect.Field; - -@UtilityClass -public class ReflectionUtil { - private static final Field scriptCommandField, commandPermissionField, commandDescriptionField, - commandUsageField, argumentTypeField, exprField, skriptEventInfoField, structureField; - - static { - Field tempScriptCommandField = null; - try { - tempScriptCommandField = StructCommand.class.getDeclaredField("scriptCommand"); - tempScriptCommandField.setAccessible(true); - } catch (Exception e) { - e.printStackTrace(System.out); - } - scriptCommandField = tempScriptCommandField; - - Field tempCommandPermissionField = null; - try { - tempCommandPermissionField = ScriptCommand.class.getDeclaredField("permission"); - tempCommandPermissionField.setAccessible(true); - } catch (Exception e) { - e.printStackTrace(System.out); - } - commandPermissionField = tempCommandPermissionField; - - Field tempCommandDescriptionField = null; - try { - tempCommandDescriptionField = ScriptCommand.class.getDeclaredField("description"); - tempCommandDescriptionField.setAccessible(true); - } catch (Exception e) { - e.printStackTrace(System.out); - } - commandDescriptionField = tempCommandDescriptionField; - - Field tempCommandUsageField = null; - try { - tempCommandUsageField = ScriptCommand.class.getDeclaredField("usage"); - tempCommandUsageField.setAccessible(true); - } catch (Exception e) { - e.printStackTrace(System.out); - } - commandUsageField = tempCommandUsageField; - - - Field tempArgumentTypeField = null; - try { - tempArgumentTypeField = Argument.class.getDeclaredField("type"); - tempArgumentTypeField.setAccessible(true); - } catch (Exception e) { - e.printStackTrace(System.out); - } - argumentTypeField = tempArgumentTypeField; - - Field tempExprField = null; - try { - tempExprField = SkriptEvent.class.getDeclaredField("expr"); - tempExprField.setAccessible(true); - } catch (Exception e) { - e.printStackTrace(System.out); - } - exprField = tempExprField; - - Field tempSkriptEventInfo = null; - try { - tempSkriptEventInfo = SkriptEvent.class.getDeclaredField("skriptEventInfo"); - tempSkriptEventInfo.setAccessible(true); - } catch (Exception e) { - e.printStackTrace(System.out); - } - skriptEventInfoField = tempSkriptEventInfo; - - Field tempStructureField = null; - try { - tempStructureField = StructFunction.class.getDeclaredField("signature"); - tempStructureField.setAccessible(true); - } catch (Exception e) { - e.printStackTrace(System.out); - } - structureField = tempStructureField; - } - - public static ScriptCommand getScriptCommand(StructCommand command) { - try { - return (ScriptCommand) scriptCommandField.get(command); - } catch (Throwable e) { - e.printStackTrace(System.out); - return null; - } - } - - public static String getCommandPermission(ScriptCommand command) { - try { - return (String) commandPermissionField.get(command); - } catch (Throwable e) { - e.printStackTrace(System.out); - return null; - } - } - - public static String getCommandDescription(ScriptCommand command) { - try { - return (String) commandDescriptionField.get(command); - } catch (Throwable e) { - e.printStackTrace(System.out); - return null; - } - } - - public static String getCommandUsage(ScriptCommand command) { - try { - return (String) commandUsageField.get(command); - } catch (Throwable e) { - e.printStackTrace(System.out); - return null; - } - } - - public static ClassInfo getArgumentType(Argument argument) { - try { - return (ClassInfo) argumentTypeField.get(argument); - } catch (Throwable e) { - e.printStackTrace(System.out); - return null; - } - } - - public static String getEventExpression(SkriptEvent event) { - try { - return (String) exprField.get(event); - } catch (Throwable e) { - e.printStackTrace(System.out); - return null; - } - } - - public static SkriptEventInfo getEventInfo(SkriptEvent event) { - try { - return (SkriptEventInfo) skriptEventInfoField.get(event); - } catch (Throwable e) { - e.printStackTrace(System.out); - return null; - } - } - - public static Signature getFunctionSignature(StructFunction function) { - try { - return (Signature) structureField.get(function); - } catch (Throwable e) { - e.printStackTrace(System.out); - return null; - } - } -} diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 278093e..9dd43d9 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -1,35 +1,19 @@ plugins { - id("java") id("java-library") - id("com.github.johnrengelman.shadow") version "8.1.1" -} - -repositories { - mavenCentral() - maven("https://repo.papermc.io/repository/maven-public/") + id("io.github.goooler.shadow") } dependencies { - compileOnly("org.projectlombok:lombok:1.18.30") - annotationProcessor("org.projectlombok:lombok:1.18.30") - api("com.github.seeseemelk:MockBukkit-v1.20:3.78.0") + compileOnly("org.projectlombok:lombok:1.18.32") + annotationProcessor("org.projectlombok:lombok:1.18.32") + api("com.github.seeseemelk:MockBukkit-v1.20:3.89.0") implementation("org.apache.logging.log4j:log4j-core:3.0.0-alpha1") implementation("org.slf4j:slf4j-simple:2.0.9") implementation("commons-io:commons-io:2.14.0") implementation("commons-lang:commons-lang:2.6") } -java { - toolchain.languageVersion.set(JavaLanguageVersion.of(17)) -} - tasks { - compileJava { - options.encoding = Charsets.UTF_8.name() - options.release.set(17) - dependsOn(clean) - } - shadowJar { listOf(project(":MockSkript"), project(":MockSkriptBridge")).forEach { addon -> dependsOn(addon.tasks.clean) diff --git a/api/src/main/java/me/glicz/skanalyzer/AnalyzerFlag.java b/api/src/main/java/me/glicz/skanalyzer/AnalyzerFlag.java index de0617a..2bfa1d0 100644 --- a/api/src/main/java/me/glicz/skanalyzer/AnalyzerFlag.java +++ b/api/src/main/java/me/glicz/skanalyzer/AnalyzerFlag.java @@ -8,13 +8,16 @@ @AllArgsConstructor public enum AnalyzerFlag { FORCE_VAULT_HOOK("--forceVaultHook"), - FORCE_REGIONS_HOOK("--forceRegionsHook"); + FORCE_REGIONS_HOOK("--forceRegionsHook"), + SKIP_EXTRACTING_ADDONS("--skipExtractingAddons"), + ; private static final Map ARG_TO_FLAG = new HashMap<>(); static { - for (AnalyzerFlag flag : values()) + for (AnalyzerFlag flag : values()) { ARG_TO_FLAG.put(flag.arg, flag); + } } private final String arg; diff --git a/api/src/main/java/me/glicz/skanalyzer/ScriptAnalyzeResult.java b/api/src/main/java/me/glicz/skanalyzer/ScriptAnalyzeResult.java deleted file mode 100644 index 6b73af7..0000000 --- a/api/src/main/java/me/glicz/skanalyzer/ScriptAnalyzeResult.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.glicz.skanalyzer; - -import me.glicz.skanalyzer.error.ScriptError; -import me.glicz.skanalyzer.structure.ScriptStructure; -import org.jetbrains.annotations.Unmodifiable; - -import java.util.Collections; -import java.util.List; - -public record ScriptAnalyzeResult(String jsonResult, List errors, ScriptStructure structure) { - @Override - @Unmodifiable - public List errors() { - return Collections.unmodifiableList(errors); - } -} diff --git a/api/src/main/java/me/glicz/skanalyzer/SkAnalyzer.java b/api/src/main/java/me/glicz/skanalyzer/SkAnalyzer.java index 4e3d9bd..3129194 100644 --- a/api/src/main/java/me/glicz/skanalyzer/SkAnalyzer.java +++ b/api/src/main/java/me/glicz/skanalyzer/SkAnalyzer.java @@ -9,6 +9,7 @@ import lombok.experimental.Accessors; import me.glicz.skanalyzer.loader.AddonsLoader; import me.glicz.skanalyzer.mockbukkit.AnalyzerServer; +import me.glicz.skanalyzer.result.ScriptAnalyzeResults; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.EnumUtils; import org.apache.logging.log4j.LogManager; @@ -31,37 +32,54 @@ public class SkAnalyzer { public static final String LOGGER_TYPE_PROPERTY = "skanalyzer.loggerType"; public static final String WORKING_DIR_PROPERTY = "skanalyzer.workingDir"; + + private static final File USER_HOME_DIR = new File(System.getProperty("user.home")); + private final EnumSet flags; private final LoggerType loggerType; private final File workingDirectory; private final Logger logger; - private final AnalyzerServer server; - private final AddonsLoader addonsLoader; + private AnalyzerServer server; private SkAnalyzer(AnalyzerFlag[] flags, LoggerType loggerType, File workingDirectory) { this.flags = EnumSet.noneOf(AnalyzerFlag.class); this.flags.addAll(List.of(flags)); this.loggerType = loggerType; - this.workingDirectory = Objects.requireNonNullElse(workingDirectory, AddonsLoader.ADDONS); + this.workingDirectory = Objects.requireNonNullElse(workingDirectory, new File(USER_HOME_DIR, "SkAnalyzer")); this.logger = LogManager.getLogger(loggerType.getLoggerName()); Configurator.setLevel(logger, loggerType.getLoggerLevel()); + } + + @Contract(" -> new") + public static @NotNull Builder builder() { + return new Builder(); + } + public CompletableFuture start() { logger.info("Enabling..."); - this.server = MockBukkit.mock(new AnalyzerServer()); + return buildServer().thenAccept(server -> { + this.server = server; + logger.info("Successfully enabled. Have fun!"); + }); + } - extractEmbeddedAddons(); + private CompletableFuture buildServer() { + CompletableFuture future = new CompletableFuture<>(); - this.addonsLoader = new AddonsLoader(this); - this.addonsLoader.loadAddons(); + Thread thread = new Thread(() -> { + AnalyzerServer server = MockBukkit.mock(new AnalyzerServer(this)); - server.startTicking(); - logger.info("Successfully enabled. Have fun!"); - } + extractEmbeddedAddons(server.getAddonsLoader()); + server.getAddonsLoader().loadAddons(); - @Contract(" -> new") - public static @NotNull Builder builder() { - return new Builder(); + future.complete(server); + + server.startTicking(); + }, "Server Thread"); + thread.start(); + + return future; } @Unmodifiable @@ -69,23 +87,43 @@ public EnumSet getFlags() { return EnumSet.copyOf(flags); } - public CompletableFuture parseScript(String path) { - return addonsLoader.getMockSkriptBridge().parseScript(path); + public CompletableFuture parseScript(String path) { + return parseScript(path, false); } - private void extractEmbeddedAddons() { + public CompletableFuture parseScript(String path, boolean load) { + return server.getAddonsLoader().getMockSkriptBridge().parseScript(path, load); + } + + public boolean unloadScript(String path) { + return server.getAddonsLoader().getMockSkriptBridge().unloadScript(path); + } + + public void unloadAllScripts() { + server.getAddonsLoader().getMockSkriptBridge().unloadAllScripts(); + } + + private void extractEmbeddedAddons(AddonsLoader addonsLoader) { + if (flags.contains(AnalyzerFlag.SKIP_EXTRACTING_ADDONS)) { + logger.warn("{} flag is present! This means that default embedded addons (and Skript) won't be extracted. " + + "If you're not sure what may it cause, remove it immediately!", + AnalyzerFlag.SKIP_EXTRACTING_ADDONS.name() + ); + return; + } + logger.info("Extracting embedded addons..."); - extractEmbeddedAddon(AddonsLoader.MOCK_SKRIPT); - extractEmbeddedAddon(AddonsLoader.MOCK_SKRIPT_BRIDGE); + extractEmbeddedAddon(addonsLoader, AddonsLoader.MOCK_SKRIPT_FILE); + extractEmbeddedAddon(addonsLoader, AddonsLoader.MOCK_SKRIPT_BRIDGE_FILE); logger.info("Successfully extracted embedded addons!"); } - private void extractEmbeddedAddon(String name) { + private void extractEmbeddedAddon(AddonsLoader addonsLoader, String name) { try (InputStream embeddedJar = getClass().getClassLoader().getResourceAsStream(name + ".embedded")) { Preconditions.checkArgument(embeddedJar != null, "Couldn't find embedded %s", name); - FileUtils.copyInputStreamToFile(embeddedJar, new File(workingDirectory, name)); + FileUtils.copyInputStreamToFile(embeddedJar, new File(addonsLoader.getAddonsDirectory(), name)); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/api/src/main/java/me/glicz/skanalyzer/bridge/MockSkriptBridge.java b/api/src/main/java/me/glicz/skanalyzer/bridge/MockSkriptBridge.java index e1a991c..9541978 100644 --- a/api/src/main/java/me/glicz/skanalyzer/bridge/MockSkriptBridge.java +++ b/api/src/main/java/me/glicz/skanalyzer/bridge/MockSkriptBridge.java @@ -1,8 +1,8 @@ package me.glicz.skanalyzer.bridge; import lombok.AllArgsConstructor; -import me.glicz.skanalyzer.ScriptAnalyzeResult; import me.glicz.skanalyzer.SkAnalyzer; +import me.glicz.skanalyzer.result.ScriptAnalyzeResults; import org.bukkit.plugin.java.JavaPlugin; import java.util.concurrent.CompletableFuture; @@ -11,5 +11,9 @@ public abstract class MockSkriptBridge extends JavaPlugin { protected final SkAnalyzer skAnalyzer; - public abstract CompletableFuture parseScript(String path); + public abstract CompletableFuture parseScript(String path, boolean load); + + public abstract boolean unloadScript(String path); + + public abstract void unloadAllScripts(); } diff --git a/api/src/main/java/me/glicz/skanalyzer/error/ScriptError.java b/api/src/main/java/me/glicz/skanalyzer/error/ScriptError.java index 550cb22..b9bd460 100644 --- a/api/src/main/java/me/glicz/skanalyzer/error/ScriptError.java +++ b/api/src/main/java/me/glicz/skanalyzer/error/ScriptError.java @@ -1,6 +1,9 @@ package me.glicz.skanalyzer.error; +import com.google.gson.annotations.JsonAdapter; +import me.glicz.skanalyzer.util.LevelSerializer; + import java.util.logging.Level; -public record ScriptError(int line, String message, Level level) { +public record ScriptError(int line, String message, @JsonAdapter(LevelSerializer.class) Level level) { } diff --git a/api/src/main/java/me/glicz/skanalyzer/loader/AddonsLoader.java b/api/src/main/java/me/glicz/skanalyzer/loader/AddonsLoader.java index e9be7d2..af788e2 100644 --- a/api/src/main/java/me/glicz/skanalyzer/loader/AddonsLoader.java +++ b/api/src/main/java/me/glicz/skanalyzer/loader/AddonsLoader.java @@ -6,6 +6,7 @@ import me.glicz.skanalyzer.SkAnalyzer; import me.glicz.skanalyzer.bridge.MockSkriptBridge; import me.glicz.skanalyzer.mockbukkit.AnalyzerClassLoader; +import me.glicz.skanalyzer.mockbukkit.AnalyzerServer; import org.apache.commons.io.FileUtils; import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.java.JavaPlugin; @@ -19,32 +20,35 @@ @RequiredArgsConstructor public class AddonsLoader { - public static final File USER_HOME = new File(System.getProperty("user.home")); - public static final File ADDONS = new File(USER_HOME, "SkAnalyzer/addons"); - public static final String MOCK_SKRIPT = "MockSkript.jar"; - public static final String MOCK_SKRIPT_BRIDGE = "MockSkriptBridge.jar"; - - private static final Map addons = new HashMap<>(); + public static final String MOCK_SKRIPT_FILE = "MockSkript.jar"; + public static final String MOCK_SKRIPT_BRIDGE_FILE = "MockSkriptBridge.jar"; + private final Map addons = new HashMap<>(); private final SkAnalyzer skAnalyzer; + private final AnalyzerServer server; private JavaPlugin skript; @Getter private MockSkriptBridge mockSkriptBridge; + public File getAddonsDirectory() { + return new File(skAnalyzer.getWorkingDirectory(), "Addons"); + } + @SuppressWarnings({"deprecation"}) public void loadAddons() { - if (skript != null) + if (skript != null) { throw new RuntimeException("Addons are already loaded!"); + } - skript = Objects.requireNonNull(initSimpleAddon(new File(skAnalyzer.getWorkingDirectory(), MOCK_SKRIPT))); + skript = Objects.requireNonNull(initSimpleAddon(new File(getAddonsDirectory(), MOCK_SKRIPT_FILE))); loadAddon(skript); - skAnalyzer.getServer().getPluginManager().enablePlugin(skript); + server.getPluginManager().enablePlugin(skript); mockSkriptBridge = Objects.requireNonNull(initMockSkriptBridge()); loadAddon(mockSkriptBridge); - skAnalyzer.getServer().getPluginManager().enablePlugin(mockSkriptBridge); + server.getPluginManager().enablePlugin(mockSkriptBridge); - FileUtils.listFiles(skAnalyzer.getWorkingDirectory(), new String[]{"jar"}, false).forEach(this::initSimpleAddon); + FileUtils.listFiles(getAddonsDirectory(), new String[]{"jar"}, false).forEach(this::initSimpleAddon); addons.values().forEach(addon -> { try { @@ -55,7 +59,7 @@ public void loadAddons() { } }); - addons.values().forEach(addon -> skAnalyzer.getServer().getPluginManager().enablePlugin(addon)); + addons.values().forEach(addon -> server.getPluginManager().enablePlugin(addon)); skAnalyzer.getLogger().info( "Successfully loaded addons: {}", @@ -82,7 +86,7 @@ private JavaPlugin initSimpleAddon(File file) { } private MockSkriptBridge initMockSkriptBridge() { - File file = new File(skAnalyzer.getWorkingDirectory(), MOCK_SKRIPT_BRIDGE); + File file = new File(getAddonsDirectory(), MOCK_SKRIPT_BRIDGE_FILE); Class pluginClass = initAddon(file); if (pluginClass == null) return null; @@ -100,26 +104,28 @@ private MockSkriptBridge initMockSkriptBridge() { @SuppressWarnings("UnstableApiUsage") private Class initAddon(File file) { - if (skript != null && file.getName().equals(MOCK_SKRIPT)) return null; - if (mockSkriptBridge != null && file.getName().equals(MOCK_SKRIPT_BRIDGE)) return null; + if (skript != null && file.getName().equals(MOCK_SKRIPT_FILE)) return null; + if (mockSkriptBridge != null && file.getName().equals(MOCK_SKRIPT_BRIDGE_FILE)) return null; try { JarFile jarFile = new JarFile(file); PluginDescriptionFile description = new PluginDescriptionFile(jarFile.getInputStream(jarFile.getEntry("plugin.yml"))); - if (addons.containsKey(description.getName())) + if (addons.containsKey(description.getName())) { throw new RuntimeException("Plugin named '%s' is already loaded".formatted(description.getName())); + } AnalyzerClassLoader classLoader = new AnalyzerClassLoader( SkAnalyzer.class.getClassLoader(), description, - new File(skAnalyzer.getWorkingDirectory(), description.getName()), + new File(getAddonsDirectory(), description.getName()), file, jarFile ); - if (skript != null) + if (skript != null) { classLoader.getGroup().add((ConfiguredPluginClassLoader) skript.getClass().getClassLoader()); + } return classLoader.loadClass(description.getMainClass(), true, false, false); } catch (Exception | ExceptionInInitializerError e) { @@ -131,27 +137,30 @@ private Class initAddon(File file) { @SuppressWarnings({"deprecation", "UnstableApiUsage"}) private void loadAddon(JavaPlugin addon) { - if (skAnalyzer.getServer().getPluginManager().getPlugin(addon.getName()) != null) return; + if (server.getPluginManager().getPlugin(addon.getName()) != null) return; AnalyzerClassLoader classLoader = (AnalyzerClassLoader) addon.getClass().getClassLoader(); addon.getDescription().getDepend().forEach(depend -> { - if (!addons.containsKey(depend)) + if (!addons.containsKey(depend)) { throw new NullPointerException("Missing dependency: " + depend); + } - if (depend.equals("Skript")) + if (depend.equals("Skript")) { return; + } classLoader.getGroup().add((ConfiguredPluginClassLoader) addons.get(depend).getClass().getClassLoader()); }); addon.getDescription().getSoftDepend().forEach(softDepend -> { - if (!addons.containsKey(softDepend) || softDepend.equals("Skript")) + if (!addons.containsKey(softDepend) || softDepend.equals("Skript")) { return; + } classLoader.getGroup().add((ConfiguredPluginClassLoader) addons.get(softDepend).getClass().getClassLoader()); }); - skAnalyzer.getServer().getPluginManager().registerLoadedPlugin(addon); + server.getPluginManager().registerLoadedPlugin(addon); } } diff --git a/api/src/main/java/me/glicz/skanalyzer/mockbukkit/AnalyzerClassLoader.java b/api/src/main/java/me/glicz/skanalyzer/mockbukkit/AnalyzerClassLoader.java index 7bbe672..a34e6ea 100644 --- a/api/src/main/java/me/glicz/skanalyzer/mockbukkit/AnalyzerClassLoader.java +++ b/api/src/main/java/me/glicz/skanalyzer/mockbukkit/AnalyzerClassLoader.java @@ -65,15 +65,17 @@ public Class loadClass(@NotNull String name, boolean resolve, boolean checkGl private Class loadClass0(String name, boolean resolve, boolean checkGlobal) throws ClassNotFoundException { try { Class result = super.loadClass(name, resolve); - if (checkGlobal || result.getClassLoader() == this) + if (checkGlobal || result.getClassLoader() == this) { return result; + } } catch (ClassNotFoundException ignored) { } if (checkGlobal) { Class result = group.getClassByName(name, resolve, this); - if (result != null) + if (result != null) { return result; + } } throw new ClassNotFoundException(name); @@ -104,15 +106,18 @@ protected Class findClass(String name) throws ClassNotFoundException { String pkgName = name.substring(0, dot); if (getDefinedPackage(pkgName) == null) { try { - if (manifest != null) + if (manifest != null) { definePackage(pkgName, manifest, url); - else definePackage( - pkgName, null, null, null, - null, null, null, null - ); + } else { + definePackage( + pkgName, null, null, null, + null, null, null, null + ); + } } catch (IllegalArgumentException ex) { - if (getDefinedPackage(pkgName) == null) + if (getDefinedPackage(pkgName) == null) { throw new IllegalStateException("Cannot find package " + pkgName); + } } } } @@ -123,8 +128,9 @@ else definePackage( result = defineClass(name, classBytes, 0, classBytes.length, source); } - if (result == null) + if (result == null) { result = super.findClass(name); + } classes.put(name, result); } diff --git a/api/src/main/java/me/glicz/skanalyzer/mockbukkit/AnalyzerServer.java b/api/src/main/java/me/glicz/skanalyzer/mockbukkit/AnalyzerServer.java index bf6bc5c..83af7a3 100644 --- a/api/src/main/java/me/glicz/skanalyzer/mockbukkit/AnalyzerServer.java +++ b/api/src/main/java/me/glicz/skanalyzer/mockbukkit/AnalyzerServer.java @@ -3,27 +3,54 @@ import be.seeseemelk.mockbukkit.ServerMock; import be.seeseemelk.mockbukkit.scheduler.BukkitSchedulerMock; import lombok.Getter; +import me.glicz.skanalyzer.SkAnalyzer; +import me.glicz.skanalyzer.loader.AddonsLoader; +import net.kyori.adventure.util.Ticks; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.block.data.BlockData; import org.jetbrains.annotations.NotNull; -import java.util.Timer; -import java.util.TimerTask; - @Getter public class AnalyzerServer extends ServerMock { private final AnalyzerUnsafeValues unsafe = new AnalyzerUnsafeValues(); private final AnalyzerStructureManager structureManager = new AnalyzerStructureManager(); private final AnalyzerPotionBrewer potionBrewer = new AnalyzerPotionBrewer(); + private final SkAnalyzer skAnalyzer; + private final AddonsLoader addonsLoader; + + public AnalyzerServer(SkAnalyzer skAnalyzer) { + this.skAnalyzer = skAnalyzer; + this.addonsLoader = new AddonsLoader(skAnalyzer, this); + } + @SuppressWarnings({"InfiniteLoopStatement", "BusyWait"}) public void startTicking() { - new Timer().scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { + try { + while (true) { + long start = System.currentTimeMillis(); + ((BukkitSchedulerMock) Bukkit.getScheduler()).performOneTick(); + + long end = System.currentTimeMillis(); + long duration = end - start; + + long sleepTime = Ticks.SINGLE_TICK_DURATION_MS - duration; + if (sleepTime > 0) { + try { + Thread.sleep(sleepTime); + } catch (InterruptedException ex) { + skAnalyzer.getLogger().atError() + .withThrowable(ex) + .log("Something went wrong while trying to wait until next tick"); + } + } } - }, 50, 50); + } catch (Exception ex) { + skAnalyzer.getLogger().atError() + .withThrowable(ex) + .log("Something went wrong while trying to tick"); + } } @Override @@ -31,17 +58,15 @@ public void run() { return "SkAnalyzer"; } - @SuppressWarnings("DataFlowIssue") @Override public @NotNull BlockData createBlockData(String data) { - if (data.contains(":")) - data = data.split(":")[1]; String rawMaterial = (data.indexOf('[') == -1) ? data : data.substring(0, data.indexOf('[')); - Material material = Material.getMaterial(rawMaterial.toUpperCase()); - if (material == null) - return null; + Material material = Material.matchMaterial(rawMaterial); + if (material == null) { + throw new IllegalArgumentException(); + } return createBlockData(material); } diff --git a/api/src/main/java/me/glicz/skanalyzer/mockbukkit/AnalyzerUnsafeValues.java b/api/src/main/java/me/glicz/skanalyzer/mockbukkit/AnalyzerUnsafeValues.java index 8a9432a..508187d 100644 --- a/api/src/main/java/me/glicz/skanalyzer/mockbukkit/AnalyzerUnsafeValues.java +++ b/api/src/main/java/me/glicz/skanalyzer/mockbukkit/AnalyzerUnsafeValues.java @@ -1,22 +1,12 @@ package me.glicz.skanalyzer.mockbukkit; import be.seeseemelk.mockbukkit.MockUnsafeValues; -import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; import org.bukkit.inventory.ItemStack; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.java.JavaPlugin; -import java.util.function.BooleanSupplier; - -@SuppressWarnings({"deprecation", "UnstableApiUsage"}) +@SuppressWarnings("deprecation") public class AnalyzerUnsafeValues extends MockUnsafeValues { @Override public ItemStack modifyItemStack(ItemStack stack, String arguments) { return stack; } - - @Override - public LifecycleEventManager createPluginLifecycleEventManager(JavaPlugin javaPlugin, BooleanSupplier booleanSupplier) { - return null; - } } diff --git a/api/src/main/java/me/glicz/skanalyzer/result/ScriptAnalyzeResult.java b/api/src/main/java/me/glicz/skanalyzer/result/ScriptAnalyzeResult.java new file mode 100644 index 0000000..60caa88 --- /dev/null +++ b/api/src/main/java/me/glicz/skanalyzer/result/ScriptAnalyzeResult.java @@ -0,0 +1,25 @@ +package me.glicz.skanalyzer.result; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import me.glicz.skanalyzer.error.ScriptError; +import me.glicz.skanalyzer.structure.ScriptStructure; +import org.jetbrains.annotations.Unmodifiable; + +import java.io.File; +import java.util.List; + +public record ScriptAnalyzeResult(File file, List errors, ScriptStructure structure) { + private static final Gson GSON = new Gson(); + + @Override + public @Unmodifiable List errors() { + return List.copyOf(errors); + } + + public JsonObject toJsonObject() { + JsonObject jsonObject = GSON.toJsonTree(structure).getAsJsonObject(); + jsonObject.add("errors", GSON.toJsonTree(errors)); + return jsonObject; + } +} diff --git a/api/src/main/java/me/glicz/skanalyzer/result/ScriptAnalyzeResults.java b/api/src/main/java/me/glicz/skanalyzer/result/ScriptAnalyzeResults.java new file mode 100644 index 0000000..35cb140 --- /dev/null +++ b/api/src/main/java/me/glicz/skanalyzer/result/ScriptAnalyzeResults.java @@ -0,0 +1,32 @@ +package me.glicz.skanalyzer.result; + +import com.google.gson.JsonObject; +import org.jetbrains.annotations.Unmodifiable; + +import java.io.File; +import java.io.IOException; +import java.util.Map; + +public record ScriptAnalyzeResults(Map results) { + @Override + public @Unmodifiable Map results() { + return Map.copyOf(results); + } + + public String jsonResult() { + JsonObject jsonObject = new JsonObject(); + results.forEach((file, result) -> jsonObject.add( + canonicalPath(file).replace('\\', '/'), + result.toJsonObject() + )); + return jsonObject.toString(); + } + + private String canonicalPath(File file) { + try { + return file.getCanonicalPath(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/api/src/main/java/me/glicz/skanalyzer/structure/ScriptStructure.java b/api/src/main/java/me/glicz/skanalyzer/structure/ScriptStructure.java index 3474489..de17f3f 100644 --- a/api/src/main/java/me/glicz/skanalyzer/structure/ScriptStructure.java +++ b/api/src/main/java/me/glicz/skanalyzer/structure/ScriptStructure.java @@ -5,29 +5,25 @@ import me.glicz.skanalyzer.structure.data.FunctionData; import org.jetbrains.annotations.Unmodifiable; -import java.util.Collections; import java.util.List; import java.util.Map; -public record ScriptStructure(List commandDataList, List eventDataList, - List functionDataList, Map options) { - @Override - public @Unmodifiable List eventDataList() { - return Collections.unmodifiableList(eventDataList); +public record ScriptStructure(List commands, List events, + List functions, Map options) { + public @Unmodifiable List events() { + return List.copyOf(events); } - @Override - public @Unmodifiable List functionDataList() { - return Collections.unmodifiableList(functionDataList); + public @Unmodifiable List functions() { + return List.copyOf(functions); } - @Override - public @Unmodifiable List commandDataList() { - return Collections.unmodifiableList(commandDataList); + public @Unmodifiable List commands() { + return List.copyOf(commands); } @Override public @Unmodifiable Map options() { - return Collections.unmodifiableMap(options); + return Map.copyOf(options); } } diff --git a/api/src/main/java/me/glicz/skanalyzer/structure/data/CommandData.java b/api/src/main/java/me/glicz/skanalyzer/structure/data/CommandData.java index 17e0ee0..66bfa0f 100644 --- a/api/src/main/java/me/glicz/skanalyzer/structure/data/CommandData.java +++ b/api/src/main/java/me/glicz/skanalyzer/structure/data/CommandData.java @@ -1,7 +1,12 @@ package me.glicz.skanalyzer.structure.data; +import lombok.Getter; +import lombok.experimental.Accessors; + import java.util.List; +@Getter +@Accessors(fluent = true) public final class CommandData extends StructureData { private final List aliases; private final String permission; diff --git a/api/src/main/java/me/glicz/skanalyzer/structure/data/EventData.java b/api/src/main/java/me/glicz/skanalyzer/structure/data/EventData.java index a6b2793..9672d8c 100644 --- a/api/src/main/java/me/glicz/skanalyzer/structure/data/EventData.java +++ b/api/src/main/java/me/glicz/skanalyzer/structure/data/EventData.java @@ -1,7 +1,11 @@ package me.glicz.skanalyzer.structure.data; +import lombok.Getter; +import lombok.experimental.Accessors; import org.bukkit.event.EventPriority; +@Getter +@Accessors(fluent = true) public final class EventData extends StructureData { private final String id; private final EventPriority eventPriority; diff --git a/api/src/main/java/me/glicz/skanalyzer/structure/data/FunctionData.java b/api/src/main/java/me/glicz/skanalyzer/structure/data/FunctionData.java index 099cd97..eafba16 100644 --- a/api/src/main/java/me/glicz/skanalyzer/structure/data/FunctionData.java +++ b/api/src/main/java/me/glicz/skanalyzer/structure/data/FunctionData.java @@ -1,7 +1,12 @@ package me.glicz.skanalyzer.structure.data; +import lombok.Getter; +import lombok.experimental.Accessors; + import java.util.Map; +@Getter +@Accessors(fluent = true) public final class FunctionData extends StructureData { private final boolean local; private final Map parameters; diff --git a/api/src/main/java/me/glicz/skanalyzer/structure/data/StructureData.java b/api/src/main/java/me/glicz/skanalyzer/structure/data/StructureData.java index 42e8c6a..2c4fbb1 100644 --- a/api/src/main/java/me/glicz/skanalyzer/structure/data/StructureData.java +++ b/api/src/main/java/me/glicz/skanalyzer/structure/data/StructureData.java @@ -1,5 +1,10 @@ package me.glicz.skanalyzer.structure.data; +import lombok.Getter; +import lombok.experimental.Accessors; + +@Getter +@Accessors(fluent = true) public sealed class StructureData permits CommandData, EventData, FunctionData { private final int line; private final String value; diff --git a/api/src/main/java/me/glicz/skanalyzer/util/LevelSerializer.java b/api/src/main/java/me/glicz/skanalyzer/util/LevelSerializer.java new file mode 100644 index 0000000..e343261 --- /dev/null +++ b/api/src/main/java/me/glicz/skanalyzer/util/LevelSerializer.java @@ -0,0 +1,16 @@ +package me.glicz.skanalyzer.util; + +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import java.lang.reflect.Type; +import java.util.logging.Level; + +public class LevelSerializer implements JsonSerializer { + @Override + public JsonElement serialize(Level level, Type type, JsonSerializationContext ctx) { + return new JsonPrimitive(level.getName()); + } +} \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 11153ca..7bb2440 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,25 +1,16 @@ plugins { - id("java") - id("com.github.johnrengelman.shadow") version "8.1.1" + id("io.github.goooler.shadow") } dependencies { + compileOnly("org.projectlombok:lombok:1.18.32") + annotationProcessor("org.projectlombok:lombok:1.18.32") implementation(project(":api", "shadow")) } -java { - toolchain.languageVersion.set(JavaLanguageVersion.of(17)) -} - tasks { - compileJava { - options.encoding = Charsets.UTF_8.name() - options.release.set(17) - dependsOn(clean) - } - shadowJar { - manifest.attributes["Main-Class"] = "me.glicz.skanalyzer.Main" + manifest.attributes["Main-Class"] = "me.glicz.skanalyzer.app.SkAnalyzerApp" manifest.attributes["Specification-Version"] = version archiveBaseName = rootProject.name diff --git a/app/src/main/java/me/glicz/skanalyzer/Main.java b/app/src/main/java/me/glicz/skanalyzer/Main.java deleted file mode 100644 index e64ada5..0000000 --- a/app/src/main/java/me/glicz/skanalyzer/Main.java +++ /dev/null @@ -1,44 +0,0 @@ -package me.glicz.skanalyzer; - -import java.util.Arrays; -import java.util.Objects; -import java.util.Scanner; - -public class Main { - public static void main(String[] args) { - System.out.printf("SkAnalyzer v%s - simple Skript parser. Created by Glicz.%n", Main.class.getPackage().getSpecificationVersion()); - SkAnalyzer skAnalyzer = SkAnalyzer.builder() - .flags(parseFlags(args)) - .build(); - startReadingInput(skAnalyzer); - } - - private static AnalyzerFlag[] parseFlags(String[] args) { - return Arrays.stream(args) - .map(AnalyzerFlag::getByArg) - .filter(Objects::nonNull) - .toArray(AnalyzerFlag[]::new); - } - - private static void startReadingInput(SkAnalyzer skAnalyzer) { - Thread thread = new Thread() { - private final Scanner scanner = new Scanner(System.in); - - @Override - public void run() { - while (!Thread.interrupted()) { - if (scanner.hasNext()) { - String line = scanner.nextLine(); - if (line != null) { - if (line.trim().equals("exit")) - System.exit(0); - skAnalyzer.parseScript(line); - } - } - } - } - }; - thread.setDaemon(true); - thread.start(); - } -} diff --git a/app/src/main/java/me/glicz/skanalyzer/app/SkAnalyzerApp.java b/app/src/main/java/me/glicz/skanalyzer/app/SkAnalyzerApp.java new file mode 100644 index 0000000..9299669 --- /dev/null +++ b/app/src/main/java/me/glicz/skanalyzer/app/SkAnalyzerApp.java @@ -0,0 +1,94 @@ +package me.glicz.skanalyzer.app; + +import lombok.Getter; +import lombok.experimental.Accessors; +import me.glicz.skanalyzer.AnalyzerFlag; +import me.glicz.skanalyzer.SkAnalyzer; +import me.glicz.skanalyzer.app.command.*; +import me.glicz.skanalyzer.app.registry.CommandRegistry; + +import java.util.Arrays; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Scanner; + +@Getter +@Accessors(fluent = true) +public class SkAnalyzerApp { + public static final String PARENT_PROCESS_PROPERTY = "skanalyzer.parentProcess"; + + private final SkAnalyzer skAnalyzer; + private final CommandRegistry commandRegistry; + + public SkAnalyzerApp(String[] args) { + System.out.printf("SkAnalyzer v%s - simple Skript parser. Created by Glicz.%n", getClass().getPackage().getSpecificationVersion()); + + String parentProcess = System.getProperty(PARENT_PROCESS_PROPERTY); + if (parentProcess != null) { + try { + long pid = Long.parseLong(parentProcess); + ProcessHandle processHandle = ProcessHandle.of(pid).orElseThrow(); + processHandle.onExit().thenRun(() -> + System.exit(0) + ); + } catch (NumberFormatException | NoSuchElementException ex) { + System.err.printf("Invalid parent process: %s%n", parentProcess); + } + } + + this.skAnalyzer = SkAnalyzer.builder() + .flags(parseFlags(args)) + .build(); + this.commandRegistry = new CommandRegistry(); + + this.skAnalyzer.start().thenRun(() -> { + this.commandRegistry.register(new ExitCommand(this)); + this.commandRegistry.register(new HelpCommand(this)); + this.commandRegistry.register(new ParseCommand(this)); + this.commandRegistry.register(new LoadCommand(this)); + this.commandRegistry.register(new ParseCommand(this)); + this.commandRegistry.register(new UnloadCommand(this)); + + this.skAnalyzer.getLogger().info("Type 'help' for help."); + + startReadingInput(); + }); + } + + public static void main(String[] args) { + new SkAnalyzerApp(args); + } + + private AnalyzerFlag[] parseFlags(String[] args) { + return Arrays.stream(args) + .map(AnalyzerFlag::getByArg) + .filter(Objects::nonNull) + .toArray(AnalyzerFlag[]::new); + } + + private void startReadingInput() { + Thread thread = new Thread() { + private final Scanner scanner = new Scanner(System.in); + + @Override + public void run() { + while (!Thread.interrupted()) { + if (scanner.hasNext()) { + String line = scanner.nextLine(); + if (line != null) { + if (line.isBlank()) return; + + String[] args = line.split(" "); + commandRegistry.getCommand(args[0]).ifPresentOrElse( + command -> command.execute(Arrays.copyOfRange(args, 1, args.length)), + () -> skAnalyzer.getLogger().error("Unknown command: {}", args[0]) + ); + } + } + } + } + }; + thread.setDaemon(true); + thread.start(); + } +} diff --git a/app/src/main/java/me/glicz/skanalyzer/app/command/Command.java b/app/src/main/java/me/glicz/skanalyzer/app/command/Command.java new file mode 100644 index 0000000..31455bc --- /dev/null +++ b/app/src/main/java/me/glicz/skanalyzer/app/command/Command.java @@ -0,0 +1,17 @@ +package me.glicz.skanalyzer.app.command; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.experimental.Accessors; +import me.glicz.skanalyzer.app.SkAnalyzerApp; + +@Getter +@Accessors(fluent = true) +@AllArgsConstructor +public abstract class Command { + protected final SkAnalyzerApp app; + protected final String name; + protected final String description; + + public abstract void execute(String[] args); +} diff --git a/app/src/main/java/me/glicz/skanalyzer/app/command/ExitCommand.java b/app/src/main/java/me/glicz/skanalyzer/app/command/ExitCommand.java new file mode 100644 index 0000000..2f3125b --- /dev/null +++ b/app/src/main/java/me/glicz/skanalyzer/app/command/ExitCommand.java @@ -0,0 +1,14 @@ +package me.glicz.skanalyzer.app.command; + +import me.glicz.skanalyzer.app.SkAnalyzerApp; + +public class ExitCommand extends Command { + public ExitCommand(SkAnalyzerApp app) { + super(app, "exit", "Exits the program"); + } + + @Override + public void execute(String[] args) { + System.exit(0); + } +} diff --git a/app/src/main/java/me/glicz/skanalyzer/app/command/HelpCommand.java b/app/src/main/java/me/glicz/skanalyzer/app/command/HelpCommand.java new file mode 100644 index 0000000..f1b59ad --- /dev/null +++ b/app/src/main/java/me/glicz/skanalyzer/app/command/HelpCommand.java @@ -0,0 +1,16 @@ +package me.glicz.skanalyzer.app.command; + +import me.glicz.skanalyzer.app.SkAnalyzerApp; + +public class HelpCommand extends Command { + public HelpCommand(SkAnalyzerApp app) { + super(app, "help", "Displays help"); + } + + @Override + public void execute(String[] args) { + app.commandRegistry().getCommands().forEach(command -> + app.skAnalyzer().getLogger().info("{} - {}", command.name, command.description) + ); + } +} diff --git a/app/src/main/java/me/glicz/skanalyzer/app/command/LoadCommand.java b/app/src/main/java/me/glicz/skanalyzer/app/command/LoadCommand.java new file mode 100644 index 0000000..57d5693 --- /dev/null +++ b/app/src/main/java/me/glicz/skanalyzer/app/command/LoadCommand.java @@ -0,0 +1,21 @@ +package me.glicz.skanalyzer.app.command; + +import me.glicz.skanalyzer.app.SkAnalyzerApp; + +public class LoadCommand extends Command { + public LoadCommand(SkAnalyzerApp app) { + super(app, "load", "Loads specified script(s)"); + } + + @Override + public void execute(String[] args) { + if (args.length < 1) { + app.skAnalyzer().getLogger().error("You need to specify file path"); + return; + } + + app.skAnalyzer().parseScript(String.join(" ", args), true).thenAccept(results -> + app.skAnalyzer().getLogger().info(results.jsonResult()) + ); + } +} diff --git a/app/src/main/java/me/glicz/skanalyzer/app/command/ParseCommand.java b/app/src/main/java/me/glicz/skanalyzer/app/command/ParseCommand.java new file mode 100644 index 0000000..50b7ed3 --- /dev/null +++ b/app/src/main/java/me/glicz/skanalyzer/app/command/ParseCommand.java @@ -0,0 +1,21 @@ +package me.glicz.skanalyzer.app.command; + +import me.glicz.skanalyzer.app.SkAnalyzerApp; + +public class ParseCommand extends Command { + public ParseCommand(SkAnalyzerApp app) { + super(app, "parse", "Parses specified script"); + } + + @Override + public void execute(String[] args) { + if (args.length < 1) { + app.skAnalyzer().getLogger().error("You need to specify file path"); + return; + } + + app.skAnalyzer().parseScript(String.join(" ", args)).thenAccept(results -> + app.skAnalyzer().getLogger().info(results.jsonResult()) + ); + } +} diff --git a/app/src/main/java/me/glicz/skanalyzer/app/command/UnloadCommand.java b/app/src/main/java/me/glicz/skanalyzer/app/command/UnloadCommand.java new file mode 100644 index 0000000..c6689a6 --- /dev/null +++ b/app/src/main/java/me/glicz/skanalyzer/app/command/UnloadCommand.java @@ -0,0 +1,26 @@ +package me.glicz.skanalyzer.app.command; + +import me.glicz.skanalyzer.app.SkAnalyzerApp; + +public class UnloadCommand extends Command { + public UnloadCommand(SkAnalyzerApp app) { + super(app, "unload", "Unloads specified script(s)"); + } + + @Override + public void execute(String[] args) { + if (args.length < 1) { + app.skAnalyzer().getLogger().error("You need to specify file path"); + return; + } + + if (args[0].equals("*")) { + app.skAnalyzer().unloadAllScripts(); + app.skAnalyzer().getLogger().info("Successfully unloaded all scripts"); + } else { + if (app.skAnalyzer().unloadScript(String.join(" ", args))) { + app.skAnalyzer().getLogger().info("Successfully unloaded this script"); + } + } + } +} diff --git a/app/src/main/java/me/glicz/skanalyzer/app/registry/CommandRegistry.java b/app/src/main/java/me/glicz/skanalyzer/app/registry/CommandRegistry.java new file mode 100644 index 0000000..bb25b53 --- /dev/null +++ b/app/src/main/java/me/glicz/skanalyzer/app/registry/CommandRegistry.java @@ -0,0 +1,26 @@ +package me.glicz.skanalyzer.app.registry; + +import me.glicz.skanalyzer.app.command.Command; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class CommandRegistry { + private final Map commandMap = new HashMap<>(); + + public void register(Command command) { + commandMap.put(command.name(), command); + } + + public Optional getCommand(String name) { + return Optional.ofNullable(commandMap.get(name)); + } + + @Unmodifiable + public List getCommands() { + return List.copyOf(commandMap.values()); + } +} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..e8db9ca --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,26 @@ +plugins { + id("java") + id("io.github.goooler.shadow") version "8.1.7" apply false +} + +configure(subprojects.filter { it.name != "MockSkript" }) { + plugins.apply("java") + + repositories { + mavenCentral() + maven("https://jitpack.io") + maven("https://repo.papermc.io/repository/maven-public/") + } + + java { + toolchain.languageVersion = JavaLanguageVersion.of(21) + } + + tasks { + withType { + options.encoding = Charsets.UTF_8.name() + options.release = 21 + dependsOn(clean) + } + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index f2098f1..3ee6784 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=1.3 +version=1.4 group=me.glicz org.gradle.caching=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 033e24c..e644113 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a80b22c..b82aa23 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index fcb6fca..1aa94a4 100644 --- a/gradlew +++ b/gradlew @@ -83,7 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -201,11 +202,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 93e3f59..25da30d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail