Class UltiTools

java.lang.Object
org.bukkit.plugin.PluginBase
org.bukkit.plugin.java.JavaPlugin
com.ultikits.ultitools.UltiTools
All Implemented Interfaces:
Localized, io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner, net.kyori.adventure.key.Namespaced, org.bukkit.command.CommandExecutor, org.bukkit.command.TabCompleter, org.bukkit.command.TabExecutor, org.bukkit.plugin.Plugin

public final class UltiTools extends org.bukkit.plugin.java.JavaPlugin implements Localized
UltiTools plugin main class.
Version:
6.0.7
Author:
wisdommen, qianmo
  • Field Details

    • VERSION_PATTERN

      private static final Pattern VERSION_PATTERN
    • MODULE_SCAN_LOGGER

      private static final Logger MODULE_SCAN_LOGGER
    • DEFAULT_COMMAND_BLOCKLIST

      private static final List<String> DEFAULT_COMMAND_BLOCKLIST
      D-03: the ten current blocked commands, shipped as the operator-editable blocklist's default.
    • COMMAND_BLOCKLIST_COMMENT

      private static final List<String> COMMAND_BLOCKLIST_COMMENT
    • DEFAULT_EDITABLE_ROOTS

      private static final List<String> DEFAULT_EDITABLE_ROOTS
      D-15: the default editable-root set — plugin configs and historical logs.
    • EDITABLE_ROOTS_COMMENT

      private static final List<String> EDITABLE_ROOTS_COMMENT
    • ACTION_LOG_SIZE_COMMENT

      private static final List<String> ACTION_LOG_SIZE_COMMENT
    • ACTION_LOG_FILES_COMMENT

      private static final List<String> ACTION_LOG_FILES_COMMENT
    • ultiTools

      private static UltiTools ultiTools
    • listenerManager

      private final ListenerManager listenerManager
    • commandManager

      private final CommandManager commandManager
    • dependenceManagers

      private DependenceManagers dependenceManagers
    • ultiToolsClassLoader

      private URLClassLoader ultiToolsClassLoader
    • moduleFileTransactions

      private ModuleFileTransactions moduleFileTransactions
      The module update and removal transactions of this start (#505): applied before the module class loader is built, decided after the modules load.
    • SHIPPED_LANGUAGES

      private static final String[] SHIPPED_LANGUAGES
      The official languages the framework ships (lang/<code>.json in its jar), the codes a custom language name may be based on (#608). supported() returns them.
    • language

      private Language language
    • pluginManager

      private PluginManager pluginManager
    • configManager

      private ConfigManager configManager
    • dataStore

      private DataStore dataStore
    • serverMonitorManager

      private ServerMonitorManager serverMonitorManager
    • commandExecutionManager

      private CommandExecutionManager commandExecutionManager
    • fileOperationManager

      private FileOperationManager fileOperationManager
    • logStreamManager

      private LogStreamManager logStreamManager
    • playerEventManager

      private PlayerEventManager playerEventManager
    • serverPropertiesManager

      private ServerPropertiesManager serverPropertiesManager
    • updateManager

      private UpdateManager updateManager
    • eventBus

      private EventBus eventBus
    • errorReportCollector

      private ErrorReportCollector errorReportCollector
    • remoteActionLog

      private RemoteActionLog remoteActionLog
      The single accessor PluginInitiationUtils's capability gate and every WebSocket manager use to append action-log lines — getRemoteActionLog() (generated by Getter above).
    • sensitiveCommandRegistry

      private final SensitiveCommandRegistry sensitiveCommandRegistry
      The table of commands whose arguments never reach UltiCloud, reached via getSensitiveCommandRegistry() (generated by Getter above). A module registers its secret-carrying commands and their aliases here from its enable hook; the log stream, error reports and player events consult it on their way to the cloud (see SensitiveCommandRegistry). Final and created with the plugin instance, before the early log capture and before any module loads, so it exists for every caller whether or not the server is linked; it starts empty and is never replaced, so no registration is lost across a reload.
      Since:
      6.3.1
    • panelResponderRegistry

      private PanelResponderRegistry panelResponderRegistry
      The single-owner registry a module uses to claim a request/response responder for a panel message type the framework itself does not own (WIRE-16, D-26/D-27), reached via getPanelResponderRegistry() (generated by Getter above) from both the protocol v2 module.request handler and PluginManager's two module-unload sites. Created exactly once per enable, by initPanelResponderRegistry(), which onEnable() calls immediately before module loading: a module may claim a responder from registerSelf() or a @PostConstruct method, and PluginManager attributes that claim to the loading module only while this registry exists. It is never replaced while the framework is enabled, so no claim made during start-up is lost; onDisable() shuts it down. Not gated by any Capability, since the registry itself holds no data and starts no collection; what flows through it is already gated at the dispatch table it sits behind.
    • cloudFeatures

      private CloudFeatures cloudFeatures
      The inbound UltiCloud features of this enable (plan 22-21): created once by onEnable() before any module loads, so a module can set its player-link handler while it enables, and stopped by onDisable() after every module has been unloaded. Reached through getCloudPlayerLinks().
  • Constructor Details

    • UltiTools

      public UltiTools()
  • Method Details

    • getCloudPlayerLinks

      public CloudPlayerLinks getCloudPlayerLinks()
      The UltiCloud player-link API (as of 6.3.1): one object for the whole time the framework is enabled. It works before, during and between connections -- a create while the server is not connected completes with CloudPlayerLinks.Failure.NOT_CONNECTED -- and the handler a module sets on it stays registered across every reconnect until the module removes it or is unloaded.
      Returns:
      the player-link API; never null once the framework has enabled
      Since:
      6.3.1
    • getInstance

      public static UltiTools getInstance()
      Returns the instance of the UltiTools.
      Returns:
      the instance of the UltiTools
    • getPluginVersion

      public static int getPluginVersion()
      Gets the version of UltiTools.
      Returns:
      the version of the UltiTools
    • parsePluginVersion

      static int parsePluginVersion(String versionString)
    • getEnv

      public static org.bukkit.configuration.file.YamlConfiguration getEnv()
      Retrieves the YAML configuration object containing environment variables.
      Returns:
      the YAML configuration object
    • onLoad

      public void onLoad()
      Specified by:
      onLoad in interface org.bukkit.plugin.Plugin
      Overrides:
      onLoad in class org.bukkit.plugin.java.JavaPlugin
    • onEnable

      public void onEnable()
      Specified by:
      onEnable in interface org.bukkit.plugin.Plugin
      Overrides:
      onEnable in class org.bukkit.plugin.java.JavaPlugin
    • releaseEarlyLogCapture

      private void releaseEarlyLogCapture(boolean loginSuccess)
      Releases the early log capture (#487) when the panel's log stream cannot start soon: without a cloud login no connection opens until an operator logs in, which may never happen. Otherwise it is released once its time is up, even on a server that logs nothing more.
    • initDependencies

      private boolean initDependencies()
    • initLanguage

      private void initLanguage()
      Builds the framework's language from the language setting of config.yml (#608).

      First writes the official files lang/en.json and lang/zh.json to the data folder so an operator can copy them, restoring any that differ from the bundled version (official language files are framework-owned, maintainer decision 2026-10-04; see OfficialLanguageFiles.syncFrameworkFiles(java.io.File, java.util.List<java.lang.String>, java.lang.ClassLoader, java.util.logging.Logger)). Then resolves the setting: an official code is used as it is; a custom name such as zh-myserver reads the operator's lang/zh-myserver.json from the data folder when it exists, and takes every message it lacks from the official language its name starts with (zh); a name that starts with no official code uses English for those messages and logs one warning. The official texts always come from the jar, read as UTF-8. The custom file is only ever read.

    • readBundledLanguage

      private Language readBundledLanguage(String code)
      Reads the official catalogue lang/<code>.json from the framework jar as UTF-8.
      Returns:
      the catalogue, or an empty language when it is missing or unreadable (the reason is logged)
    • initDataStore

      private void initDataStore()
    • initPanelResponderRegistry

      private void initPanelResponderRegistry()
      Creates the panel responder registry, once per enable, before any module loads. Nothing else assigns panelResponderRegistry; see the field javadoc.
    • initPluginModules

      private void initPluginModules()
    • loadModulesThenObserve

      static void loadModulesThenObserve(ModuleFileTransactions transactions, UltiTools.ModuleLoad load, Supplier<List<UltiToolsPlugin>> loaded) throws IOException
      Loads the modules, then decides every update applied at this start from what loaded (#505). A load that throws counts as "not loaded" for every one of them.
      Parameters:
      transactions - this start's transactions
      load - the module loading step
      loaded - the modules loaded, read after load returns
      Throws:
      IOException - when load does
    • migrateCapabilitiesConfig

      private void migrateCapabilitiesConfig()
      Adds the panel keys the framework's own config.yml is missing, invoked from onEnable() after JavaPlugin.saveDefaultConfig() (called in onLoad()) and before initWebSocketManagers(). Reloads JavaPlugin.getConfig() only when the file was written, so every manager constructed afterward sees the merged file; when the write is refused nothing is reloaded and the jar's config.yml, which JavaPlugin.getConfig() uses as its defaults, answers for the missing keys.
    • migrateCapabilitiesConfig

      private static boolean migrateCapabilitiesConfig(File configFile, Logger logger)
      The migration itself, taking explicit inputs rather than reading this - the same test-seam shape parsePluginVersion(String) already uses, so it is callable from a plain JUnit test with no live server.

      Why it cannot overwrite operator content (maintainer decisions of 2026-10-04: "operator-written configuration is never overwritten" and "what code may write, by file type" - a shipped file gets missing keys and the framework's own comments, insert only; UltiKits/UltiTools-Reborn#605). The file is read through ConfigDocument.load(java.nio.file.Path); only keys absent from it are inserted, each with its comment, and the write goes through the config write gate (OperatorFileWriter) owning exactly those keys: every other byte of the file - values, hex numbers, dotted keys, quoting, layout, comments - must come out byte-identical, and the file must still hold the bytes read. Otherwise nothing is written and the gate logs one WARNING naming the keys and the reason. A key the operator set, even to an explicit null, is never touched. A file that cannot be read or parsed is never written (logged at SEVERE, as before).

      The server reads this file through Bukkit's YamlConfiguration, which splits a flat dotted key (ultipanel.capabilities.logs: false) into a path, while the document layer keeps it one whole key. So a key counts as present when either reading holds it, and the edit is first rendered and read the way Bukkit reads it: if any value Bukkit reads for an existing key would change - a nested section added after a flat dotted key replaces it - nothing is written, and one WARNING names the file and that key, never a value (PR #611 local Codex run 2).

      Parameters:
      configFile - the config.yml file to migrate
      logger - where to log a load or write failure
      Returns:
      true if the file was written
    • addIfAbsent

      private static void addIfAbsent(ConfigDocument document, org.bukkit.configuration.file.YamlConfiguration server, Map<List<String>,Object> missing, Map<List<String>,List<String>> comments, String dottedPath, Object value, List<String> commentLines)
      Records dottedPath (framework keys, no key contains a dot) as missing when neither the document nor the server's Bukkit reading of the same bytes holds it (a flat dotted key is present only in the latter).
    • bukkitReading

      private static org.bukkit.configuration.file.YamlConfiguration bukkitReading(File configFile, String fingerprint)
      The file as the server reads it - Bukkit's YamlConfiguration over the same bytes the document was loaded from - or null when those bytes cannot be read again unchanged or Bukkit cannot parse them (nothing is written then; the next start tries again).
    • textIfUnchanged

      private static String textIfUnchanged(File configFile, String fingerprint)
      The file's text when its bytes still have fingerprint, else null.
    • keyBukkitWouldReadDifferently

      private static String keyBukkitWouldReadDifferently(File configFile, String fingerprint, org.bukkit.configuration.file.YamlConfiguration before, Consumer<ConfigDocument> insert)
      Renders insert on a fresh copy of the file and reads the result the way the server does: the first key whose value Bukkit would read differently than before does, or null when every existing key reads the same. A file that changed meanwhile, or a rendering Bukkit cannot read, names the whole file.
    • startMetrics

      static Metrics startMetrics(org.bukkit.plugin.java.JavaPlugin plugin, int serviceId, Logger logger)
      Starts bStats metrics only when its shared file allows it without being written over.

      Why it cannot overwrite operator content (maintainer foundational rule of 2026-10-04: a file an operator may edit is never overwritten automatically; inventory B3, UltiKits/UltiTools-Reborn#606). The vendored Metrics - not edited - saves its defaults over plugins/bStats/config.yml, a file shared by every bStats plugin on the server, whenever it cannot read serverUuid; on a server an unparseable file loads as empty, so its content would be replaced. This guard reads the file without writing it and constructs Metrics only when the file is absent (bStats then creates it) or already holds serverUuid (bStats then writes nothing). A file that cannot be read or parsed, or one without serverUuid, is left byte-identical: one line names the file and the reason, never a value, and UltiTools' metrics stay off for this run.

      Parameters:
      plugin - the plugin bStats reports for; its data folder's parent holds bStats/config.yml
      serviceId - the bStats service id
      logger - where the refusal line goes
      Returns:
      the started metrics, or null when the shared file was left alone
    • bStatsFileRefusal

      private static String bStatsFileRefusal(File shared)
      Why shared must not be handed to bStats, or null; reads only.
    • initWebSocketManagers

      private void initWebSocketManagers()
    • attemptCloudLogin

      private boolean attemptCloudLogin()
    • initWebSocket

      private void initWebSocket()
    • registerCommands

      private void registerCommands()
    • scheduleStartupMessages

      private void scheduleStartupMessages()
    • onDisable

      public void onDisable()
      Specified by:
      onDisable in interface org.bukkit.plugin.Plugin
      Overrides:
      onDisable in class org.bukkit.plugin.java.JavaPlugin
    • reloadPlugins

      public void reloadPlugins() throws IOException
      Reloads the UltiTools plugins by calling the reload method in the PluginManager.
      Throws:
      IOException - if an I/O error occurs during the reloading process
    • reloadPluginsAndReport

      @Internal public List<String> reloadPluginsAndReport() throws IOException
      Reloads the framework configuration and language, then every module, and returns the summary /ul reload shows its sender (#509). Each module is reloaded in isolation; see PluginManager#reloadAllAndReport(). A changed operator setting the panel mirrors (power actions) is reported to an open UltiCloud connection once, as settings.changed (6.3.1).
      Returns:
      the summary lines, already localized
      Throws:
      IOException - if an I/O error occurs during the reloading process
      Since:
      6.3.0
    • supported

      public List<String> supported()
      Returns the supported language codes.
      Specified by:
      supported in interface Localized
      Returns:
      a list of supported language codes
      See Also:
    • i18n

      public String i18n(String str)
      Internationalization method that translates the given string based on the current language. If the string is not found in the dictionary, the original string is returned.
      Parameters:
      str - the string to be translated
      Returns:
      the translated string or the original string if not found in the dictionary
    • getFileResource

      private InputStream getFileResource(String filename)
      Retrieves the input stream for the specified file resource.
      Parameters:
      filename - the name of the file resource
      Returns:
      the input stream for the file resource, or null if an I/O error occurs
    • getServerJar

      public URL getServerJar()
      Get server jar file URL.
      Returns:
      Server jar file URL
    • getModuleUrls

      private URL[] getModuleUrls()
      Get URLs for module plugin JARs in the UltiTools/plugins directory.
      Returns:
      Array of URLs for module plugin JARs
    • moduleClassPath

      static URL[] moduleClassPath(ModuleFileTransactions transactions, File modulesFolder, URL serverJar)
      The module class path of this start: first the recorded module updates are put in place (#505), then the modules folder is listed -- in that order, so the class loader is built over the new JARs and never opens a JAR an update replaces.
      Parameters:
      transactions - this start's transactions
      modulesFolder - the modules folder
      serverJar - the server JAR, or null
      Returns:
      the URLs to build the module class loader from
    • collectModuleJarUrls

      static List<URL> collectModuleJarUrls(File pluginDir)
      Scan a directory for module plugin JARs and collect the URLs of the ones that pass SecurityPolicy.isValidModuleJar(File) — a JAR is validated before its URL is added, never after. A failing JAR is skipped and named in a WARNING; the scan continues and never throws (D-05: module-granularity skip, not a bootstrap abort).

      Package-private and static so it can be exercised directly by a test against a @TempDir, without standing up the whole plugin.

      Parameters:
      pluginDir - directory to scan for module JARs
      Returns:
      collected URLs of the JARs that passed validation, empty if pluginDir is null or does not exist
    • getJavaPluginClassLoader

      public static ClassLoader getJavaPluginClassLoader()
      Get the JavaPlugin class loader. This ensures all class loading operations use the correct parent class loader.
      Returns:
      JavaPlugin class loader