Class EconomyUtils

java.lang.Object
com.ultikits.ultitools.utils.EconomyUtils

public final class EconomyUtils extends Object
Utility class for economy operations using Vault. Provides convenient static methods for common economy operations.

@ApiStatus.Internal note (D-08/D-09, #451, 6.3.0): this class's own PUBLIC signatures — including getEconomy()'s Vault-typed return — are frozen for backward compatibility: six modules already call this façade directly, and this plan changes none of their call sites. That is the one deliberate exception to the framework's structural guard against soft-dependency types in reflected signatures (buildtools.SoftDependencySignatureInvariantTest) — this class carries its own explicit allowlist entry there, with this paragraph as the one-line reason required of every entry.

Internally, every operation now delegates to the internal EconomyProvider seam and reports the economy's unavailability honestly (D-08): the server's console gets exactly one WARNING per calling module per server session, naming the module, distinguishing "Vault is not installed" from "Vault is installed but no provider is registered", stating this is the server operator's environment rather than a framework or module defect, and giving the install instruction. This is a server-console line — it is never routed to the panel, since it names which modules are installed.

Since:
6.2.0
Version:
1.0.0
Author:
wisdomme
  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    private static final class 
    One frame's match outcome: the EconomyUtils.MatchKind, plus the resolved module name when (and only when) kind == RESOLVED.
    private static enum 
    The three possible outcomes of matching one stack frame against prefixToModule (Codex P2, PR #463, round 3): a bare null return collapsed "this frame belongs to no registered module" and "this frame's closest root is claimed by two or more DIFFERENT modules" into the same value, and the last two review rounds both found a defect that traces straight back to that conflation — a NONE frame must let the per-frame walk continue outward (a real non-match, no evidence either way), while an AMBIGUOUS frame must terminate the walk immediately (real evidence that a module frame exists here, but WHICH module cannot be determined) — see attributeModule(StackTraceElement[], Map)'s own javadoc for why continuing past an ambiguous frame is itself the bug.
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    private static final String[]
    Frames skipped when locating an unattributed caller's own package (#489).
    private static EconomyProvider
     
    (package private) static final String
    Attribution key used when no registered module's package appears anywhere on the calling stack.
    private static final Set<String>
     
  • Constructor Summary

    Constructors
    Modifier
    Constructor
    Description
    private
     
  • Method Summary

    Modifier and Type
    Method
    Description
    private static void
    addExternalPluginRoots(Map<String,String> prefixToModule, PluginManager pluginManager)
    Merges every connected External Plugin API consumer's scan package into prefixToModule under the plugin's name (#462), by the same rules as a module's roots.
    (package private) static void
    addModuleRoots(Map<String,String> prefixToModule, PluginManager pluginManager, Class<? extends UltiToolsPlugin> pluginClass, String owner)
    Merges one module's attribution roots into prefixToModule: every declared scan root (PluginManager.getPluginScanPackages(Class)), plus the module main class's own package as an implicit root (#489) -- a module whose declared roots do not cover its own package used to be unattributable for callers in that package.
    private static String
    The module-attribution helper (D-08): the first stack frame belonging to a registered module's own scan packages, using the same Thread.getStackTrace() idiom SystemLogHandler already relies on for trigger inference — no StackWalker, since this project's bytecode target is Java 8.
    (package private) static String
    attributeModule(StackTraceElement[] stack, Map<String,String> prefixToModule)
    Pure: given a stack trace and a map from package prefix to module name, returns the module name attributed to the CLOSEST (most-recent) frame that matches ANY mapped prefix, or null when no frame in the whole stack matches — including the empty-input cases (a null/empty stack, or an empty prefix map, as when no module is registered yet).
    private static String
     
    static boolean
    deposit(UUID uuid, double amount)
    Deposits money into a player's account.
    static boolean
    deposit(org.bukkit.OfflinePlayer player, double amount)
    Deposits money into a player's account.
    static String
    format(double amount)
    Formats an amount according to the economy's format.
    static double
    Gets the balance of a player by UUID.
    static double
    getBalance(org.bukkit.OfflinePlayer player)
    Gets the balance of a player.
    static String
    Gets the currency name (singular).
    static String
    Gets the currency name (plural).
    static net.milkbowl.vault.economy.Economy
    Gets the economy instance.
    static boolean
    has(UUID uuid, double amount)
    Checks if a player has at least the specified amount.
    static boolean
    has(org.bukkit.OfflinePlayer player, double amount)
    Checks if a player has at least the specified amount.
    static boolean
    Checks if Vault economy is available.
    private static boolean
     
    private static void
    log(String message, boolean info)
     
    static void
    Logs the framework's one start-up line naming the current economy service state (D-08).
    matchFrame(String className, Map<String,String> prefixToModule)
    Matches a single frame's class name against every entry in prefixToModule, applying the same most-specific-(longest)-prefix-wins rule attributeModule(java.lang.StackTraceElement[], java.util.Map<java.lang.String, java.lang.String>) has always used, and reports the outcome as a EconomyUtils.FrameMatch rather than a bare, ambiguity-erasing String.
    (package private) static void
    mergeScanPackageOwner(Map<String,String> prefixToModule, String pkg, String owner)
    Merges one (scan package, module name) declaration into prefixToModule, in place.
    (package private) static void
    The WARN/dedup logic, taking the calling module's name directly rather than resolving it from the live call stack.
    private static void
    Called by every public operation above before delegating to provider.
    (package private) static void
    The WARN/dedup logic for a request attribution could not name (#489): the warning still names UNKNOWN_MODULE, exactly as before, but its once-per-session dedup is keyed by the package of the closest calling frame outside the framework and the JDK, so a second unattributable caller in a different package gets its own warning instead of none.
    static void
    Resets the economy setup state.
    static void
    Framework-internal wiring point — called once by DependenceManagers at bootstrap with the same EconomyProvider instance it registers into the IoC container.
    static boolean
    Checks whether the economy provider is currently set up.
    static boolean
    transfer(UUID from, UUID to, double amount)
    Transfers money from one player to another.
    static boolean
    transfer(org.bukkit.OfflinePlayer from, org.bukkit.OfflinePlayer to, double amount)
    Transfers money from one player to another.
    (package private) static String
    The package of the closest frame in stack that belongs neither to the framework (com.ultikits.ultitools.) nor to the JDK -- the unattributed caller's own code.
    static boolean
    withdraw(UUID uuid, double amount)
    Withdraws money from a player's account.
    static boolean
    withdraw(org.bukkit.OfflinePlayer player, double amount)
    Withdraws money from a player's account.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Field Details

    • UNKNOWN_MODULE

      static final String UNKNOWN_MODULE
      Attribution key used when no registered module's package appears anywhere on the calling stack.
      See Also:
    • provider

      private static volatile EconomyProvider provider
    • warnedModules

      private static final Set<String> warnedModules
    • FRAMEWORK_AND_JDK_PREFIXES

      private static final String[] FRAMEWORK_AND_JDK_PREFIXES
      Frames skipped when locating an unattributed caller's own package (#489).
  • Constructor Details

    • EconomyUtils

      private EconomyUtils()
  • Method Details

    • setProvider

      public static void setProvider(EconomyProvider provider)
      Framework-internal wiring point — called once by DependenceManagers at bootstrap with the same EconomyProvider instance it registers into the IoC container. Not meant to be called by module code; public only because DependenceManagers lives in another package.
      Parameters:
      provider - the active economy provider
    • logStartupState

      public static void logStartupState()
      Logs the framework's one start-up line naming the current economy service state (D-08). Framework-internal — called once by DependenceManagers after setProvider(com.ultikits.ultitools.services.EconomyProvider).
    • setup

      public static boolean setup()
      Checks whether the economy provider is currently set up.

      [Rule 1 fix, CR-01, 16-07]: re-checks the live provider on every call, exactly like every sibling operation, instead of latching a failed first attempt forever — Vault (or the economy plugin that registers a provider with it) can start after this framework's own bootstrap runs, since Bukkit gives no ordering guarantee beyond declared dependencies.

      Returns:
      true if economy was set up successfully
    • isAvailable

      public static boolean isAvailable()
      Checks if Vault economy is available.
      Returns:
      true if economy is available
    • getEconomy

      @Nullable public static net.milkbowl.vault.economy.Economy getEconomy()
      Gets the economy instance.

      [Rule 1 fix, CR-01, 16-07]: re-checks the live provider on every call and reports D-08's honest unavailability warning, exactly like every sibling operation, instead of caching a failed first attempt for the rest of the server's uptime (the same defect class this milestone exists to close, reproduced by this seam's own two frozen-signature methods — see 16-REVIEW-economy.md CR-01).

      Returns:
      the economy instance, or null if not available
    • getBalance

      public static double getBalance(org.bukkit.OfflinePlayer player)
      Gets the balance of a player.
      Parameters:
      player - the player
      Returns:
      the balance, or 0 if economy is not available
    • getBalance

      public static double getBalance(UUID uuid)
      Gets the balance of a player by UUID.
      Parameters:
      uuid - the player's UUID
      Returns:
      the balance, or 0 if economy is not available
    • has

      public static boolean has(org.bukkit.OfflinePlayer player, double amount)
      Checks if a player has at least the specified amount.
      Parameters:
      player - the player
      amount - the amount to check
      Returns:
      true if the player has at least the amount
    • has

      public static boolean has(UUID uuid, double amount)
      Checks if a player has at least the specified amount.
      Parameters:
      uuid - the player's UUID
      amount - the amount to check
      Returns:
      true if the player has at least the amount
    • deposit

      public static boolean deposit(org.bukkit.OfflinePlayer player, double amount)
      Deposits money into a player's account.
      Parameters:
      player - the player
      amount - the amount to deposit
      Returns:
      true if the deposit was successful
    • deposit

      public static boolean deposit(UUID uuid, double amount)
      Deposits money into a player's account.
      Parameters:
      uuid - the player's UUID
      amount - the amount to deposit
      Returns:
      true if the deposit was successful
    • withdraw

      public static boolean withdraw(org.bukkit.OfflinePlayer player, double amount)
      Withdraws money from a player's account.
      Parameters:
      player - the player
      amount - the amount to withdraw
      Returns:
      true if the withdrawal was successful
    • withdraw

      public static boolean withdraw(UUID uuid, double amount)
      Withdraws money from a player's account.
      Parameters:
      uuid - the player's UUID
      amount - the amount to withdraw
      Returns:
      true if the withdrawal was successful
    • transfer

      public static boolean transfer(org.bukkit.OfflinePlayer from, org.bukkit.OfflinePlayer to, double amount)
      Transfers money from one player to another.
      Parameters:
      from - the player to withdraw from
      to - the player to deposit to
      amount - the amount to transfer
      Returns:
      true if the transfer was successful
    • transfer

      public static boolean transfer(UUID from, UUID to, double amount)
      Transfers money from one player to another.
      Parameters:
      from - the UUID of the player to withdraw from
      to - the UUID of the player to deposit to
      amount - the amount to transfer
      Returns:
      true if the transfer was successful
    • format

      public static String format(double amount)
      Formats an amount according to the economy's format.
      Parameters:
      amount - the amount to format
      Returns:
      the formatted amount string
    • getCurrencyName

      public static String getCurrencyName()
      Gets the currency name (singular).
      Returns:
      the currency name
    • getCurrencyNamePlural

      public static String getCurrencyNamePlural()
      Gets the currency name (plural).
      Returns:
      the currency name (plural)
    • reset

      public static void reset()
      Resets the economy setup state. Used primarily for testing.
    • reportIfUnavailable

      private static void reportIfUnavailable()
      Called by every public operation above before delegating to provider. Reports at most once per calling module per server session, when the economy is unavailable — a request made while it IS available is always silent and never touches warnedModules.
    • reportEconomyStateIfUnavailable

      static void reportEconomyStateIfUnavailable(String moduleName)
      The WARN/dedup logic, taking the calling module's name directly rather than resolving it from the live call stack. Package-private so EconomyUtilsReportingTest can drive the seven D-08 behaviours directly, without needing a fully-registered module fixture just to exercise dedup/state-text logic that does not semantically depend on how the module's name was obtained. reportIfUnavailable() is the one real call site, which resolves the module name from the live stack via attributeCallingModule().
      Parameters:
      moduleName - the calling module's name, or null for an unattributed caller (behavior: a request whose stack contains no registered module package still logs once, attributed to UNKNOWN_MODULE, and never throws)
    • reportUnattributedIfUnavailable

      static void reportUnattributedIfUnavailable(String callerPackage)
      The WARN/dedup logic for a request attribution could not name (#489): the warning still names UNKNOWN_MODULE, exactly as before, but its once-per-session dedup is keyed by the package of the closest calling frame outside the framework and the JDK, so a second unattributable caller in a different package gets its own warning instead of none. The number of keys is bounded by the number of distinct calling packages. Package-private for the same reason as reportEconomyStateIfUnavailable(String).
      Parameters:
      callerPackage - the closest calling frame's package, or null when none could be determined (then the single UNKNOWN_MODULE slot is used)
    • unattributedCallerPackage

      static String unattributedCallerPackage(StackTraceElement[] stack)
      The package of the closest frame in stack that belongs neither to the framework (com.ultikits.ultitools.) nor to the JDK -- the unattributed caller's own code.
      Returns:
      that package, or null when no such frame exists or it is in the default package
    • isFrameworkOrJdkClass

      private static boolean isFrameworkOrJdkClass(String className)
    • buildWarningMessage

      private static String buildWarningMessage(String moduleName, EconomyProvider.State state)
    • log

      private static void log(String message, boolean info)
    • attributeCallingModule

      private static String attributeCallingModule()
      The module-attribution helper (D-08): the first stack frame belonging to a registered module's own scan packages, using the same Thread.getStackTrace() idiom SystemLogHandler already relies on for trigger inference — no StackWalker, since this project's bytecode target is Java 8.

      [Rule 1 fix, Codex P2, PR #463]: pluginManager.getPluginList() is PluginManager's live, unsynchronized ArrayList (returned directly by its @Getter) — PluginInstallUtils#uninstallPlugin mutates that same list via .remove(...), reachable from a normal /upm uninstall command. A module calling this facade from an async command or @Scheduled(async = true) task can race that mutation and see ConcurrentModificationException instead of a fallback value. Snapshotting into a fresh ArrayList narrows the window, but does not close it: ArrayList's copy constructor reads via toArray(), which itself reads the live elementData array and size field with no synchronization and no fail-fast modCount check — under a genuine cross-thread race, a structural change mid-copy can leave a trailing null in the copied array instead of ever throwing ConcurrentModificationException at all (Codex P2, PR #463, follow-up finding), which would otherwise surface as an uncaught NullPointerException from plugin.getClass() below. Both are the same underlying condition — attribution raced a concurrent structural change — so both are caught together and treated exactly like an already-existing case this method documents: unattributable, return null — matching this class's "never throws" contract (see log(String, boolean)).

      Returns:
      the attributed module's name, or null when nothing on the framework's own plugin list is currently reachable (no live UltiTools instance, no plugin manager, no registered module's scan package appears anywhere on the stack, the calling frame's closest-matching scan package is declared by two or more DIFFERENT modules — see mergeScanPackageOwner(Map, String, String), Codex P2, PR #463 — or the plugin list raced a concurrent structural change during attribution)
    • mergeScanPackageOwner

      static void mergeScanPackageOwner(Map<String,String> prefixToModule, String pkg, String owner)
      Merges one (scan package, module name) declaration into prefixToModule, in place. Two or more DIFFERENT modules declaring the identical scan-package string is recorded as ambiguous ownership by setting that entry's value to null — GREEN fix, Codex P2, PR #463, gate-2 finding on this branch's own code, EconomyUtils.java:440. The previous behaviour (Map#putIfAbsent, now replaced) silently kept only the FIRST-registered module for a shared root and discarded every later one, so which module a shared root's frames got attributed to depended on module load order, not on which module the request actually came from — the D-08 warning could name the wrong module, and the wrongly-named module's dedup slot silently absorbed the true caller's one-per-session warning, suppressing it entirely.

      attributeModule(StackTraceElement[], Map) needs NO change to honour this: a null-valued winning (longest-matching) entry already falls through its existing if (bestModule != null) check exactly like "no match for this frame" — the loop moves on to the next (outer) frame, and if nothing else matches, attribution ends in the same null it already returns for an unattributable caller, which the D-08 reporting path already reports as UNKNOWN_MODULE (never a second, per-module dedup slot).

      Why not resolve ownership from the calling class's ClassLoader/code source instead: every module in this framework is loaded by the same shared ultiToolsClassLoader (see UltiTools's bootstrap sequence), so neither signal cleanly separates two modules sharing one package in this codebase — building attribution on it would be the string-matching-on-a-third-party-convention mistake this project already removed once (see SecurityPolicy's deleted name-based classload filters) in a new position. Keeping every owner seen for a root and reporting an honest "unknown" when a root has more than one is reliable regardless of classloader topology, and costs nothing the normal (single-owner) case was already paying.

      Parameters:
      prefixToModule - the map being built, mutated in place; an existing null value (already-ambiguous) is left as null — one differing later owner is enough to mark a root ambiguous permanently for this build
      pkg - the scan package being registered
      owner - the module declaring it
    • addModuleRoots

      static void addModuleRoots(Map<String,String> prefixToModule, PluginManager pluginManager, Class<? extends UltiToolsPlugin> pluginClass, String owner)
      Merges one module's attribution roots into prefixToModule: every declared scan root (PluginManager.getPluginScanPackages(Class)), plus the module main class's own package as an implicit root (#489) -- a module whose declared roots do not cover its own package used to be unattributable for callers in that package. The implicit root goes through mergeScanPackageOwner(java.util.Map<java.lang.String, java.lang.String>, java.lang.String, java.lang.String) like a declared one, so the same rules apply: the longest matching prefix wins, and the same prefix claimed by two different modules is ambiguous. A root equal to one of the module's own declared roots changes nothing.
      Parameters:
      prefixToModule - the map being built, mutated in place
      pluginManager - source of the declared roots
      pluginClass - the module's main class
      owner - the module's name
    • matchFrame

      private static EconomyUtils.FrameMatch matchFrame(String className, Map<String,String> prefixToModule)
      Matches a single frame's class name against every entry in prefixToModule, applying the same most-specific-(longest)-prefix-wins rule attributeModule(java.lang.StackTraceElement[], java.util.Map<java.lang.String, java.lang.String>) has always used, and reports the outcome as a EconomyUtils.FrameMatch rather than a bare, ambiguity-erasing String.
      Parameters:
      className - the frame's fully-qualified class name
      prefixToModule - package prefix to sole owner, or to null when 2+ distinct modules declared it (see mergeScanPackageOwner(java.util.Map<java.lang.String, java.lang.String>, java.lang.String, java.lang.String))
      Returns:
      EconomyUtils.FrameMatch.NO_MATCH when nothing matches, EconomyUtils.FrameMatch.AMBIGUOUS_MATCH when the closest (longest) matching entry's value is null, or a EconomyUtils.FrameMatch.resolved(java.lang.String) result carrying that entry's sole owner otherwise
    • addExternalPluginRoots

      private static void addExternalPluginRoots(Map<String,String> prefixToModule, PluginManager pluginManager)
      Merges every connected External Plugin API consumer's scan package into prefixToModule under the plugin's name (#462), by the same rules as a module's roots. An empty scan package (a plugin main class in the default package) contributes nothing.
      Parameters:
      prefixToModule - the map being built, mutated in place
      pluginManager - source of the connected external plugins
    • attributeModule

      static String attributeModule(StackTraceElement[] stack, Map<String,String> prefixToModule)
      Pure: given a stack trace and a map from package prefix to module name, returns the module name attributed to the CLOSEST (most-recent) frame that matches ANY mapped prefix, or null when no frame in the whole stack matches — including the empty-input cases (a null/empty stack, or an empty prefix map, as when no module is registered yet). Package- private so EconomyUtilsReportingTest can unit-test the attribution logic itself with synthetic input, independent of a live UltiTools instance or real registered modules.

      When a single frame matches more than one mapped prefix — e.g. two registered modules declare nested scan roots "com.example" and "com.example.shop", and the frame's class is under com.example.shop — the most specific (longest) matching prefix wins, independent of prefixToModule's iteration/insertion order (Codex P2, PR #463, #482). Selecting by insertion order alone let module *load* order silently decide attribution for a frame that unambiguously belongs to the more specific package.

      The closest matching frame identifies the requester — full stop (Codex P2, PR #463, round 3). If that frame's own closest-matching root is itself ambiguous (2+ distinct modules declared it identically — see mergeScanPackageOwner(java.util.Map<java.lang.String, java.lang.String>, java.lang.String, java.lang.String)), the requester cannot be determined and this method returns null immediately, WITHOUT examining any more distant frame. An earlier version of this method treated an ambiguous frame exactly like a non-match and kept walking outward — which let a more distant, entirely unrelated module's unambiguous frame win the attribution, reproducing the wrong-module-name defect this whole fix exists to remove, just by a longer route. A module deeper in the call stack is not the requester merely because the real (closest) requester was ambiguous.

      Parameters:
      stack - the stack trace to search, most-recent frame first
      prefixToModule - package prefix to module name; order does not affect the result
      Returns:
      the attributed module name, or null when the closest matching frame (if any) is ambiguous, or when no frame matches at all