Class EconomyUtils
@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 ClassesModifier and TypeClassDescriptionprivate static final classOne frame's match outcome: theEconomyUtils.MatchKind, plus the resolved module name when (and only when)kind == RESOLVED.private static enumThe three possible outcomes of matching one stack frame againstprefixToModule(Codex P2, PR #463, round 3): a barenullreturn 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 — aNONEframe must let the per-frame walk continue outward (a real non-match, no evidence either way), while anAMBIGUOUSframe must terminate the walk immediately (real evidence that a module frame exists here, but WHICH module cannot be determined) — seeattributeModule(StackTraceElement[], Map)'s own javadoc for why continuing past an ambiguous frame is itself the bug. -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate static final String[]Frames skipped when locating an unattributed caller's own package (#489).private static EconomyProvider(package private) static final StringAttribution key used when no registered module's package appears anywhere on the calling stack. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprivate static voidaddExternalPluginRoots(Map<String, String> prefixToModule, PluginManager pluginManager) Merges every connected External Plugin API consumer's scan package intoprefixToModuleunder the plugin's name (#462), by the same rules as a module's roots.(package private) static voidaddModuleRoots(Map<String, String> prefixToModule, PluginManager pluginManager, Class<? extends UltiToolsPlugin> pluginClass, String owner) Merges one module's attribution roots intoprefixToModule: 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 StringThe module-attribution helper (D-08): the first stack frame belonging to a registered module's own scan packages, using the sameThread.getStackTrace()idiomSystemLogHandleralready relies on for trigger inference — noStackWalker, since this project's bytecode target is Java 8.(package private) static StringattributeModule(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, ornullwhen 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 StringbuildWarningMessage(String moduleName, EconomyProvider.State state) static booleanDeposits money into a player's account.static booleandeposit(org.bukkit.OfflinePlayer player, double amount) Deposits money into a player's account.static Stringformat(double amount) Formats an amount according to the economy's format.static doublegetBalance(UUID uuid) Gets the balance of a player by UUID.static doublegetBalance(org.bukkit.OfflinePlayer player) Gets the balance of a player.static StringGets the currency name (singular).static StringGets the currency name (plural).static net.milkbowl.vault.economy.EconomyGets the economy instance.static booleanChecks if a player has at least the specified amount.static booleanhas(org.bukkit.OfflinePlayer player, double amount) Checks if a player has at least the specified amount.static booleanChecks if Vault economy is available.private static booleanisFrameworkOrJdkClass(String className) private static voidstatic voidLogs the framework's one start-up line naming the current economy service state (D-08).private static EconomyUtils.FrameMatchmatchFrame(String className, Map<String, String> prefixToModule) Matches a single frame's class name against every entry inprefixToModule, applying the same most-specific-(longest)-prefix-wins ruleattributeModule(java.lang.StackTraceElement[], java.util.Map<java.lang.String, java.lang.String>)has always used, and reports the outcome as aEconomyUtils.FrameMatchrather than a bare, ambiguity-erasingString.(package private) static voidMerges one (scan package, module name) declaration intoprefixToModule, in place.(package private) static voidreportEconomyStateIfUnavailable(String moduleName) The WARN/dedup logic, taking the calling module's name directly rather than resolving it from the live call stack.private static voidCalled by every public operation above before delegating toprovider.(package private) static voidreportUnattributedIfUnavailable(String callerPackage) The WARN/dedup logic for a request attribution could not name (#489): the warning still namesUNKNOWN_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 voidreset()Resets the economy setup state.static voidsetProvider(EconomyProvider provider) Framework-internal wiring point — called once byDependenceManagersat bootstrap with the sameEconomyProviderinstance it registers into the IoC container.static booleansetup()Checks whether the economy provider is currently set up.static booleanTransfers money from one player to another.static booleantransfer(org.bukkit.OfflinePlayer from, org.bukkit.OfflinePlayer to, double amount) Transfers money from one player to another.(package private) static StringThe package of the closest frame instackthat belongs neither to the framework (com.ultikits.ultitools.) nor to the JDK -- the unattributed caller's own code.static booleanWithdraws money from a player's account.static booleanwithdraw(org.bukkit.OfflinePlayer player, double amount) Withdraws money from a player's account.
-
Field Details
-
UNKNOWN_MODULE
Attribution key used when no registered module's package appears anywhere on the calling stack.- See Also:
-
provider
-
warnedModules
-
FRAMEWORK_AND_JDK_PREFIXES
Frames skipped when locating an unattributed caller's own package (#489).
-
-
Constructor Details
-
EconomyUtils
private EconomyUtils()
-
-
Method Details
-
setProvider
Framework-internal wiring point — called once byDependenceManagersat bootstrap with the sameEconomyProviderinstance it registers into the IoC container. Not meant to be called by module code; public only becauseDependenceManagerslives 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 byDependenceManagersaftersetProvider(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
provideron 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
Gets the economy instance.[Rule 1 fix, CR-01, 16-07]: re-checks the live
provideron 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 — see16-REVIEW-economy.mdCR-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
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 playeramount- the amount to check- Returns:
- true if the player has at least the amount
-
has
Checks if a player has at least the specified amount.- Parameters:
uuid- the player's UUIDamount- 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 playeramount- the amount to deposit- Returns:
- true if the deposit was successful
-
deposit
Deposits money into a player's account.- Parameters:
uuid- the player's UUIDamount- 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 playeramount- the amount to withdraw- Returns:
- true if the withdrawal was successful
-
withdraw
Withdraws money from a player's account.- Parameters:
uuid- the player's UUIDamount- 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 fromto- the player to deposit toamount- the amount to transfer- Returns:
- true if the transfer was successful
-
transfer
Transfers money from one player to another.- Parameters:
from- the UUID of the player to withdraw fromto- the UUID of the player to deposit toamount- the amount to transfer- Returns:
- true if the transfer was successful
-
format
Formats an amount according to the economy's format.- Parameters:
amount- the amount to format- Returns:
- the formatted amount string
-
getCurrencyName
Gets the currency name (singular).- Returns:
- the currency name
-
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. -
unattributedCallerPackage
The package of the closest frame instackthat belongs neither to the framework (com.ultikits.ultitools.) nor to the JDK -- the unattributed caller's own code.- Returns:
- that package, or
nullwhen no such frame exists or it is in the default package
-
isFrameworkOrJdkClass
-
buildWarningMessage
-
log
-
attributeCallingModule
The module-attribution helper (D-08): the first stack frame belonging to a registered module's own scan packages, using the sameThread.getStackTrace()idiomSystemLogHandleralready relies on for trigger inference — noStackWalker, since this project's bytecode target is Java 8.[Rule 1 fix, Codex P2, PR #463]:
pluginManager.getPluginList()isPluginManager's live, unsynchronizedArrayList(returned directly by its@Getter) —PluginInstallUtils#uninstallPluginmutates that same list via.remove(...), reachable from a normal/upm uninstallcommand. A module calling this facade from an async command or@Scheduled(async = true)task can race that mutation and seeConcurrentModificationExceptioninstead of a fallback value. Snapshotting into a freshArrayListnarrows the window, but does not close it:ArrayList's copy constructor reads viatoArray(), which itself reads the liveelementDataarray andsizefield with no synchronization and no fail-fast modCount check — under a genuine cross-thread race, a structural change mid-copy can leave a trailingnullin the copied array instead of ever throwingConcurrentModificationExceptionat all (Codex P2, PR #463, follow-up finding), which would otherwise surface as an uncaughtNullPointerExceptionfromplugin.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, returnnull— matching this class's "never throws" contract (seelog(String, boolean)).- Returns:
- the attributed module's name, or
nullwhen nothing on the framework's own plugin list is currently reachable (no liveUltiToolsinstance, 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 — seemergeScanPackageOwner(Map, String, String), Codex P2, PR #463 — or the plugin list raced a concurrent structural change during attribution)
-
mergeScanPackageOwner
Merges one (scan package, module name) declaration intoprefixToModule, in place. Two or more DIFFERENT modules declaring the identical scan-package string is recorded as ambiguous ownership by setting that entry's value tonull— 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: anull-valued winning (longest-matching) entry already falls through its existingif (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 samenullit already returns for an unattributable caller, which the D-08 reporting path already reports asUNKNOWN_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 sharedultiToolsClassLoader(seeUltiTools'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 (seeSecurityPolicy'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 existingnullvalue (already-ambiguous) is left asnull— one differing later owner is enough to mark a root ambiguous permanently for this buildpkg- the scan package being registeredowner- 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 intoprefixToModule: 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 throughmergeScanPackageOwner(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 placepluginManager- source of the declared rootspluginClass- the module's main classowner- 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 inprefixToModule, applying the same most-specific-(longest)-prefix-wins ruleattributeModule(java.lang.StackTraceElement[], java.util.Map<java.lang.String, java.lang.String>)has always used, and reports the outcome as aEconomyUtils.FrameMatchrather than a bare, ambiguity-erasingString.- Parameters:
className- the frame's fully-qualified class nameprefixToModule- package prefix to sole owner, or tonullwhen 2+ distinct modules declared it (seemergeScanPackageOwner(java.util.Map<java.lang.String, java.lang.String>, java.lang.String, java.lang.String))- Returns:
EconomyUtils.FrameMatch.NO_MATCHwhen nothing matches,EconomyUtils.FrameMatch.AMBIGUOUS_MATCHwhen the closest (longest) matching entry's value isnull, or aEconomyUtils.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 intoprefixToModuleunder 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 placepluginManager- source of the connected external plugins
-
attributeModule
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, ornullwhen 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 soEconomyUtilsReportingTestcan unit-test the attribution logic itself with synthetic input, independent of a liveUltiToolsinstance 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 undercom.example.shop— the most specific (longest) matching prefix wins, independent ofprefixToModule'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 returnsnullimmediately, 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 firstprefixToModule- package prefix to module name; order does not affect the result- Returns:
- the attributed module name, or
nullwhen the closest matching frame (if any) is ambiguous, or when no frame matches at all
-