diff --git a/settings.gradle b/settings.gradle index 70753910..6931c9f5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -25,6 +25,7 @@ include ( 'spark-api', 'spark-common', 'spark-bukkit', + 'spark-paper', 'spark-bungeecord', 'spark-velocity', 'spark-velocity4', diff --git a/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java b/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java index 21f92103..733510dc 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java +++ b/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java @@ -55,6 +55,7 @@ import me.lucko.spark.common.util.Configuration; import me.lucko.spark.common.util.SparkStaticLogger; import me.lucko.spark.common.util.TemporaryFiles; +import me.lucko.spark.common.util.classfinder.ClassFinder; import me.lucko.spark.common.ws.TrustedKeyStore; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; @@ -285,6 +286,10 @@ public ClassSourceLookup createClassSourceLookup() { return this.plugin.createClassSourceLookup(); } + public ClassFinder createClassFinder() { + return this.plugin.createClassFinder(); + } + public TickStatistics getTickStatistics() { return this.tickStatistics; } diff --git a/spark-common/src/main/java/me/lucko/spark/common/SparkPlugin.java b/spark-common/src/main/java/me/lucko/spark/common/SparkPlugin.java index a3bdceb2..bf745dfd 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/SparkPlugin.java +++ b/spark-common/src/main/java/me/lucko/spark/common/SparkPlugin.java @@ -33,6 +33,9 @@ import me.lucko.spark.common.sampler.source.SourceMetadata; import me.lucko.spark.common.tick.TickHook; import me.lucko.spark.common.tick.TickReporter; +import me.lucko.spark.common.util.classfinder.ClassFinder; +import me.lucko.spark.common.util.classfinder.FallbackClassFinder; +import me.lucko.spark.common.util.classfinder.InstrumentationClassFinder; import java.nio.file.Path; import java.util.Collection; @@ -149,6 +152,18 @@ default ClassSourceLookup createClassSourceLookup() { return ClassSourceLookup.NO_OP; } + /** + * Creates a class finder for the platform. + * + * @return the class finder + */ + default ClassFinder createClassFinder() { + return ClassFinder.combining( + new InstrumentationClassFinder(this), + FallbackClassFinder.INSTANCE + ); + } + /** * Gets a list of known sources (plugins/mods) on the platform. * diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java index 9ce66dc6..9e2647a0 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java +++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java @@ -507,7 +507,7 @@ private Sampler.ExportProps getExportProps(SparkPlatform platform, CommandRespon .creator(resp.senderData()) .comment(Iterables.getFirst(arguments.stringFlag("comment"), null)) .mergeMode(() -> { - MethodDisambiguator methodDisambiguator = new MethodDisambiguator(); + MethodDisambiguator methodDisambiguator = new MethodDisambiguator(platform.createClassFinder()); return arguments.boolFlag("separate-parent-calls") ? MergeMode.separateParentCalls(methodDisambiguator) : MergeMode.sameMethod(methodDisambiguator); diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/AbstractSampler.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/AbstractSampler.java index 445702ef..8a9c05f3 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/sampler/AbstractSampler.java +++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/AbstractSampler.java @@ -32,6 +32,7 @@ import me.lucko.spark.common.sampler.source.SourceMetadata; import me.lucko.spark.common.sampler.window.ProtoTimeEncoder; import me.lucko.spark.common.sampler.window.WindowStatisticsCollector; +import me.lucko.spark.common.util.classfinder.ClassFinder; import me.lucko.spark.common.ws.ViewerSocket; import me.lucko.spark.proto.SparkProtos; import me.lucko.spark.proto.SparkSamplerProtos.SamplerData; @@ -44,6 +45,7 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Supplier; /** * Base implementation class for {@link Sampler}s. @@ -230,11 +232,11 @@ protected void writeMetadataToProto(SamplerData.Builder proto, SparkPlatform pla proto.setMetadata(metadata); } - protected void writeDataToProto(SamplerData.Builder proto, DataAggregator dataAggregator, MergeMode mergeMode, ClassSourceLookup classSourceLookup) { + protected void writeDataToProto(SamplerData.Builder proto, DataAggregator dataAggregator, MergeMode mergeMode, ClassSourceLookup classSourceLookup, Supplier classFinderSupplier) { List data = dataAggregator.exportData(); data.sort(Comparator.comparing(ThreadNode::getThreadLabel)); - ClassSourceLookup.Visitor classSourceVisitor = ClassSourceLookup.createVisitor(classSourceLookup); + ClassSourceLookup.Visitor classSourceVisitor = ClassSourceLookup.createVisitor(classSourceLookup, classFinderSupplier); ProtoTimeEncoder timeEncoder = new ProtoTimeEncoder(getMode().valueTransformer(), data); int[] timeWindows = timeEncoder.getKeys(); diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/async/AsyncSampler.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/async/AsyncSampler.java index e3470b4b..5350558e 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/sampler/async/AsyncSampler.java +++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/async/AsyncSampler.java @@ -221,7 +221,7 @@ public SamplerData toProto(SparkPlatform platform, ExportProps exportProps) { proto.setChannelInfo(exportProps.channelInfo()); } writeMetadataToProto(proto, platform, exportProps.creator(), exportProps.comment(), this.dataAggregator); - writeDataToProto(proto, this.dataAggregator, exportProps.mergeMode().get(), exportProps.classSourceLookup().get()); + writeDataToProto(proto, this.dataAggregator, exportProps.mergeMode().get(), exportProps.classSourceLookup().get(), platform::createClassFinder); return proto.build(); } diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/java/JavaSampler.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/java/JavaSampler.java index bae7e190..e3ae73a0 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/sampler/java/JavaSampler.java +++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/java/JavaSampler.java @@ -193,7 +193,7 @@ public SamplerData toProto(SparkPlatform platform, ExportProps exportProps) { proto.setChannelInfo(exportProps.channelInfo()); } writeMetadataToProto(proto, platform, exportProps.creator(), exportProps.comment(), this.dataAggregator); - writeDataToProto(proto, this.dataAggregator, exportProps.mergeMode().get(), exportProps.classSourceLookup().get()); + writeDataToProto(proto, this.dataAggregator, exportProps.mergeMode().get(), exportProps.classSourceLookup().get(), platform::createClassFinder); return proto.build(); } diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/source/ClassSourceLookup.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/source/ClassSourceLookup.java index a62f8d14..c2173d3b 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/sampler/source/ClassSourceLookup.java +++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/source/ClassSourceLookup.java @@ -23,7 +23,8 @@ import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.sampler.node.StackTraceNode; import me.lucko.spark.common.sampler.node.ThreadNode; -import me.lucko.spark.common.util.ClassFinder; +import me.lucko.spark.common.util.classfinder.ClassFinder; +import me.lucko.spark.common.util.classfinder.InstrumentationClassFinder; import org.checkerframework.checker.nullness.qual.Nullable; import java.io.IOException; @@ -42,6 +43,7 @@ import java.util.Objects; import java.util.Queue; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; /** @@ -203,11 +205,11 @@ interface Visitor { Map getLineSourceMapping(); } - static Visitor createVisitor(ClassSourceLookup lookup) { + static Visitor createVisitor(ClassSourceLookup lookup, Supplier classFinderSupplier) { if (lookup == ClassSourceLookup.NO_OP) { return NoOpVisitor.INSTANCE; // don't bother! } - return new VisitorImpl(lookup); + return new VisitorImpl(lookup, classFinderSupplier.get()); } enum NoOpVisitor implements Visitor { @@ -254,14 +256,15 @@ public Map getLineSourceMapping() { */ class VisitorImpl implements Visitor { private final ClassSourceLookup lookup; - private final ClassFinder classFinder = new ClassFinder(); + private final ClassFinder classFinder; private final SourcesMap classSources = new SourcesMap<>(Function.identity()); private final SourcesMap methodSources = new SourcesMap<>(MethodCall::toString); private final SourcesMap lineSources = new SourcesMap<>(MethodCallByLine::toString); - VisitorImpl(ClassSourceLookup lookup) { + VisitorImpl(ClassSourceLookup lookup, ClassFinder classFinder) { this.lookup = lookup; + this.classFinder = classFinder; } @Override diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/MethodDisambiguator.java b/spark-common/src/main/java/me/lucko/spark/common/util/MethodDisambiguator.java index 2b2e3c78..0cb61235 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/util/MethodDisambiguator.java +++ b/spark-common/src/main/java/me/lucko/spark/common/util/MethodDisambiguator.java @@ -24,6 +24,8 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ListMultimap; import me.lucko.spark.common.sampler.node.StackTraceNode; +import me.lucko.spark.common.util.classfinder.ClassFinder; +import me.lucko.spark.common.util.classfinder.InstrumentationClassFinder; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Label; @@ -43,8 +45,13 @@ * to a method (method name + method description). */ public final class MethodDisambiguator { - private final Map cache = new ConcurrentHashMap<>(); - private final ClassFinder classFinder = new ClassFinder(); + private final ClassFinder classFinder; + private final Map cache; + + public MethodDisambiguator(ClassFinder classFinder) { + this.classFinder = classFinder; + this.cache = new ConcurrentHashMap<>(); + } public Optional disambiguate(StackTraceNode element) { String desc = element.getMethodDescription(); diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/classfinder/ClassFinder.java b/spark-common/src/main/java/me/lucko/spark/common/util/classfinder/ClassFinder.java new file mode 100644 index 00000000..1ee75c66 --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/util/classfinder/ClassFinder.java @@ -0,0 +1,46 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.common.util.classfinder; + +import com.google.common.collect.ImmutableList; +import org.checkerframework.checker.nullness.qual.Nullable; + +public interface ClassFinder { + + /** + * Creates a ClassFinder that combines the results of multiple other finders. + * + * @param finders the other class finders + * @return the combined class finder + */ + static ClassFinder combining(ClassFinder... finders) { + return new CombinedClassFinder(ImmutableList.copyOf(finders)); + } + + /** + * Attempts to find a class by name. + * + * @param className the name of the class + * @return the class, if found + */ + @Nullable Class findClass(String className); + +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/classfinder/CombinedClassFinder.java b/spark-common/src/main/java/me/lucko/spark/common/util/classfinder/CombinedClassFinder.java new file mode 100644 index 00000000..ed63f36c --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/util/classfinder/CombinedClassFinder.java @@ -0,0 +1,44 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.common.util.classfinder; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.List; + +class CombinedClassFinder implements ClassFinder { + private final List finders; + + CombinedClassFinder(List finders) { + this.finders = finders; + } + + @Override + public @Nullable Class findClass(String className) { + for (ClassFinder finder : this.finders) { + Class clazz = finder.findClass(className); + if (clazz != null) { + return clazz; + } + } + return null; + } +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/classfinder/FallbackClassFinder.java b/spark-common/src/main/java/me/lucko/spark/common/util/classfinder/FallbackClassFinder.java new file mode 100644 index 00000000..dd3c9f00 --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/util/classfinder/FallbackClassFinder.java @@ -0,0 +1,40 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.common.util.classfinder; + +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Uses {@link Class#forName(String)} to find a class reference for given class names. + */ +public enum FallbackClassFinder implements ClassFinder { + INSTANCE; + + @Override + public @Nullable Class findClass(String className) { + try { + return Class.forName(className); + } catch (Throwable e) { + return null; + } + } + +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/ClassFinder.java b/spark-common/src/main/java/me/lucko/spark/common/util/classfinder/InstrumentationClassFinder.java similarity index 70% rename from spark-common/src/main/java/me/lucko/spark/common/util/ClassFinder.java rename to spark-common/src/main/java/me/lucko/spark/common/util/classfinder/InstrumentationClassFinder.java index cead9383..8068f765 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/util/ClassFinder.java +++ b/spark-common/src/main/java/me/lucko/spark/common/util/classfinder/InstrumentationClassFinder.java @@ -18,8 +18,11 @@ * along with this program. If not, see . */ -package me.lucko.spark.common.util; +package me.lucko.spark.common.util.classfinder; +import me.lucko.spark.common.SparkPlugin; +import me.lucko.spark.common.util.JavaVersion; +import me.lucko.spark.common.util.SparkStaticLogger; import net.bytebuddy.agent.ByteBuddyAgent; import org.checkerframework.checker.nullness.qual.Nullable; @@ -33,18 +36,18 @@ * *

This is necessary as we don't always have access to the classloader for a given class.

*/ -public class ClassFinder { +public class InstrumentationClassFinder implements ClassFinder { private static boolean warned = false; - private static Instrumentation loadInstrumentation() { + private static Instrumentation loadInstrumentation(SparkPlugin plugin) { Instrumentation instrumentation = null; try { instrumentation = ByteBuddyAgent.install(); if (!warned && JavaVersion.getJavaVersion() >= 21) { warned = true; - SparkStaticLogger.log(Level.INFO, "If you see a warning above that says \"WARNING: A Java agent has been loaded dynamically\", it can be safely ignored."); - SparkStaticLogger.log(Level.INFO, "See here for more information: https://spark.lucko.me/docs/misc/Java-agent-warning"); + plugin.log(Level.INFO, "If you see a warning above that says \"WARNING: A Java agent has been loaded dynamically\", it can be safely ignored."); + plugin.log(Level.INFO, "See here for more information: https://spark.lucko.me/docs/misc/Java-agent-warning"); } } catch (Exception e) { // ignored @@ -54,8 +57,8 @@ private static Instrumentation loadInstrumentation() { private final Map> classes = new HashMap<>(); - public ClassFinder() { - Instrumentation instrumentation = loadInstrumentation(); + public InstrumentationClassFinder(SparkPlugin plugin) { + Instrumentation instrumentation = loadInstrumentation(plugin); if (instrumentation == null) { return; } @@ -66,21 +69,9 @@ public ClassFinder() { } } + @Override public @Nullable Class findClass(String className) { - // try instrumentation - Class clazz = this.classes.get(className); - if (clazz != null) { - return clazz; - } - - // try Class.forName - try { - return Class.forName(className); - } catch (Throwable e) { - // ignore - } - - return null; + return this.classes.get(className); } } diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricClassSourceLookup.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricClassSourceLookup.java index bd5385bd..3989d651 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricClassSourceLookup.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricClassSourceLookup.java @@ -22,7 +22,8 @@ import com.google.common.collect.ImmutableMap; import me.lucko.spark.common.sampler.source.ClassSourceLookup; -import me.lucko.spark.common.util.ClassFinder; +import me.lucko.spark.common.util.classfinder.ClassFinder; +import me.lucko.spark.common.util.classfinder.InstrumentationClassFinder; import me.lucko.spark.fabric.smap.MixinUtils; import me.lucko.spark.fabric.smap.SourceMap; import me.lucko.spark.fabric.smap.SourceMapProvider; @@ -41,14 +42,15 @@ import java.util.Map; public class FabricClassSourceLookup extends ClassSourceLookup.ByCodeSource { - - private final ClassFinder classFinder = new ClassFinder(); - private final SourceMapProvider smapProvider = new SourceMapProvider(); - + private final ClassFinder classFinder; + private final SourceMapProvider smapProvider; private final Path modsDirectory; private final Map pathToModMap; - public FabricClassSourceLookup() { + public FabricClassSourceLookup(ClassFinder classFinder) { + this.classFinder = classFinder; + this.smapProvider = new SourceMapProvider(); + FabricLoader loader = FabricLoader.getInstance(); this.modsDirectory = loader.getGameDir().resolve("mods").toAbsolutePath().normalize(); this.pathToModMap = constructPathToModIdMap(loader.getAllMods()); diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkPlugin.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkPlugin.java index d1262226..1569bf80 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkPlugin.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkPlugin.java @@ -109,7 +109,7 @@ public void log(Level level, String msg) { @Override public ClassSourceLookup createClassSourceLookup() { - return new FabricClassSourceLookup(); + return new FabricClassSourceLookup(createClassFinder()); } @Override diff --git a/spark-paper/build.gradle b/spark-paper/build.gradle new file mode 100644 index 00000000..66f3639a --- /dev/null +++ b/spark-paper/build.gradle @@ -0,0 +1,37 @@ +plugins { + id 'io.github.goooler.shadow' version '8.1.7' +} + +tasks.withType(JavaCompile) { + // override, compile targeting J21 + options.release = 21 +} + +dependencies { + implementation project(':spark-common') + compileOnly 'io.papermc.paper:paper-api:1.21-R0.1-SNAPSHOT' +} + +repositories { + maven { url "https://repo.papermc.io/repository/maven-public/" } +} + +shadowJar { + archiveFileName = "spark-${project.pluginVersion}-paper.jar" + + relocate 'net.bytebuddy', 'me.lucko.spark.lib.bytebuddy' + relocate 'com.google.protobuf', 'me.lucko.spark.lib.protobuf' + relocate 'org.objectweb.asm', 'me.lucko.spark.lib.asm' + relocate 'one.profiler', 'me.lucko.spark.lib.asyncprofiler' + relocate 'me.lucko.bytesocks.client', 'me.lucko.spark.lib.bytesocks' + relocate 'org.java_websocket', 'me.lucko.spark.lib.bytesocks.ws' + + exclude 'module-info.class' + exclude 'META-INF/maven/**' + exclude 'META-INF/proguard/**' +} + +artifacts { + archives shadowJar + shadow shadowJar +} diff --git a/spark-paper/src/main/java/me/lucko/spark/paper/PaperClassLookup.java b/spark-paper/src/main/java/me/lucko/spark/paper/PaperClassLookup.java new file mode 100644 index 00000000..68a41539 --- /dev/null +++ b/spark-paper/src/main/java/me/lucko/spark/paper/PaperClassLookup.java @@ -0,0 +1,27 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.paper; + +public interface PaperClassLookup { + + Class lookup(String className) throws Exception; + +} diff --git a/spark-paper/src/main/java/me/lucko/spark/paper/PaperClassSourceLookup.java b/spark-paper/src/main/java/me/lucko/spark/paper/PaperClassSourceLookup.java new file mode 100644 index 00000000..2c5f7c0e --- /dev/null +++ b/spark-paper/src/main/java/me/lucko/spark/paper/PaperClassSourceLookup.java @@ -0,0 +1,61 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.paper; + +import me.lucko.spark.common.sampler.source.ClassSourceLookup; +import org.bukkit.plugin.java.JavaPlugin; + +import java.lang.reflect.Field; + +public class PaperClassSourceLookup extends ClassSourceLookup.ByClassLoader { + private static final Class PLUGIN_CLASS_LOADER; + private static final Field PLUGIN_FIELD; + + private static final Class PAPER_PLUGIN_CLASS_LOADER; + private static final Field PAPER_PLUGIN_FIELD; + + static { + try { + PLUGIN_CLASS_LOADER = Class.forName("org.bukkit.plugin.java.PluginClassLoader"); + PLUGIN_FIELD = PLUGIN_CLASS_LOADER.getDeclaredField("plugin"); + PLUGIN_FIELD.setAccessible(true); + + PAPER_PLUGIN_CLASS_LOADER = Class.forName("io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader"); + PAPER_PLUGIN_FIELD = PAPER_PLUGIN_CLASS_LOADER.getDeclaredField("loadedJavaPlugin"); + PAPER_PLUGIN_FIELD.setAccessible(true); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } + + @Override + public String identify(ClassLoader loader) throws ReflectiveOperationException { + if (PLUGIN_CLASS_LOADER.isInstance(loader)) { + JavaPlugin plugin = (JavaPlugin) PLUGIN_FIELD.get(loader); + return plugin.getName(); + } else if (PAPER_PLUGIN_CLASS_LOADER.isInstance(loader)) { + JavaPlugin plugin = (JavaPlugin) PAPER_PLUGIN_FIELD.get(loader); + return plugin.getName(); + } + return null; + } +} + diff --git a/spark-paper/src/main/java/me/lucko/spark/paper/PaperCommandSender.java b/spark-paper/src/main/java/me/lucko/spark/paper/PaperCommandSender.java new file mode 100644 index 00000000..c3b569d1 --- /dev/null +++ b/spark-paper/src/main/java/me/lucko/spark/paper/PaperCommandSender.java @@ -0,0 +1,58 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.paper; + +import me.lucko.spark.common.command.sender.AbstractCommandSender; +import net.kyori.adventure.text.Component; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.UUID; + +public class PaperCommandSender extends AbstractCommandSender { + + public PaperCommandSender(CommandSender sender) { + super(sender); + } + + @Override + public String getName() { + return this.delegate.getName(); + } + + @Override + public UUID getUniqueId() { + if (super.delegate instanceof Player player) { + return player.getUniqueId(); + } + return null; + } + + @Override + public void sendMessage(Component message) { + super.delegate.sendMessage(message); + } + + @Override + public boolean hasPermission(String permission) { + return super.delegate.hasPermission(permission); + } +} diff --git a/spark-paper/src/main/java/me/lucko/spark/paper/PaperPlatformInfo.java b/spark-paper/src/main/java/me/lucko/spark/paper/PaperPlatformInfo.java new file mode 100644 index 00000000..a7a35794 --- /dev/null +++ b/spark-paper/src/main/java/me/lucko/spark/paper/PaperPlatformInfo.java @@ -0,0 +1,53 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.paper; + +import io.papermc.paper.ServerBuildInfo; +import me.lucko.spark.common.platform.PlatformInfo; + +public enum PaperPlatformInfo implements PlatformInfo { + INSTANCE; + + @Override + public Type getType() { + return Type.SERVER; + } + + @Override + public String getName() { + return "Bukkit"; // TODO: change to Paper and update viewer code accordingly + } + + @Override + public String getBrand() { + return ServerBuildInfo.buildInfo().brandName(); + } + + @Override + public String getVersion() { + return ServerBuildInfo.buildInfo().asString(ServerBuildInfo.StringRepresentation.VERSION_SIMPLE); + } + + @Override + public String getMinecraftVersion() { + return ServerBuildInfo.buildInfo().minecraftVersionId(); + } +} diff --git a/spark-paper/src/main/java/me/lucko/spark/paper/PaperPlayerPingProvider.java b/spark-paper/src/main/java/me/lucko/spark/paper/PaperPlayerPingProvider.java new file mode 100644 index 00000000..e896b214 --- /dev/null +++ b/spark-paper/src/main/java/me/lucko/spark/paper/PaperPlayerPingProvider.java @@ -0,0 +1,45 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.paper; + +import com.google.common.collect.ImmutableMap; +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; +import org.bukkit.Server; +import org.bukkit.entity.Player; + +import java.util.Map; + +public class PaperPlayerPingProvider implements PlayerPingProvider { + private final Server server; + + public PaperPlayerPingProvider(Server server) { + this.server = server; + } + + @Override + public Map poll() { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (Player player : this.server.getOnlinePlayers()) { + builder.put(player.getName(), player.getPing()); + } + return builder.build(); + } +} diff --git a/spark-paper/src/main/java/me/lucko/spark/paper/PaperScheduler.java b/spark-paper/src/main/java/me/lucko/spark/paper/PaperScheduler.java new file mode 100644 index 00000000..612c8eee --- /dev/null +++ b/spark-paper/src/main/java/me/lucko/spark/paper/PaperScheduler.java @@ -0,0 +1,29 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.paper; + +public interface PaperScheduler { + + void executeAsync(Runnable task); + + void executeSync(Runnable task); + +} diff --git a/spark-paper/src/main/java/me/lucko/spark/paper/PaperServerConfigProvider.java b/spark-paper/src/main/java/me/lucko/spark/paper/PaperServerConfigProvider.java new file mode 100644 index 00000000..d1301f8f --- /dev/null +++ b/spark-paper/src/main/java/me/lucko/spark/paper/PaperServerConfigProvider.java @@ -0,0 +1,159 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.paper; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializer; +import me.lucko.spark.common.platform.serverconfig.ConfigParser; +import me.lucko.spark.common.platform.serverconfig.ExcludedConfigFilter; +import me.lucko.spark.common.platform.serverconfig.PropertiesConfigParser; +import me.lucko.spark.common.platform.serverconfig.ServerConfigProvider; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.configuration.MemorySection; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +public class PaperServerConfigProvider extends ServerConfigProvider { + + /** A map of provided files and their type */ + private static final Map FILES; + /** A collection of paths to be excluded from the files */ + private static final Collection HIDDEN_PATHS; + + public PaperServerConfigProvider() { + super(FILES, HIDDEN_PATHS); + } + + private static class YamlConfigParser implements ConfigParser { + public static final YamlConfigParser INSTANCE = new YamlConfigParser(); + protected static final Gson GSON = new GsonBuilder() + .registerTypeAdapter(MemorySection.class, (JsonSerializer) (obj, type, ctx) -> ctx.serialize(obj.getValues(false))) + .create(); + + @Override + public JsonElement load(String file, ExcludedConfigFilter filter) throws IOException { + Map values = this.parse(Paths.get(file)); + if (values == null) { + return null; + } + + return filter.apply(GSON.toJsonTree(values)); + } + + @Override + public Map parse(BufferedReader reader) throws IOException { + YamlConfiguration config = YamlConfiguration.loadConfiguration(reader); + return config.getValues(false); + } + } + + // Paper 1.19+ split config layout + private static class SplitYamlConfigParser extends YamlConfigParser { + public static final SplitYamlConfigParser INSTANCE = new SplitYamlConfigParser(); + + @Override + public JsonElement load(String group, ExcludedConfigFilter filter) throws IOException { + String prefix = group.replace("/", ""); + + Path configDir = Paths.get("config"); + if (!Files.exists(configDir)) { + return null; + } + + JsonObject root = new JsonObject(); + + for (Map.Entry entry : getNestedFiles(configDir, prefix).entrySet()) { + String fileName = entry.getKey(); + Path path = entry.getValue(); + + Map values = this.parse(path); + if (values == null) { + continue; + } + + // apply the filter individually to each nested file + root.add(fileName, filter.apply(GSON.toJsonTree(values))); + } + + return root; + } + + private static Map getNestedFiles(Path configDir, String prefix) { + Map files = new LinkedHashMap<>(); + files.put("global.yml", configDir.resolve(prefix + "-global.yml")); + files.put("world-defaults.yml", configDir.resolve(prefix + "-world-defaults.yml")); + for (World world : Bukkit.getWorlds()) { + files.put(world.getName() + ".yml", world.getWorldFolder().toPath().resolve(prefix + "-world.yml")); + } + return files; + } + } + + static { + ImmutableMap.Builder files = ImmutableMap.builder() + .put("server.properties", PropertiesConfigParser.INSTANCE) + .put("bukkit.yml", YamlConfigParser.INSTANCE) + .put("spigot.yml", YamlConfigParser.INSTANCE) + .put("paper.yml", YamlConfigParser.INSTANCE) + .put("paper/", SplitYamlConfigParser.INSTANCE) + .put("purpur.yml", YamlConfigParser.INSTANCE) + .put("pufferfish.yml", YamlConfigParser.INSTANCE); + + for (String config : getSystemPropertyList("spark.serverconfigs.extra")) { + files.put(config, YamlConfigParser.INSTANCE); + } + + ImmutableSet.Builder hiddenPaths = ImmutableSet.builder() + .add("database") + .add("settings.bungeecord-addresses") + .add("settings.velocity-support.secret") + .add("proxies.velocity.secret") + .add("server-ip") + .add("motd") + .add("resource-pack") + .add("rconpassword") + .add("rconip") + .add("level-seed") + .add("world-settings.*.feature-seeds") + .add("world-settings.*.seed-*") + .add("feature-seeds") + .add("seed-*") + .addAll(getSystemPropertyList("spark.serverconfigs.hiddenpaths")); + + FILES = files.build(); + HIDDEN_PATHS = hiddenPaths.build(); + } + +} diff --git a/spark-paper/src/main/java/me/lucko/spark/paper/PaperSparkPlugin.java b/spark-paper/src/main/java/me/lucko/spark/paper/PaperSparkPlugin.java new file mode 100644 index 00000000..7221380a --- /dev/null +++ b/spark-paper/src/main/java/me/lucko/spark/paper/PaperSparkPlugin.java @@ -0,0 +1,223 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.paper; + +import me.lucko.spark.api.Spark; +import me.lucko.spark.common.SparkPlatform; +import me.lucko.spark.common.SparkPlugin; +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; +import me.lucko.spark.common.platform.PlatformInfo; +import me.lucko.spark.common.platform.serverconfig.ServerConfigProvider; +import me.lucko.spark.common.platform.world.WorldInfoProvider; +import me.lucko.spark.common.sampler.ThreadDumper; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.SourceMetadata; +import me.lucko.spark.common.tick.TickHook; +import me.lucko.spark.common.tick.TickReporter; +import me.lucko.spark.common.util.classfinder.ClassFinder; +import org.bukkit.Server; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.ServicePriority; + +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Stream; + +/** + * Spark 'plugin' for use as a library within the Paper server implementation. + * + *

Paper provides:

+ *
    + *
  • a {@link Server} instance
  • + *
  • a {@link Logger} instance
  • + *
  • a {@link PaperScheduler} instance
  • + *
  • a {@link PaperClassLookup} instance
  • + *
+ * + *

Paper is expected to:

+ *
    + *
  • call {@link #enable()} to enable spark, either immediately or when the server has finished starting
  • + *
  • call {@link #onCommand(CommandSender, String[])} when the spark command is executed
  • + *
  • call {@link #onTabComplete(CommandSender, String[])} when the spark command is tab completed
  • + *
  • call {@link #onServerTickStart()} at the start of each server tick
  • + *
  • call {@link #onServerTickEnd(double)} at the end of each server tick
  • + *
+ */ +public class PaperSparkPlugin implements SparkPlugin, AutoCloseable { + private final Server server; + private final Logger logger; + private final PaperScheduler scheduler; + private final PaperClassLookup classLookup; + + private final PaperTickHook tickHook; + private final PaperTickReporter tickReporter; + private final ThreadDumper gameThreadDumper; + private final SparkPlatform platform; + + public PaperSparkPlugin(Server server, Logger logger, PaperScheduler scheduler, PaperClassLookup classLookup) { + this.server = server; + this.logger = logger; + this.scheduler = scheduler; + this.classLookup = classLookup; + this.tickHook = new PaperTickHook(); + this.tickReporter = new PaperTickReporter(); + this.gameThreadDumper = new ThreadDumper.Specific(Thread.currentThread()); + this.platform = new SparkPlatform(this); + } + + public void enable() { + this.platform.enable(); + } + + @Override + public void close() { + this.platform.disable(); + } + + public boolean onCommand(CommandSender sender, String[] args) { + this.platform.executeCommand(new PaperCommandSender(sender), args); + return true; + } + + public List onTabComplete(CommandSender sender, String[] args) { + return this.platform.tabCompleteCommand(new PaperCommandSender(sender), args); + } + + // equivalent of ServerTickStartEvent + public void onServerTickStart() { + this.tickHook.onTick(); + } + + // equivalent of ServerTickEndEvent + public void onServerTickEnd(double duration) { + this.tickReporter.onTick(duration); + } + + @Override + public String getVersion() { + return "TODO"; // TODO - get from package implementation version? + } + + @Override + public Path getPluginDirectory() { + return this.server.getPluginsFolder().toPath().resolve("spark"); + } + + @Override + public String getCommandName() { + return "spark"; + } + + @Override + public Stream getCommandSenders() { + return Stream.concat( + this.server.getOnlinePlayers().stream(), + Stream.of(this.server.getConsoleSender()) + ).map(PaperCommandSender::new); + } + + @Override + public void executeAsync(Runnable task) { + this.scheduler.executeAsync(task); + } + + @Override + public void executeSync(Runnable task) { + this.scheduler.executeSync(task); + } + + @Override + public void log(Level level, String msg) { + this.logger.log(level, msg); + } + + @Override + public ThreadDumper getDefaultThreadDumper() { + return this.gameThreadDumper; + } + + @Override + public TickHook createTickHook() { + return this.tickHook; + } + + @Override + public TickReporter createTickReporter() { + return this.tickReporter; + } + + @Override + public ClassSourceLookup createClassSourceLookup() { + return new PaperClassSourceLookup(); + } + + @Override + public ClassFinder createClassFinder() { + return className -> { + try { + return this.classLookup.lookup(className); + } catch (Exception e) { + return null; + } + }; + } + + @Override + public Collection getKnownSources() { + return SourceMetadata.gather( + Arrays.asList(this.server.getPluginManager().getPlugins()), + Plugin::getName, + plugin -> plugin.getPluginMeta().getVersion(), + plugin -> String.join(", ", plugin.getPluginMeta().getAuthors()) + ); + } + + @Override + public PlayerPingProvider createPlayerPingProvider() { + return new PaperPlayerPingProvider(this.server); + } + + @Override + public ServerConfigProvider createServerConfigProvider() { + return new PaperServerConfigProvider(); + } + + @Override + public WorldInfoProvider createWorldInfoProvider() { + return new PaperWorldInfoProvider(this.server); + } + + @Override + public PlatformInfo getPlatformInfo() { + return PaperPlatformInfo.INSTANCE; + } + + @SuppressWarnings("DataFlowIssue") // null plugin + @Override + public void registerApi(Spark api) { + this.server.getServicesManager().register(Spark.class, api, null, ServicePriority.Normal); + } +} diff --git a/spark-paper/src/main/java/me/lucko/spark/paper/PaperTickHook.java b/spark-paper/src/main/java/me/lucko/spark/paper/PaperTickHook.java new file mode 100644 index 00000000..06126e16 --- /dev/null +++ b/spark-paper/src/main/java/me/lucko/spark/paper/PaperTickHook.java @@ -0,0 +1,46 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.paper; + +import me.lucko.spark.common.tick.AbstractTickHook; +import me.lucko.spark.common.tick.TickHook; +import org.bukkit.event.Listener; + +public class PaperTickHook extends AbstractTickHook implements TickHook, Listener { + private boolean open = false; + + @Override + public void start() { + this.open = true; + } + + @Override + public void close() { + this.open = false; + } + + @Override + public void onTick() { + if (this.open) { + super.onTick(); + } + } +} diff --git a/spark-paper/src/main/java/me/lucko/spark/paper/PaperTickReporter.java b/spark-paper/src/main/java/me/lucko/spark/paper/PaperTickReporter.java new file mode 100644 index 00000000..4db1f16e --- /dev/null +++ b/spark-paper/src/main/java/me/lucko/spark/paper/PaperTickReporter.java @@ -0,0 +1,46 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.paper; + +import me.lucko.spark.common.tick.AbstractTickReporter; +import me.lucko.spark.common.tick.TickReporter; +import org.bukkit.event.Listener; + +public class PaperTickReporter extends AbstractTickReporter implements TickReporter, Listener { + private boolean open = false; + + @Override + public void start() { + this.open = true; + } + + @Override + public void close() { + this.open = false; + } + + @Override + public void onTick(double duration) { + if (this.open) { + super.onTick(duration); + } + } +} diff --git a/spark-paper/src/main/java/me/lucko/spark/paper/PaperWorldInfoProvider.java b/spark-paper/src/main/java/me/lucko/spark/paper/PaperWorldInfoProvider.java new file mode 100644 index 00000000..29ab1ef4 --- /dev/null +++ b/spark-paper/src/main/java/me/lucko/spark/paper/PaperWorldInfoProvider.java @@ -0,0 +1,105 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.paper; + +import me.lucko.spark.common.platform.world.AbstractChunkInfo; +import me.lucko.spark.common.platform.world.CountMap; +import me.lucko.spark.common.platform.world.WorldInfoProvider; +import org.bukkit.Chunk; +import org.bukkit.Server; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; + +import java.util.ArrayList; +import java.util.List; + +public class PaperWorldInfoProvider implements WorldInfoProvider { + private final Server server; + + public PaperWorldInfoProvider(Server server) { + this.server = server; + } + + @Override + public CountsResult pollCounts() { + int players = this.server.getOnlinePlayers().size(); + int entities = 0; + int tileEntities = 0; + int chunks = 0; + + for (World world : this.server.getWorlds()) { + entities += world.getEntityCount(); + tileEntities += world.getTileEntityCount(); + chunks += world.getChunkCount(); + } + + return new CountsResult(players, entities, tileEntities, chunks); + } + + @Override + public ChunksResult pollChunks() { + ChunksResult data = new ChunksResult<>(); + + for (World world : this.server.getWorlds()) { + Chunk[] chunks = world.getLoadedChunks(); + + List list = new ArrayList<>(chunks.length); + for (Chunk chunk : chunks) { + if (chunk != null) { + list.add(new PaperChunkInfo(chunk)); + } + } + + data.put(world.getName(), list); + } + + return data; + } + + static final class PaperChunkInfo extends AbstractChunkInfo { + private final CountMap entityCounts; + + PaperChunkInfo(Chunk chunk) { + super(chunk.getX(), chunk.getZ()); + + this.entityCounts = new CountMap.EnumKeyed<>(EntityType.class); + for (Entity entity : chunk.getEntities()) { + if (entity != null) { + this.entityCounts.increment(entity.getType()); + } + } + } + + @Override + public CountMap getEntityCounts() { + return this.entityCounts; + } + + @SuppressWarnings("deprecation") + @Override + public String entityTypeName(EntityType type) { + return type.getName(); + } + + } + +}