Class PluginInstallCommands

java.lang.Object
com.ultikits.ultitools.abstracts.command.BaseCommandExecutor
com.ultikits.ultitools.commands.PluginInstallCommands
All Implemented Interfaces:
org.bukkit.command.CommandExecutor, org.bukkit.command.TabCompleter, org.bukkit.command.TabExecutor

@CmdExecutor(description="UltiTools Plugin Management Commands", alias="upm", requireOp=true) public class PluginInstallCommands extends BaseCommandExecutor
  • Field Details

  • Constructor Details

    • PluginInstallCommands

      public PluginInstallCommands()
  • Method Details

    • listPlugins

      public void listPlugins(org.bukkit.command.CommandSender sender, String page)
    • sendComponentMessage

      static void sendComponentMessage(org.bukkit.entity.Player player, net.kyori.adventure.text.TextComponent text)
      Send a TextComponent message to a player. This method is extracted from BukkitRunnable for testability.
      Parameters:
      player - the player to send the message to
      text - the text component to send
    • sendStringMessage

      static void sendStringMessage(org.bukkit.command.CommandSender sender, String message)
      Send a string message to a command sender. This method is extracted from BukkitRunnable for testability.
      Parameters:
      sender - the sender to send the message to
      message - the message to send
    • listPlugins

      public void listPlugins(org.bukkit.command.CommandSender sender)
    • installPlugin

      public void installPlugin(org.bukkit.command.CommandSender sender, String plugin, String version)
    • installPlugin

      public void installPlugin(org.bukkit.command.CommandSender sender, String plugin)
    • sendInstallResult

      private static void sendInstallResult(org.bukkit.command.CommandSender sender, PluginInstallUtils.InstallResult result)
      Tells the operator what an install did: the success line, the reason when the module's API level was why it was not installed (plan 18-40), or the bare failure line otherwise.
    • listVersions

      public void listVersions(org.bukkit.command.CommandSender sender, String plugin)
    • uninstallPlugin

      public void uninstallPlugin(org.bukkit.command.CommandSender sender, String plugin)
    • sendDeferredRemoval

      private static void sendDeferredRemoval(org.bukkit.command.CommandSender sender, PluginInstallUtils.RemovalDeferredException deferred)
      The JARs the uninstall could not delete now -- held open by the running server, as Windows does with every loaded module JAR, or in a folder that is not writable -- and has recorded for the next start, which deletes them before any module loads (#518). Not "delete them by hand": on Windows the running server prevents exactly that.
    • modulesFolderPath

      private static String modulesFolderPath()
      The modules folder, as the uninstall replies name it -- the one the loader reads (#517).
    • sendUnlistableFolder

      private static void sendUnlistableFolder(org.bukkit.command.CommandSender sender, String folder)
      The modules folder is there and its contents are unknown, so nothing may be claimed about what it still holds -- including whether a JAR of this module is among it.
    • sendUnreadableEntriesOf

      private static void sendUnreadableEntriesOf(org.bukkit.command.CommandSender sender, Throwable failure)
      Names the entries a no-JAR-found failure carries with it: state C survives that answer, and one of those entries may be the module's own JAR.
    • sendUndeterminedEntries

      private static void sendUndeterminedEntries(org.bukkit.command.CommandSender sender, PluginInstallUtils.UninstallReport report)
      Says what the uninstall could not determine: entries it could not read at all. They are not called copies of the module, because nothing established that they are -- what the operator needs is the count and what follows from it if one of them turns out to be one.
    • sendJarOutcomeAfterUnloadError

      private static void sendJarOutcomeAfterUnloadError(org.bukkit.command.CommandSender sender, PluginInstallUtils.ModuleUnloadFailedException unloadError)
      Reports the jar outcome PluginInstallUtils.uninstallPlugin(java.lang.String) attached to its unload-error exception: none attached means every matching jar was deleted.
    • undeterminedOf

      private static List<String> undeterminedOf(Throwable failure)
      The undetermined entries a failure carries.
    • collectUndetermined

      private static void collectUndetermined(Throwable failure, List<String> files)
      Collects the undetermined entries a failure carries, wherever in its suppressed chain they sit.
    • collectUndetermined

      private static void collectUndetermined(Throwable failure, List<String> files, Set<Throwable> seen)
      collectUndetermined(Throwable, List), remembering what it has already walked.

      `addSuppressed` refuses a throwable suppressing itself but not a longer loop, and a walk that meets one never returns. Nothing here builds such a chain today; the uninstall's replies are not the place to find out that something else did.

      Parameters:
      failure - the failure to walk
      files - the paths collected so far
      seen - the throwables already visited, by identity
    • sendUndeletedJars

      private static void sendUndeletedJars(org.bukkit.command.CommandSender sender, FileSystemException failure)
      Names every jar the uninstall could not delete. Each one loads the module again at the next start, so an instruction that names only the first is one the operator cannot act on (#501).

      When the deletion could not be recorded for the next start either, that is said on a line of its own, with its reason and no instruction to delete anything: the record failure travels as a PluginInstallUtils.RemovalNotRecordedException, not a FileSystemException, so the file it names -- a transaction record or temporary file under the server root's .ultikits folder -- is never listed among the module JARs to delete.

    • notRecordedIn

      private static PluginInstallUtils.RemovalNotRecordedException notRecordedIn(Throwable failure, Set<Throwable> seen)
      The record failure a failure carries, wherever in its suppressed chain it sits; null when there is none. Remembers what it has walked, for the same reason collectUndetermined(Throwable, List, Set) does.
      Parameters:
      failure - the failure to walk
      seen - the throwables already visited, by identity
      Returns:
      the carrier, or null
    • collectNamedFiles

      private static void collectNamedFiles(FileSystemException failure, List<String> files)
      Collects the file named by failure and by every FileSystemException attached to it, at any depth -- a file the operator is not told about is one they leave behind. A record failure is not one of them: it travels as a PluginInstallUtils.RemovalNotRecordedException, which this walk skips by type.
      Parameters:
      failure - the failure to walk
      files - the names collected so far, in the order they were found
    • collectNamedFiles

      private static void collectNamedFiles(FileSystemException failure, List<String> files, Set<Throwable> seen)
      collectNamedFiles(FileSystemException, List), remembering what it has already walked, for the same reason collectUndetermined(Throwable, List, Set) does.
      Parameters:
      failure - the failure to walk
      files - the paths collected so far
      seen - the throwables already visited, by identity
    • checkUpdates

      public void checkUpdates(org.bukkit.command.CommandSender sender)
    • unconfirmed

      private static List<String> unconfirmed(UpdateManager updateManager)
      What the start-up check could not confirm (D19-032), or an empty list when there is no update manager yet.
    • sendIncompleteIfAny

      private static boolean sendIncompleteIfAny(org.bukkit.command.CommandSender sender, List<String> names)
      Phase 19 D19-032: when a lookup of the start-up check failed, "No updates available." is not known to be true for what it could not confirm; this says so instead, naming it, with the same line the console shows at start-up.
      Returns:
      whether a line was sent
    • unconfirmedModules

      private static List<String> unconfirmedModules(UpdateManager updateManager)
      unconfirmed(UpdateManager) without the framework: /upm update all updates modules only.
    • updatePlugin

      public void updatePlugin(org.bukkit.command.CommandSender sender, String pluginName)
    • sendStageResult

      private static boolean sendStageResult(org.bukkit.command.CommandSender sender, String pluginName, ModuleFileTransactions.StageResult result)
      Tells the operator what staging an update did (#505): staged to take effect at the next start -- never "updated", which is decided only by what that start observes -- or why nothing was staged.
      Parameters:
      sender - who asked
      pluginName - the module, as the operator named it
      result - what staging did
      Returns:
      whether the update was staged
    • updateAllPlugins

      private void updateAllPlugins(org.bukkit.command.CommandSender sender)
    • isSameModule

      static boolean isSameModule(UltiToolsPlugin installedPlugin, PluginEntity catalogueEntry)
      Decides whether installedPlugin (a currently loaded module) and catalogueEntry (a directory entry from the UltiCloud catalogue) refer to the SAME module (#439). The prior check compared installedPlugin.getPluginName() (the module's Bukkit plugin.yml name:) against catalogueEntry.getName() (the catalogue's own display name) with plain string equality -- correct only when the two strings genuinely agree, which they do not for every module.

      Preferred key, but inert today: identifyString. This is the field PluginInstallUtils and UpdateManager already treat as this framework's stable module identity for install/update/version lookups, and the one field PluginEntity and UltiToolsPlugin both carry under the same name. Compared only when BOTH sides carry a non-blank value -- two identify-string-less entries are never treated as a match on that basis alone, or every such module would appear installed against every such catalogue entry, which is worse than the bug being fixed. Measured as of 6.3.0 (WR-01, `16-REVIEW-command.md`): zero of the seventeen module directories under Modules/ declare identify-string: in plugin.yml (verified: grep -rl "identify-string:" Modules/*&#47;src/main/resources/plugin.yml returns nothing), so installedPlugin.getIdentifyString() is null for every module that exists today, including both modules #439 names, and this branch does not fire for any of them. It exists so a module that DOES declare one -- and any future migration toward identify-string:-based identity -- is matched correctly without this method needing to change again; today it is aspirational, not load-bearing.

      What actually fires today: exact name equality, plus one fixed, deterministic prefix strip. Measured (WR-01): only 4 of the 15 active modules declare a Bukkit name: carrying the literal "UltiTools-" prefix -- UltiTools-Chat (Modules/UltiChat/src/main/resources/plugin.yml:1), UltiTools-Economy (Modules/UltiEconomy/.../plugin.yml:1), UltiTools-Kits (Modules/UltiKits/.../plugin.yml:1), and UltiTools-Menu (Modules/UltiMenu/.../plugin.yml:1) -- the last two ARE #439's own two named modules. The other 11 modules declare their bare folder name with no prefix at all (e.g. UltiLogin, UltiWorlds). This is NOT a declared or tool-enforced convention: Tooling/ultikits-cli/src/lib/templates.ts:152 templates name: directly from the author-supplied module name with no prefix logic, and Tooling/UltiTools-Maven-Archetype ships no plugin.yml template at all -- it is simply each of these 4 authors' own historical choice. Stripping that one fixed prefix before comparing is a single deterministic transform, not a similarity/fuzzy heuristic: it accepts precisely the pairs that differ by nothing, or by exactly that one literal prefix, and rejects every other pair -- including one that merely shares a prefix or substring (a catalogue name that only resembles a loaded module's, such as one ending in "Pro", is rejected, not matched).

      Two present identifiers are authoritative, even on a mismatch (gate-2 Codex round 1). When BOTH sides carry a non-blank identifyString, that comparison alone decides the outcome -- a match returns true immediately, and a MISMATCH returns false immediately, without ever falling through to the name/prefix heuristic below. Two present, different stable identifiers prove the modules are different; the name heuristic must not override that proof (a loaded UltiTools-Foo with id author-a.foo must not be reported as the catalogue's unrelated Foo with id author-b.foo, even though the name/prefix rule alone would match them). The comparison is normalised (trimmed, lower-cased) via the same convention PluginInstallUtils's own (private) normalizeIdentifyString already applies before every install/update/lookup comparison elsewhere in this package, so a module and a catalogue entry that agree except for case are still recognised as the same.

      Residual collision risk (WR-01), disclosed rather than papered over: the risk above is closed whenever BOTH sides carry an identifier. It remains open only when at least one side's identifyString is blank -- true for every module today (0/17) -- in which case a THIRD-PARTY module that names itself UltiTools-Foo in its own plugin.yml would still be reported installed against a different, unrelated author's unaffiliated catalogue entry literally named Foo, since neither UltiToolsPlugin nor PluginEntity exposes anything else (a developer/author ID) this method could cross-check in that case. Still strictly better than the defect being fixed: today, EVERY module whose name doesn't already match its catalogue display name byte-for-byte is unconditionally reported not installed, a guaranteed false negative for real, correctly-loaded modules. Closing the remaining gap needs a stable identifier populated on both sides (#474), not a looser name heuristic.

      Parameters:
      installedPlugin - a currently loaded module
      catalogueEntry - a directory entry from the catalogue
      Returns:
      true iff the two are judged to refer to the same module
    • resolveInstalledRuntimeName

      static String resolveInstalledRuntimeName(List<UltiToolsPlugin> installedPlugins, PluginEntity catalogueEntry)
      Resolves the runtime name to use for the /upm uninstall click command in listPlugins(CommandSender, String)'s player branch: the LOADED module's own getPluginName(), not the catalogue entry's display name (gate-2 Codex round 1). PluginInstallUtils.uninstallPlugin(java.lang.String) matches only against the runtime name -- for exactly the display-name-mismatch case isSameModule(com.ultikits.ultitools.abstracts.UltiToolsPlugin, com.ultikits.ultitools.entities.PluginEntity) exists to recognise as installed, using the catalogue's display name here would build an uninstall command that silently fails (no unregister, no file delete, a generic failure message).
      Parameters:
      installedPlugins - every currently-loaded module
      catalogueEntry - the catalogue entry being rendered
      Returns:
      the matched module's own runtime name, or null if none matches
    • normalizeIdentifyString

      private static String normalizeIdentifyString(String value)
      Trims and lower-cases an identify string, the same normalisation PluginInstallUtils's own (private) normalizeIdentifyString applies before every install/update/lookup comparison elsewhere in this package (gate-2 Codex round 1) -- kept as a small local copy rather than widening that method's visibility, since the transform itself is a one-line, well-established convention, not shared mutable state.
      Parameters:
      value - the raw identify string, possibly null or blank
      Returns:
      the normalised value, or null if the input was null or blank
    • stripVendorPrefix

      private static String stripVendorPrefix(String runtimeName)
    • handleHelp

      protected void handleHelp(org.bukkit.command.CommandSender sender)
      Description copied from class: BaseCommandExecutor
      Handles the help command. Override to provide custom help.
      Specified by:
      handleHelp in class BaseCommandExecutor
      Parameters:
      sender - the command sender