From c2a58a471fed9372ec760084fe3d60ce48c34782 Mon Sep 17 00:00:00 2001 From: Aikar Date: Sat, 20 May 2017 01:45:56 -0400 Subject: [PATCH] Refactor everything to a modular format - WIP This does not compile Almost done with core !!! --- .idea/compiler.xml | 5 + .idea/copyright/profiles_settings.xml | 6 +- .idea/encodings.xml | 2 + .idea/modules.xml | 3 + .idea/scopes/All_But_Apache_Commons.xml | 3 + APACHE_COMMONS_LANG_LICENSE.txt | 202 +++ acf-parent.iml | 13 + bukkit/acf-bukkit.iml | 33 + bukkit/pom.xml | 55 + .../java/co/aikar/commands/ACFBukkitUtil.java | 302 ++++ .../commands/BukkitCommandCompletions.java | 0 .../aikar/commands/BukkitCommandContexts.java | 18 +- .../commands/BukkitCommandContexts_1_12.java | 0 .../BukkitCommandExecutionContext.java | 41 + .../aikar/commands/BukkitCommandIssuer.java | 56 + .../aikar/commands/BukkitCommandManager.java | 41 +- .../co/aikar/commands/BukkitRootCommand.java | 114 ++ .../aikar/commands/contexts/OnlinePlayer.java | 0 core/acf-core.iml | 17 +- core/pom.xml | 88 +- .../main/java/co/aikar/commands/ACFLog.java | 8 +- .../main/java/co/aikar/commands/ACFUtil.java | 322 +--- .../java/co/aikar/commands/BaseCommand.java | 100 +- .../co/aikar/commands/BaseSubCommand.java | 15 - .../co/aikar/commands/CommandCompletions.java | 13 +- .../co/aikar/commands/CommandContexts.java | 27 +- .../commands/CommandExecutionContext.java | 17 +- .../commands/{ACF.java => CommandIssuer.java} | 40 +- .../co/aikar/commands/CommandManager.java | 7 +- .../co/aikar/commands/ForwardingCommand.java | 15 +- .../co/aikar/commands/RegisteredCommand.java | 30 +- .../java/co/aikar/commands/RootCommand.java | 76 +- .../ApacheCommonsExceptionUtil.java | 1014 ++++++++++++ .../ApacheCommonsLangUtil.java | 1399 +++++++++++++++++ .../commands/contexts/ContextResolver.java | 4 +- .../contexts/SenderAwareContextResolver.java | 4 +- example/acf-example.iml | 3 +- example/pom.xml | 7 +- .../java/co/aikar/acfexample/ACFExample.java | 4 +- .../acfexample/SomeCommand_ExtraSubs.java | 4 +- paper/acf-paper.iml | 34 + paper/pom.xml | 60 + .../commands/PaperCommandCompletions.java | 0 .../aikar/commands/PaperCommandContexts.java | 0 .../aikar/commands/PaperCommandManager.java | 0 pom.xml | 106 ++ 46 files changed, 3651 insertions(+), 657 deletions(-) create mode 100644 .idea/scopes/All_But_Apache_Commons.xml create mode 100644 APACHE_COMMONS_LANG_LICENSE.txt create mode 100644 acf-parent.iml create mode 100644 bukkit/acf-bukkit.iml create mode 100644 bukkit/pom.xml create mode 100644 bukkit/src/main/java/co/aikar/commands/ACFBukkitUtil.java rename {core => bukkit}/src/main/java/co/aikar/commands/BukkitCommandCompletions.java (100%) rename {core => bukkit}/src/main/java/co/aikar/commands/BukkitCommandContexts.java (87%) rename {core => bukkit}/src/main/java/co/aikar/commands/BukkitCommandContexts_1_12.java (100%) create mode 100644 bukkit/src/main/java/co/aikar/commands/BukkitCommandExecutionContext.java create mode 100644 bukkit/src/main/java/co/aikar/commands/BukkitCommandIssuer.java rename {core => bukkit}/src/main/java/co/aikar/commands/BukkitCommandManager.java (86%) create mode 100644 bukkit/src/main/java/co/aikar/commands/BukkitRootCommand.java rename {core => bukkit}/src/main/java/co/aikar/commands/contexts/OnlinePlayer.java (100%) rename core/src/main/java/co/aikar/commands/{ACF.java => CommandIssuer.java} (68%) create mode 100644 core/src/main/java/co/aikar/commands/apachecommonslang/ApacheCommonsExceptionUtil.java create mode 100644 core/src/main/java/co/aikar/commands/apachecommonslang/ApacheCommonsLangUtil.java create mode 100644 paper/acf-paper.iml create mode 100644 paper/pom.xml rename {core => paper}/src/main/java/co/aikar/commands/PaperCommandCompletions.java (100%) rename {core => paper}/src/main/java/co/aikar/commands/PaperCommandContexts.java (100%) rename {core => paper}/src/main/java/co/aikar/commands/PaperCommandManager.java (100%) create mode 100644 pom.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml index f2a31b86..c1adabf1 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -8,15 +8,20 @@ + + + + + diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml index c3ba54ac..ea20a94c 100644 --- a/.idea/copyright/profiles_settings.xml +++ b/.idea/copyright/profiles_settings.xml @@ -1,3 +1,7 @@ - + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml index c41f8423..ad72c92c 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -2,8 +2,10 @@ + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 704caab6..8305a192 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,8 +2,11 @@ + + + \ No newline at end of file diff --git a/.idea/scopes/All_But_Apache_Commons.xml b/.idea/scopes/All_But_Apache_Commons.xml new file mode 100644 index 00000000..a0889b2b --- /dev/null +++ b/.idea/scopes/All_But_Apache_Commons.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/APACHE_COMMONS_LANG_LICENSE.txt b/APACHE_COMMONS_LANG_LICENSE.txt new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/APACHE_COMMONS_LANG_LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/acf-parent.iml b/acf-parent.iml new file mode 100644 index 00000000..7436347f --- /dev/null +++ b/acf-parent.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/bukkit/acf-bukkit.iml b/bukkit/acf-bukkit.iml new file mode 100644 index 00000000..afbbfe30 --- /dev/null +++ b/bukkit/acf-bukkit.iml @@ -0,0 +1,33 @@ + + + + + + + BUKKIT + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/bukkit/pom.xml b/bukkit/pom.xml new file mode 100644 index 00000000..c984cfb6 --- /dev/null +++ b/bukkit/pom.xml @@ -0,0 +1,55 @@ + + + + + 4.0.0 + + + co.aikar + acf-parent + 0.5.0 + ../pom.xml + + + acf-bukkit + 0.5.0 + + ACF (Bukkit) + + + + co.aikar + acf-core + 0.5.0 + + + org.bukkit + bukkit + 1.12-pre2-SNAPSHOT + provided + + + diff --git a/bukkit/src/main/java/co/aikar/commands/ACFBukkitUtil.java b/bukkit/src/main/java/co/aikar/commands/ACFBukkitUtil.java new file mode 100644 index 00000000..f402f014 --- /dev/null +++ b/bukkit/src/main/java/co/aikar/commands/ACFBukkitUtil.java @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package co.aikar.commands; + +import com.google.common.collect.Iterables; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.WordUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +public class ACFBukkitUtil { + + public static String formatLocation(Location loc) { + if (loc == null) { + return null; + } + return loc.getWorld().getName() + + ":" + + loc.getBlockX() + + "," + + loc.getBlockY() + + "," + + loc.getBlockZ(); + } + + public static String color(String message) { + return ChatColor.translateAlternateColorCodes('&', message); + } + + public static void sendMsg(CommandIssuer player, String message) { + message = color(message); + if (player == null) { + for (String msg : ACFPatterns.NEWLINE.split(message)) { + ACFLog.info(msg); + } + } else { + for (String msg : ACFPatterns.NEWLINE.split(message)) { + player.sendMessage(msg); + } + } + } + + public static Location stringToLocation(String storedLoc) { + return stringToLocation(storedLoc, null); + } + public static Location stringToLocation(String storedLoc, World forcedWorld) { + if (storedLoc == null) { + return null; + } + String[] args = ACFPatterns.COLON.split(storedLoc); + if (args.length >= 4 || (args.length == 3 && forcedWorld != null)) { + String world = forcedWorld != null ? forcedWorld.getName() : args[0]; + int i = args.length == 3 ? 0 : 1; + double x = Double.parseDouble(args[i]); + double y = Double.parseDouble(args[i + 1]); + double z = Double.parseDouble(args[i + 2]); + Location loc = new Location(Bukkit.getWorld(world), x, y, z); + if (args.length >= 6) { + loc.setPitch(Float.parseFloat(args[4])); + loc.setYaw(Float.parseFloat(args[5])); + } + return loc; + } else if (args.length == 2) { + String[] args2 = ACFPatterns.COMMA.split(args[1]); + if (args2.length == 3) { + String world = forcedWorld != null ? forcedWorld.getName() : args[0]; + double x = Double.parseDouble(args2[0]); + double y = Double.parseDouble(args2[1]); + double z = Double.parseDouble(args2[2]); + return new Location(Bukkit.getWorld(world), x, y, z); + } + } + return null; + } + + public static String fullLocationToString(Location loc) { + if (loc == null) { + return null; + } + return (new StringBuilder(64)) + .append(loc.getWorld().getName()) + .append(':') + .append(ACFUtil.precision(loc.getX(), 4)) + .append(':') + .append(ACFUtil.precision(loc.getY(), 4)) + .append(':') + .append(ACFUtil.precision(loc.getZ(), 4)) + .append(':') + .append(ACFUtil.precision(loc.getPitch(), 4)) + .append(':') + .append(ACFUtil.precision(loc.getYaw(), 4)) + .toString(); + } + + public static String fullBlockLocationToString(Location loc) { + if (loc == null) { + return null; + } + return (new StringBuilder(64)) + .append(loc.getWorld().getName()) + .append(':') + .append(loc.getBlockX()) + .append(':') + .append(loc.getBlockY()) + .append(':') + .append(loc.getBlockZ()) + .append(':') + .append(ACFUtil.precision(loc.getPitch(), 4)) + .append(':') + .append(ACFUtil.precision(loc.getYaw(), 4)) + .toString(); + } + + public static String blockLocationToString(Location loc) { + if (loc == null) { + return null; + } + + return (new StringBuilder(32)) + .append(loc.getWorld().getName()) + .append(':') + .append(loc.getBlockX()) + .append(':') + .append(loc.getBlockY()) + .append(':') + .append(loc.getBlockZ()) + .toString(); + } + + public static double distance(@NotNull Entity e1, @NotNull Entity e2) { + return distance(e1.getLocation(), e2.getLocation()); + } + public static double distance2d(@NotNull Entity e1, @NotNull Entity e2) { + return distance2d(e1.getLocation(), e2.getLocation()); + } + public static double distance2d(@NotNull Location loc1, @NotNull Location loc2) { + loc1 = loc1.clone(); + loc1.setY(loc2.getY()); + return distance(loc1, loc2); + } + public static double distance(@NotNull Location loc1, @NotNull Location loc2) { + if (loc1.getWorld() != loc2.getWorld()) { + return 0; + } + return loc1.distance(loc2); + } + + public static Location getTargetLoc(Player player) { + return getTargetLoc(player, 128); + } + public static Location getTargetLoc(Player player, int maxDist) { + return getTargetLoc(player, maxDist, 1.5); + } + public static Location getTargetLoc(Player player, int maxDist, double addY) { + try { + Location target = player.getTargetBlock((Set) null, maxDist).getLocation(); + target.setY(target.getY() + addY); + return target; + } catch (Exception ignored) { + return null; + } + } + + public static Location getRandLoc(Location loc, int radius) { + return getRandLoc(loc, radius, radius, radius); + } + public static Location getRandLoc(Location loc, int xzRadius, int yRadius) { + return getRandLoc(loc, xzRadius, yRadius, xzRadius); + } + @NotNull public static Location getRandLoc(Location loc, int xRadius, int yRadius, int zRadius) { + Location newLoc = loc.clone(); + newLoc.setX(ACFUtil.rand(loc.getX()-xRadius, loc.getX()+xRadius)); + newLoc.setY(ACFUtil.rand(loc.getY()-yRadius, loc.getY()+yRadius)); + newLoc.setZ(ACFUtil.rand(loc.getZ()-zRadius, loc.getZ()+zRadius)); + return newLoc; + } + + + public static String removeColors(String msg) { + return ChatColor.stripColor(color(msg)); + } + + public static String replaceChatString(String message, String replace, String with) { + return replaceChatString(message, Pattern.compile(Pattern.quote(replace), Pattern.CASE_INSENSITIVE), with); + } + public static String replaceChatString(String message, Pattern replace, String with) { + final String[] split = replace.split(message + "1"); + + if (split.length < 2) { + return replace.matcher(message).replaceAll(with); + } + message = split[0]; + + for (int i = 1; i < split.length; i++) { + final String prev = ChatColor.getLastColors(message); + message += with + prev + split[i]; + } + return message.substring(0, message.length() - 1); + } + + public static boolean isWithinDistance(@NotNull Player p1, @NotNull Player p2, int dist) { + return isWithinDistance(p1.getLocation(), p2.getLocation(), dist); + } + public static boolean isWithinDistance(@NotNull Location loc1, @NotNull Location loc2, int dist) { + return loc1.getWorld() == loc2.getWorld() && loc1.distance(loc2) <= dist; + } + + public static Player findPlayerSmart(CommandIssuer requester, String origName) { + String name = ACFUtil.replace(origName, ":confirm", ""); + if (name.length() < 3) { + requester.sendMessage("§cUsername too short, must be at least three characters"); + return null; + } + if (!isValidName(name)) { + requester.sendMessage("§c'" + name + "' is not a valid username"); + return null; + } + + List matches = Bukkit.getServer().matchPlayer(name); + List confirmList = new ArrayList<>(); + + // Remove confirmList players from smart matching. + Iterator iter = matches.iterator(); + while (iter.hasNext()) { + Player player = iter.next(); + if (requester instanceof Player && !((Player) requester).canSee(player)) { + if (requester.hasPermission("acf.seevanish")) { + if (!origName.endsWith(":confirm")) { + confirmList.add(player); + iter.remove(); + } + } else { + iter.remove(); + } + } + } + + if (matches.size() > 1 || confirmList.size() > 1) { + requester.sendMessage("§cMultiple players matched '" + name + "', please be more specific"); + return null; + } + + if (matches.isEmpty()) { + if (confirmList.isEmpty()) { + requester.sendMessage("§cNo player matching '" + name + "' is connected to this server"); + return null; + } else { + Player player = Iterables.getOnlyElement(confirmList); + sendMsg(requester, + "&cWarning: " + player.getDisplayName() + "&c is vanished. Do not blow their cover!\n" + + "&cTo confirm your action, add &f:confirm&c to the end of their name. \n" + + "&bEx: &e/g " + player.getName() + ":confirm"); + return null; + } + } + + return matches.get(0); + } + + + public static boolean isValidName(String name) { + return name != null && !name.isEmpty() && ACFPatterns.VALID_NAME_PATTERN.matcher(name).matches(); + } + + static boolean isValidItem(ItemStack item) { + return item != null && item.getType() != Material.AIR && item.getAmount() > 0; + } +} diff --git a/core/src/main/java/co/aikar/commands/BukkitCommandCompletions.java b/bukkit/src/main/java/co/aikar/commands/BukkitCommandCompletions.java similarity index 100% rename from core/src/main/java/co/aikar/commands/BukkitCommandCompletions.java rename to bukkit/src/main/java/co/aikar/commands/BukkitCommandCompletions.java diff --git a/core/src/main/java/co/aikar/commands/BukkitCommandContexts.java b/bukkit/src/main/java/co/aikar/commands/BukkitCommandContexts.java similarity index 87% rename from core/src/main/java/co/aikar/commands/BukkitCommandContexts.java rename to bukkit/src/main/java/co/aikar/commands/BukkitCommandContexts.java index 72b9a89d..5e9a452b 100644 --- a/core/src/main/java/co/aikar/commands/BukkitCommandContexts.java +++ b/bukkit/src/main/java/co/aikar/commands/BukkitCommandContexts.java @@ -33,26 +33,25 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.inventory.PlayerInventory; -import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @SuppressWarnings("WeakerAccess") -public class BukkitCommandContexts extends CommandContexts { +public class BukkitCommandContexts extends CommandContexts { public BukkitCommandContexts(BukkitCommandManager manager) { super(manager); registerContext(OnlinePlayer.class, (c) -> { final String playercheck = c.popFirstArg(); - Player player = ACFUtil.findPlayerSmart(c.getSender(), playercheck); + Player player = ACFBukkitUtil.findPlayerSmart(c.getIssuer(), playercheck); if (player == null) { if (c.hasAnnotation(Optional.class)) { return null; } - ACFUtil.sendMsg(c.getSender(), "&cCould not find a player by the name " + playercheck); + ACFBukkitUtil.sendMsg(c.getIssuer(), "&cCould not find a player by the name " + playercheck); throw new InvalidCommandArgument(false); } return new OnlinePlayer(player); @@ -63,22 +62,22 @@ public class BukkitCommandContexts extends CommandContexts { if (world != null) { c.popFirstArg(); } - if (world == null && c.getSender() instanceof Player) { - world = ((Entity) c.getSender()).getWorld(); + if (world == null && c.getIssuer() instanceof Player) { + world = ((Entity) c.getIssuer()).getWorld(); } if (world == null) { throw new InvalidCommandArgument("Invalid World"); } return world; }); - registerSenderAwareContext(CommandSender.class, CommandExecutionContext::getSender); + registerSenderAwareContext(CommandSender.class, bukkitCommandExecutionContext -> bukkitCommandExecutionContext.getSender()); registerSenderAwareContext(Player.class, (c) -> { - Player player = c.getSender() instanceof Player ? (Player) c.getSender() : null; + Player player = c.getIssuer() instanceof Player ? (Player) c.getIssuer() : null; if (player == null && !c.hasAnnotation(Optional.class)) { throw new InvalidCommandArgument("Requires a player to run this command", false); } PlayerInventory inventory = player != null ? player.getInventory() : null; - if (inventory != null && c.hasFlag("itemheld") && !ACFUtil.isValidItem(inventory.getItem(inventory.getHeldItemSlot()))) { + if (inventory != null && c.hasFlag("itemheld") && !ACFBukkitUtil.isValidItem(inventory.getItem(inventory.getHeldItemSlot()))) { throw new InvalidCommandArgument("You must be holding an item in your main hand.", false); } return player; @@ -116,6 +115,5 @@ public class BukkitCommandContexts extends CommandContexts { BukkitCommandContexts_1_12.register(this); } } - } } diff --git a/core/src/main/java/co/aikar/commands/BukkitCommandContexts_1_12.java b/bukkit/src/main/java/co/aikar/commands/BukkitCommandContexts_1_12.java similarity index 100% rename from core/src/main/java/co/aikar/commands/BukkitCommandContexts_1_12.java rename to bukkit/src/main/java/co/aikar/commands/BukkitCommandContexts_1_12.java diff --git a/bukkit/src/main/java/co/aikar/commands/BukkitCommandExecutionContext.java b/bukkit/src/main/java/co/aikar/commands/BukkitCommandExecutionContext.java new file mode 100644 index 00000000..973b69df --- /dev/null +++ b/bukkit/src/main/java/co/aikar/commands/BukkitCommandExecutionContext.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package co.aikar.commands; + +import org.bukkit.command.CommandSender; + +import java.lang.reflect.Parameter; +import java.util.List; +import java.util.Map; + +public class BukkitCommandExecutionContext extends CommandExecutionContext { + BukkitCommandExecutionContext(RegisteredCommand cmd, Parameter param, CommandIssuer sender, List args, + int index, Map passedArgs) { + super(cmd, param, sender, args, index, passedArgs); + } + + public CommandSender getSender() { + return this.issuer.getIssuer(); + } +} diff --git a/bukkit/src/main/java/co/aikar/commands/BukkitCommandIssuer.java b/bukkit/src/main/java/co/aikar/commands/BukkitCommandIssuer.java new file mode 100644 index 00000000..cf908e17 --- /dev/null +++ b/bukkit/src/main/java/co/aikar/commands/BukkitCommandIssuer.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package co.aikar.commands; + +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class BukkitCommandIssuer implements CommandIssuer { + private final CommandSender sender; + + BukkitCommandIssuer(CommandSender sender) { + this.sender = sender; + } + + @Override + public boolean isPlayer() { + return sender instanceof Player; + } + + @Override + public T getIssuer() { + //noinspection unchecked + return (T) sender; + } + + @Override + public void sendMessage(String message) { + sender.sendMessage(message); + } + + @Override + public boolean hasPermission(String name) { + return sender.hasPermission(name); + } +} diff --git a/core/src/main/java/co/aikar/commands/BukkitCommandManager.java b/bukkit/src/main/java/co/aikar/commands/BukkitCommandManager.java similarity index 86% rename from core/src/main/java/co/aikar/commands/BukkitCommandManager.java rename to bukkit/src/main/java/co/aikar/commands/BukkitCommandManager.java index 0e73a4e6..08472a98 100644 --- a/core/src/main/java/co/aikar/commands/BukkitCommandManager.java +++ b/bukkit/src/main/java/co/aikar/commands/BukkitCommandManager.java @@ -38,6 +38,7 @@ import org.bukkit.plugin.Plugin; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.lang.reflect.Parameter; import java.util.*; @SuppressWarnings("WeakerAccess") @@ -48,7 +49,7 @@ public class BukkitCommandManager extends CommandManager { private final CommandMap commandMap; private final TimingManager timingManager; protected Map knownCommands = new HashMap<>(); - protected Map registeredCommands = new HashMap<>(); + protected Map registeredCommands = new HashMap<>(); protected CommandContexts contexts; protected CommandCompletions completions; @@ -107,32 +108,28 @@ public class BukkitCommandManager extends CommandManager { command.onRegister(this); for (Map.Entry entry : command.registeredCommands.entrySet()) { String key = entry.getKey().toLowerCase(); - RootCommand value = entry.getValue(); + BukkitRootCommand value = (BukkitRootCommand) entry.getValue(); if (!value.isRegistered) { commandMap.register(key, plugin, value); } value.isRegistered = true; - registeredCommands.put(key, command); + registeredCommands.put(key, value); } } - public void unregisterCommand(BaseCommand command) { + public void unregisterCommand(BukkitRootCommand command) { final String plugin = this.plugin.getName().toLowerCase(); - command.registeredCommands.entrySet().removeIf(entry -> { - Command cmd = entry.getValue(); - cmd.unregister(commandMap); - String key = entry.getKey(); - Command registered = knownCommands.get(key); - if (registered == command) { - knownCommands.remove(key); - } - knownCommands.remove(plugin + ":" + key); - return true; - }); + command.unregister(commandMap); + String key = command.getName(); + Command registered = knownCommands.get(key); + if (command.equals(registered)) { + knownCommands.remove(key); + } + knownCommands.remove(plugin + ":" + key); } public void unregisterCommands() { - for (Map.Entry entry : registeredCommands.entrySet()) { + for (Map.Entry entry : registeredCommands.entrySet()) { unregisterCommand(entry.getValue()); } } @@ -158,6 +155,16 @@ public class BukkitCommandManager extends CommandManager { return timingManager; } + @Override + public RootCommand createRootCommand(String cmd) { + return new BukkitRootCommand(this, cmd); + } + + @Override + public CommandExecutionContext createCommandContext(RegisteredCommand command, Parameter parameter, CommandIssuer sender, List args, int i, Map passedArgs) { + return new BukkitCommandExecutionContext(command, parameter, sender, args, i, passedArgs); + } + class ProxyCommandMap extends SimpleCommandMap { CommandMap proxied; @@ -187,7 +194,7 @@ public class BukkitCommandManager extends CommandManager { } boolean isOurCommand(Command command) { - return command instanceof BaseCommand && ((BaseCommand) command).manager == BukkitCommandManager.this; + return command instanceof RootCommand && ((RootCommand) command).getManager() == BukkitCommandManager.this; } @Override diff --git a/bukkit/src/main/java/co/aikar/commands/BukkitRootCommand.java b/bukkit/src/main/java/co/aikar/commands/BukkitRootCommand.java new file mode 100644 index 00000000..982ccc39 --- /dev/null +++ b/bukkit/src/main/java/co/aikar/commands/BukkitRootCommand.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package co.aikar.commands; + +import org.apache.commons.lang.StringUtils; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class BukkitRootCommand extends Command implements RootCommand { + + private final BukkitCommandManager manager; + private final String name; + private BaseCommand defCommand; + private Map subCommands = new HashMap<>(); + private List children = new ArrayList<>(); + boolean isRegistered = false; + + BukkitRootCommand(BukkitCommandManager manager, String name) { + super(name); + this.manager = manager; + this.name = name; + } + + @Override + public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { + return tabComplete(new BukkitCommandIssuer(sender), alias, args); + } + + @Override + public boolean execute(CommandSender sender, String commandLabel, String[] args) { + return execute(new BukkitCommandIssuer(sender), commandLabel, args); + } + + private List tabComplete(CommandIssuer sender, String alias, String[] args) throws IllegalArgumentException { + Set completions = new HashSet<>(); + this.children.forEach(child -> completions.addAll(child.tabComplete(sender, alias, args))); + return new ArrayList<>(completions); + } + + private boolean execute(CommandIssuer sender, String commandLabel, String[] args) { + for (int i = args.length; i >= 0; i--) { + String checkSub = StringUtils.join(args, " ", 0, i).toLowerCase(); + BaseCommand subHandler = this.subCommands.get(checkSub); + if (subHandler != null) { + if (!subHandler.testPermission(sender)) { + return true; + } + subHandler.execute(sender, commandLabel, args); + return false; + } + } + if (!this.defCommand.testPermission(sender)) { + return true; + } + + this.defCommand.execute(sender, commandLabel, args); + return false; + } + + public void addChild(BaseCommand command) { + if (this.defCommand == null || !command.subCommands.get("__default").isEmpty()) { + this.defCommand = command; + //this.setDescription(command.getDescription()); + //this.setUsage(command.getUsage()); + //this.setAliases(command.getAliases()); + } + command.subCommands.keySet().forEach(key -> { + if (key.equals(BaseCommand.DEFAULT) || key.equals(BaseCommand.UNKNOWN)) { + return; + } + BaseCommand regged = this.subCommands.get(key); + if (regged != null) { + ACFLog.severe("ACF Error: " + command.getName() + " registered subcommand " + key + " for root command " + getName() + " - but it is already defined in " + regged.getName()); + ACFLog.severe("2 subcommands of the same prefix may not be spread over 2 different classes. Ignoring this."); + return; + } + this.subCommands.put(key, command); + }); + this.children.add(command); + } + + @Override + public CommandManager getManager() { + return manager; + } +} diff --git a/core/src/main/java/co/aikar/commands/contexts/OnlinePlayer.java b/bukkit/src/main/java/co/aikar/commands/contexts/OnlinePlayer.java similarity index 100% rename from core/src/main/java/co/aikar/commands/contexts/OnlinePlayer.java rename to bukkit/src/main/java/co/aikar/commands/contexts/OnlinePlayer.java diff --git a/core/acf-core.iml b/core/acf-core.iml index 5ea36671..e937ca62 100644 --- a/core/acf-core.iml +++ b/core/acf-core.iml @@ -2,11 +2,7 @@ - - - BUKKIT - - + @@ -20,15 +16,8 @@ - - - - - - - - - + + \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml index d5e6f980..ed5fdfc0 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -27,90 +27,30 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - co.aikar + + co.aikar + acf-parent + 0.5.0 + ../pom.xml + + acf-core - 0.4.0-SNAPSHOT + 0.5.0 - ACF - - - UTF-8 - - - - - aikar - http://ci.emc.gs/nexus/content/repositories/aikar-release/ - - - aikar - http://ci.emc.gs/nexus/content/repositories/aikar-snapshots/ - - - - - - - aikar - http://ci.emc.gs/nexus/content/groups/aikar/ - - - paper - https://repo.destroystokyo.com/repository/maven-public/ - - - - - clean install - ${project.artifactId} - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.2 - - 1.8 - 1.8 - false - false - - -parameters - - - - - org.apache.maven.plugins - maven-source-plugin - 3.0.1 - - - attach-sources - - jar - - - - - - + ACF (Core) - - org.jetbrains - annotations - 13.0 - co.aikar minecraft-timings 1.0.3 + compile - org.bukkit - bukkit - 1.12-pre2-SNAPSHOT - provided + com.google.guava + guava + 17.0 + provided diff --git a/core/src/main/java/co/aikar/commands/ACFLog.java b/core/src/main/java/co/aikar/commands/ACFLog.java index 3c1ae1bf..3bdc9b95 100644 --- a/core/src/main/java/co/aikar/commands/ACFLog.java +++ b/core/src/main/java/co/aikar/commands/ACFLog.java @@ -23,14 +23,14 @@ package co.aikar.commands; -import org.apache.commons.lang.exception.ExceptionUtils; -import org.bukkit.Bukkit; + +import co.aikar.commands.apachecommonslang.ApacheCommonsExceptionUtil; import java.util.logging.Logger; @SuppressWarnings("WeakerAccess") final class ACFLog { - private static final Logger LOGGER = Bukkit.getLogger(); + private static final Logger LOGGER = Logger.getLogger("ACF"); public static final String PREFIX = "[ACF] "; private ACFLog() {} @@ -76,7 +76,7 @@ final class ACFLog { if (msg != null) { severe(msg); } - severe(ExceptionUtils.getFullStackTrace(e)); + severe(ApacheCommonsExceptionUtil.getFullStackTrace(e)); } public static void exception(Throwable dbg, int lines) { diff --git a/core/src/main/java/co/aikar/commands/ACFUtil.java b/core/src/main/java/co/aikar/commands/ACFUtil.java index 94f7c3cd..ae6ac37f 100644 --- a/core/src/main/java/co/aikar/commands/ACFUtil.java +++ b/core/src/main/java/co/aikar/commands/ACFUtil.java @@ -23,18 +23,8 @@ package co.aikar.commands; -import com.google.common.collect.Iterables; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.WordUtils; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; + +import co.aikar.commands.apachecommonslang.ApacheCommonsLangUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -44,10 +34,8 @@ import java.text.Normalizer.Form; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Collection; -import java.util.Iterator; import java.util.List; import java.util.Random; -import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -72,123 +60,6 @@ public final class ACFUtil { return NumberFormat.getInstance().format(balance); } - public static String formatLocation(Location loc) { - if (loc == null) { - return null; - } - return loc.getWorld().getName() + - ":" + - loc.getBlockX() + - "," + - loc.getBlockY() + - "," + - loc.getBlockZ(); - } - - public static String color(String message) { - return ChatColor.translateAlternateColorCodes('&', message); - } - - public static void sendMsg(CommandSender player, String message) { - message = color(message); - if (player == null) { - for (String msg : ACFPatterns.NEWLINE.split(message)) { - ACFLog.info(msg); - } - } else { - for (String msg : ACFPatterns.NEWLINE.split(message)) { - player.sendMessage(msg); - } - } - } - - public static Location stringToLocation(String storedLoc) { - return stringToLocation(storedLoc, null); - } - public static Location stringToLocation(String storedLoc, World forcedWorld) { - if (storedLoc == null) { - return null; - } - String[] args = ACFPatterns.COLON.split(storedLoc); - if (args.length >= 4 || (args.length == 3 && forcedWorld != null)) { - String world = forcedWorld != null ? forcedWorld.getName() : args[0]; - int i = args.length == 3 ? 0 : 1; - double x = Double.parseDouble(args[i]); - double y = Double.parseDouble(args[i + 1]); - double z = Double.parseDouble(args[i + 2]); - Location loc = new Location(Bukkit.getWorld(world), x, y, z); - if (args.length >= 6) { - loc.setPitch(Float.parseFloat(args[4])); - loc.setYaw(Float.parseFloat(args[5])); - } - return loc; - } else if (args.length == 2) { - String[] args2 = ACFPatterns.COMMA.split(args[1]); - if (args2.length == 3) { - String world = forcedWorld != null ? forcedWorld.getName() : args[0]; - double x = Double.parseDouble(args2[0]); - double y = Double.parseDouble(args2[1]); - double z = Double.parseDouble(args2[2]); - return new Location(Bukkit.getWorld(world), x, y, z); - } - } - return null; - } - - public static String fullLocationToString(Location loc) { - if (loc == null) { - return null; - } - return (new StringBuilder(64)) - .append(loc.getWorld().getName()) - .append(':') - .append(precision(loc.getX(), 4)) - .append(':') - .append(precision(loc.getY(), 4)) - .append(':') - .append(precision(loc.getZ(), 4)) - .append(':') - .append(precision(loc.getPitch(), 4)) - .append(':') - .append(precision(loc.getYaw(), 4)) - .toString(); - } - - public static String fullBlockLocationToString(Location loc) { - if (loc == null) { - return null; - } - return (new StringBuilder(64)) - .append(loc.getWorld().getName()) - .append(':') - .append(loc.getBlockX()) - .append(':') - .append(loc.getBlockY()) - .append(':') - .append(loc.getBlockZ()) - .append(':') - .append(precision(loc.getPitch(), 4)) - .append(':') - .append(precision(loc.getYaw(), 4)) - .toString(); - } - - public static String blockLocationToString(Location loc) { - if (loc == null) { - return null; - } - - return (new StringBuilder(32)) - .append(loc.getWorld().getName()) - .append(':') - .append(loc.getBlockX()) - .append(':') - .append(loc.getBlockY()) - .append(':') - .append(loc.getBlockZ()) - .toString(); - } - public static T getEnumFromName(T[] types, String name) { return getEnumFromName(types, name, null); } @@ -210,7 +81,7 @@ public final class ACFUtil { } public static String ucfirst(String str) { - return WordUtils.capitalizeFully(str); + return ApacheCommonsLangUtil.capitalizeFully(str); } public static Double parseDouble(String var) { @@ -275,17 +146,17 @@ public final class ACFUtil { } public static String join(Collection args) { - return StringUtils.join(args, " "); + return ApacheCommonsLangUtil.join(args, " "); } public static String join(Collection args, String sep) { - return StringUtils.join(args, sep); + return ApacheCommonsLangUtil.join(args, sep); } public static String join(String[] args) { return join(args, 0, ' '); } public static String join(String[] args, String sep) { - return StringUtils.join(args, sep); + return ApacheCommonsLangUtil.join(args, sep); } public static String join(String[] args, char sep) { return join(args, 0, sep); @@ -296,7 +167,7 @@ public final class ACFUtil { } public static String join(String[] args, int index, char sep) { - return StringUtils.join(args, sep, index, args.length); + return ApacheCommonsLangUtil.join(args, sep, index, args.length); } public static String simplifyString(String str) { @@ -333,36 +204,6 @@ public final class ACFUtil { } - public static String removeColors(String msg) { - return ChatColor.stripColor(ACFUtil.color(msg)); - - } - - public static String replaceChatString(String message, String replace, String with) { - return replaceChatString(message, Pattern.compile(Pattern.quote(replace), Pattern.CASE_INSENSITIVE), with); - } - public static String replaceChatString(String message, Pattern replace, String with) { - final String[] split = replace.split(message + "1"); - - if (split.length < 2) { - return replace.matcher(message).replaceAll(with); - } - message = split[0]; - - for (int i = 1; i < split.length; i++) { - final String prev = ChatColor.getLastColors(message); - message += with + prev + split[i]; - } - return message.substring(0, message.length() - 1); - } - - public static boolean isWithinDistance(@NotNull Player p1, @NotNull Player p2, int dist) { - return isWithinDistance(p1.getLocation(), p2.getLocation(), dist); - } - public static boolean isWithinDistance(@NotNull Location loc1, @NotNull Location loc2, int dist) { - return loc1.getWorld() == loc2.getWorld() && loc1.distance(loc2) <= dist; - } - public static String limit(String str, int limit) { return str.length() > limit ? str.substring(0, limit) : str; } @@ -457,48 +298,11 @@ public final class ACFUtil { return string; } - - /** - * Copied from Apache Commons WordUtils, with an exception to skip spaces after delimiters. - * - * @see org.apache.commons.lang.WordUtils#capitalize(String, char[]) - * @param str - * @param delimiters - * @return - */ public static String capitalize(String str, char[] delimiters) { - int delimLen = (delimiters == null ? -1 : delimiters.length); - if (str == null || str.isEmpty() || delimLen == 0) { - return str; - } - int strLen = str.length(); - StringBuilder builder = new StringBuilder(strLen); - boolean capitalizeNext = true; - for (int i = 0; i < strLen; i++) { - char ch = str.charAt(i); - - if (isDelimiter(ch, delimiters)) { - builder.append(ch); - capitalizeNext = true; - } else if (ch != ' ' && capitalizeNext) { - builder.append(Character.toTitleCase(ch)); - capitalizeNext = false; - } else { - builder.append(ch); - } - } - return builder.toString(); + return ApacheCommonsLangUtil.capitalize(str, delimiters); } private static boolean isDelimiter(char ch, char[] delimiters) { - if (delimiters == null) { - return Character.isWhitespace(ch); - } - for (char delimiter : delimiters) { - if (ch == delimiter) { - return true; - } - } - return false; + return ApacheCommonsLangUtil.isDelimiter(ch, delimiters); } public static T random(List arr) { @@ -584,53 +388,6 @@ public final class ACFUtil { return sb.toString(); } - public static double distance(@NotNull Entity e1, @NotNull Entity e2) { - return distance(e1.getLocation(), e2.getLocation()); - } - public static double distance2d(@NotNull Entity e1, @NotNull Entity e2) { - return distance2d(e1.getLocation(), e2.getLocation()); - } - public static double distance2d(@NotNull Location loc1, @NotNull Location loc2) { - loc1 = loc1.clone(); - loc1.setY(loc2.getY()); - return distance(loc1, loc2); - } - public static double distance(@NotNull Location loc1, @NotNull Location loc2) { - if (loc1.getWorld() != loc2.getWorld()) { - return 0; - } - return loc1.distance(loc2); - } - - public static Location getTargetLoc(Player player) { - return getTargetLoc(player, 128); - } - public static Location getTargetLoc(Player player, int maxDist) { - return getTargetLoc(player, maxDist, 1.5); - } - public static Location getTargetLoc(Player player, int maxDist, double addY) { - try { - Location target = player.getTargetBlock((Set) null, maxDist).getLocation(); - target.setY(target.getY() + addY); - return target; - } catch (Exception ignored) { - return null; - } - } - - public static Location getRandLoc(Location loc, int radius) { - return getRandLoc(loc, radius, radius, radius); - } - public static Location getRandLoc(Location loc, int xzRadius, int yRadius) { - return getRandLoc(loc, xzRadius, yRadius, xzRadius); - } - @NotNull public static Location getRandLoc(Location loc, int xRadius, int yRadius, int zRadius) { - Location newLoc = loc.clone(); - newLoc.setX(rand(loc.getX()-xRadius, loc.getX()+xRadius)); - newLoc.setY(rand(loc.getY()-yRadius, loc.getY()+yRadius)); - newLoc.setZ(rand(loc.getZ()-zRadius, loc.getZ()+zRadius)); - return newLoc; - } @Nullable public static > E simpleMatch(Class> list, String item) { if (item == null) { @@ -725,7 +482,7 @@ public final class ACFUtil { } public static boolean isNumber(String str) { - return StringUtils.isNumeric(str); + return ApacheCommonsLangUtil.isNumeric(str); } public static String intToRoman(int integer) { @@ -796,62 +553,6 @@ public final class ACFUtil { return Math.round(x * pow) / pow; } - public static Player findPlayerSmart(CommandSender requester, String origName) { - String name = replace(origName, ":confirm", ""); - if (name.length() < 3) { - requester.sendMessage("§cUsername too short, must be at least three characters"); - return null; - } - if (!isValidName(name)) { - requester.sendMessage("§c'" + name + "' is not a valid username"); - return null; - } - - List matches = Bukkit.getServer().matchPlayer(name); - List confirmList = new ArrayList<>(); - - // Remove confirmList players from smart matching. - Iterator iter = matches.iterator(); - while (iter.hasNext()) { - Player player = iter.next(); - if (requester instanceof Player && !((Player) requester).canSee(player)) { - if (requester.hasPermission("acf.seevanish")) { - if (!origName.endsWith(":confirm")) { - confirmList.add(player); - iter.remove(); - } - } else { - iter.remove(); - } - } - } - - if (matches.size() > 1 || confirmList.size() > 1) { - requester.sendMessage("§cMultiple players matched '" + name + "', please be more specific"); - return null; - } - - if (matches.isEmpty()) { - if (confirmList.isEmpty()) { - requester.sendMessage("§cNo player matching '" + name + "' is connected to this server"); - return null; - } else { - Player player = Iterables.getOnlyElement(confirmList); - sendMsg(requester, - "&cWarning: " + player.getDisplayName() + "&c is vanished. Do not blow their cover!\n" + - "&cTo confirm your action, add &f:confirm&c to the end of their name. \n" + - "&bEx: &e/g " + player.getName() + ":confirm"); - return null; - } - } - - return matches.get(0); - } - - public static boolean isValidName(String name) { - return name != null && !name.isEmpty() && ACFPatterns.VALID_NAME_PATTERN.matcher(name).matches(); - } - public static void sneaky(Throwable t) { //noinspection RedundantTypeArguments throw ACFUtil.superSneaky( t ); @@ -862,7 +563,4 @@ public final class ACFUtil { throw (T) t; } - static boolean isValidItem(ItemStack item) { - return item != null && item.getType() != Material.AIR && item.getAmount() > 0; - } } diff --git a/core/src/main/java/co/aikar/commands/BaseCommand.java b/core/src/main/java/co/aikar/commands/BaseCommand.java index 8bdea0a0..fac7d2b3 100644 --- a/core/src/main/java/co/aikar/commands/BaseCommand.java +++ b/core/src/main/java/co/aikar/commands/BaseCommand.java @@ -28,20 +28,15 @@ import co.aikar.commands.annotation.CommandPermission; import co.aikar.commands.annotation.Default; import co.aikar.commands.annotation.Subcommand; import co.aikar.commands.annotation.UnknownHandler; +import co.aikar.commands.apachecommonslang.ApacheCommonsLangUtil; import co.aikar.timings.lib.MCTiming; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.SetMultimap; -import org.apache.commons.lang.StringUtils; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.configuration.InvalidConfigurationException; -import org.bukkit.util.StringUtil; import java.lang.reflect.Constructor; -import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Parameter; @@ -59,7 +54,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; @SuppressWarnings("unused") -public class BaseCommand extends Command { +public abstract class BaseCommand { public static final String UNKNOWN = "__unknown"; public static final String DEFAULT = "__default"; @@ -73,14 +68,10 @@ public class BaseCommand extends Command { private String[] origArgs; CommandManager manager = null; Map registeredCommands = new HashMap<>(); - - public BaseCommand() { - this(null); - } - - public BaseCommand(String cmd) { - super(cmd); - } + String description; + String commandName; + String usageMessage; + String permission; /** * Gets the root command name that the user actually typed @@ -107,7 +98,7 @@ public class BaseCommand extends Command { } void onRegister(CommandManager manager) { - onRegister(manager, getName()); + onRegister(manager, null); } void onRegister(CommandManager manager, String cmd) { this.manager = manager; @@ -120,27 +111,15 @@ public class BaseCommand extends Command { cmd = ACFPatterns.PIPE.split(manager.getCommandReplacements().replace(rootCmdAlias.value()))[0]; } cmd = cmd.toLowerCase(); - try { - setName(cmd); - } catch (NoSuchMethodError ignored) { - try { - // To support pre 1.8 where setName was not added. - Field field = Command.class.getDeclaredField("name"); - field.setAccessible(true); - field.set(this, cmd); - } catch (NoSuchFieldException | IllegalAccessException e) { - ACFLog.exception("Error setting name for command", e); - } - } - setLabel(cmd); } + this.commandName = cmd; this.description = cmd + " commands"; this.usageMessage = "/" + cmd; final CommandPermission perm = self.getAnnotation(CommandPermission.class); if (perm != null) { - this.setPermission(manager.getCommandReplacements().replace(perm.value())); + this.permission = manager.getCommandReplacements().replace(perm.value()); } boolean foundDefault = false; @@ -158,7 +137,7 @@ public class BaseCommand extends Command { registerSubcommand(method, DEFAULT); foundDefault = true; } else { - ACFUtil.sneaky(new InvalidConfigurationException("Multiple @Default commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName())); + ACFUtil.sneaky(new IllegalStateException("Multiple @Default commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName())); } } if (sub != null) { @@ -171,14 +150,14 @@ public class BaseCommand extends Command { } - //CommandSender.class, String.class, String[].class + //CommandIssuer.class, String.class, String[].class UnknownHandler unknown = method.getAnnotation(UnknownHandler.class); if (unknown != null) { if (!foundUnknown) { registerSubcommand(method, UNKNOWN); foundUnknown = true; } else { - ACFUtil.sneaky(new InvalidConfigurationException("Multiple @UnknownHandler commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName())); + ACFUtil.sneaky(new IllegalStateException("Multiple @UnknownHandler commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName())); } } } @@ -247,6 +226,7 @@ public class BaseCommand extends Command { String nameLower = name.toLowerCase(); RootCommand rootCommand = manager.obtainRootCommand(nameLower); rootCommand.addChild(cmd); + this.registeredCommands.put(nameLower, rootCommand); } @@ -260,11 +240,11 @@ public class BaseCommand extends Command { for (int i = 0; i < subCommandParts.length; i++) { subCommandParts[i] = ACFPatterns.PIPE.split(subCommandParts[i])[0]; } - String prefSubCommand = StringUtils.join(subCommandParts, " "); + String prefSubCommand = ApacheCommonsLangUtil.join(subCommandParts, " "); final CommandAlias cmdAlias = method.getAnnotation(CommandAlias.class); final String[] aliasNames = cmdAlias != null ? ACFPatterns.PIPE.split(manager.getCommandReplacements().replace(cmdAlias.value().toLowerCase())) : null; - String cmdName = aliasNames != null ? aliasNames[0] : getLabel() + " "; + String cmdName = aliasNames != null ? aliasNames[0] : this.commandName + " "; RegisteredCommand cmd = new RegisteredCommand(this, cmdName, method, prefSubCommand); for (String subcmd : cmdList) { @@ -316,8 +296,7 @@ public class BaseCommand extends Command { } } - @Override - public boolean execute(CommandSender sender, String commandLabel, String[] args) { + public boolean execute(CommandIssuer sender, String commandLabel, String[] args) { commandLabel = commandLabel.toLowerCase(); execSubcommand = null; @@ -354,7 +333,7 @@ public class BaseCommand extends Command { } private CommandSearch findSubCommand(String[] args, boolean completion) { for (int i = args.length; i >= 0; i--) { - String checkSub = StringUtils.join(args, " ", 0, i).toLowerCase(); + String checkSub = ApacheCommonsLangUtil.join(args, " ", 0, i).toLowerCase(); Set cmds = subCommands.get(checkSub); final int extraArgs = args.length - i; @@ -388,30 +367,29 @@ public class BaseCommand extends Command { return null; } - private static void executeCommand(CommandSender sender, String[] args, RegisteredCommand cmd) { + private static void executeCommand(CommandIssuer sender, String[] args, RegisteredCommand cmd) { if (cmd.hasPermission(sender)) { List sargs = Lists.newArrayList(args); try (MCTiming timing = cmd.getTiming().startTiming()) { cmd.invoke(sender, sargs); } } else { - ACFUtil.sendMsg(sender, "&cI'm sorry, but you do not have permission to perform this command."); + sender.sendMessage("&cI'm sorry, but you do not have permission to perform this command."); } } - public boolean canExecute(CommandSender sender, RegisteredCommand cmd) { + public boolean canExecute(CommandIssuer sender, RegisteredCommand cmd) { return true; } - @Override - public List tabComplete(CommandSender sender, String commandLabel, String[] args) + public List tabComplete(CommandIssuer sender, String commandLabel, String[] args) throws IllegalArgumentException { commandLabel = commandLabel.toLowerCase(); final CommandSearch search = findSubCommand(args, true); - String argString = StringUtils.join(args, " ").toLowerCase(); + String argString = ApacheCommonsLangUtil.join(args, " ").toLowerCase(); final List cmds = new ArrayList<>(); @@ -436,12 +414,12 @@ public class BaseCommand extends Command { return filterTabComplete(args[args.length-1], cmds); } - private List completeCommand(CommandSender sender, RegisteredCommand cmd, String[] args, String commandLabel) { + private List completeCommand(CommandIssuer sender, RegisteredCommand cmd, String[] args, String commandLabel) { if (args.length > cmd.requiredResolvers + cmd.optionalResolvers) { return ImmutableList.of(); } if (args.length == 0 || cmd.complete == null) { - return args.length < 2 ? super.tabComplete(sender, commandLabel, args) : ImmutableList.of(); + return args.length < 2 ? null : ImmutableList.of(); } String[] completions = ACFPatterns.SPACE.split(cmd.complete); @@ -453,20 +431,20 @@ public class BaseCommand extends Command { private static List filterTabComplete(String arg, List cmds) { return cmds.stream() .distinct() - .filter(cmd -> cmd != null && (arg.isEmpty() || StringUtil.startsWithIgnoreCase(cmd, arg))) + .filter(cmd -> cmd != null && (arg.isEmpty() || ApacheCommonsLangUtil.startsWithIgnoreCase(cmd, arg))) .collect(Collectors.toList()); } - public void help(CommandSender sender, String[] args) { - ACFUtil.sendMsg(sender, "&cUnknown Command, please type &f/help"); + public void help(CommandIssuer sender, String[] args) { + sender.sendMessage("&cUnknown Command, please type &f/help"); } - private boolean executeSubcommand(String subcommand, CommandSender sender, String... args) { + private boolean executeSubcommand(String subcommand, CommandIssuer sender, String... args) { final Set defs = subCommands.get(subcommand); RegisteredCommand def = null; if (!defs.isEmpty()) { if (defs.size() == 1) { - def = Iterables.getOnlyElement(defs); + def = defs.iterator().next(); } if (def != null) { executeCommand(sender, args, def); @@ -476,16 +454,28 @@ public class BaseCommand extends Command { return false; } - public boolean preCommand(CommandSender sender, String commandLabel, String[] args) { + public boolean preCommand(CommandIssuer sender, String commandLabel, String[] args) { return false; } - public void doHelp(CommandSender sender, String... args) { + public void doHelp(CommandIssuer sender, String... args) { help(sender, args); } - public void showSyntax(CommandSender sender, RegisteredCommand cmd) { - ACFUtil.sendMsg(sender, "&cUsage: /" + cmd.command + " " + cmd.syntaxText); + public void showSyntax(CommandIssuer sender, RegisteredCommand cmd) { + sender.sendMessage("&cUsage: /" + cmd.command + " " + cmd.syntaxText); + } + + public boolean testPermission(CommandIssuer sender) { + if (permission != null && !permission.isEmpty() && !sender.hasPermission(permission)) { + // TODO: Msg + return false; + } + return true; + } + + public String getName() { + return commandName; } private static class CommandSearch { RegisteredCommand cmd; int argIndex; String checkSub; diff --git a/core/src/main/java/co/aikar/commands/BaseSubCommand.java b/core/src/main/java/co/aikar/commands/BaseSubCommand.java index 01f8ef72..c4824a6e 100644 --- a/core/src/main/java/co/aikar/commands/BaseSubCommand.java +++ b/core/src/main/java/co/aikar/commands/BaseSubCommand.java @@ -35,19 +35,4 @@ public class BaseSubCommand extends BaseCommand { public String getName() { return parentCommand.getName(); } - - @Override - public String getLabel() { - return parentCommand.getLabel(); - } - - @Override - public String getDescription() { - return parentCommand.getDescription(); - } - - @Override - public String getUsage() { - return parentCommand.getUsage(); - } } diff --git a/core/src/main/java/co/aikar/commands/CommandCompletions.java b/core/src/main/java/co/aikar/commands/CommandCompletions.java index ef40f011..e4b70bf5 100644 --- a/core/src/main/java/co/aikar/commands/CommandCompletions.java +++ b/core/src/main/java/co/aikar/commands/CommandCompletions.java @@ -27,7 +27,6 @@ import co.aikar.commands.annotation.Split; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; import java.lang.reflect.Parameter; @@ -70,7 +69,7 @@ public class CommandCompletions { } @NotNull - List of(RegisteredCommand command, CommandSender sender, String[] completionInfo, String[] args) { + List of(RegisteredCommand command, CommandIssuer sender, String[] completionInfo, String[] args) { final int argIndex = args.length - 1; String input = args[argIndex]; @@ -83,7 +82,7 @@ public class CommandCompletions { } @NotNull - List getCompletionValues(RegisteredCommand command, CommandSender sender, String completion, String[] args) { + List getCompletionValues(RegisteredCommand command, CommandIssuer sender, String completion, String[] args) { completion = manager.getCommandReplacements().replace(completion); final int argIndex = args.length - 1; @@ -123,18 +122,18 @@ public class CommandCompletions { } public interface CommandCompletionHandler { - Collection getCompletions(CommandSender sender, String config, String input, CommandCompletionContext context) throws InvalidCommandArgument; + Collection getCompletions(CommandIssuer sender, String config, String input, CommandCompletionContext context) throws InvalidCommandArgument; } public class CommandCompletionContext { private final RegisteredCommand command; - private final CommandSender sender; + private final CommandIssuer sender; private final String input; private final String config; private final Map configs = Maps.newHashMap(); private final List args; - CommandCompletionContext(RegisteredCommand command, CommandSender sender, String input, String config, String[] args) { + CommandCompletionContext(RegisteredCommand command, CommandIssuer sender, String input, String config, String[] args) { this.command = command; this.sender = sender; this.input = input; @@ -208,7 +207,7 @@ public class CommandCompletions { return (T) resolved.get(name); } - public CommandSender getSender() { + public CommandIssuer getSender() { return sender; } diff --git a/core/src/main/java/co/aikar/commands/CommandContexts.java b/core/src/main/java/co/aikar/commands/CommandContexts.java index d995d630..171d3dc8 100644 --- a/core/src/main/java/co/aikar/commands/CommandContexts.java +++ b/core/src/main/java/co/aikar/commands/CommandContexts.java @@ -29,14 +29,13 @@ import co.aikar.commands.annotation.Values; import co.aikar.commands.contexts.ContextResolver; import co.aikar.commands.contexts.SenderAwareContextResolver; import com.google.common.collect.Maps; -import org.bukkit.configuration.InvalidConfigurationException; import java.util.List; import java.util.Map; @SuppressWarnings("WeakerAccess") -public class CommandContexts { - private final Map, ContextResolver> contextMap = Maps.newHashMap(); +public class CommandContexts { + protected final Map, ContextResolver> contextMap = Maps.newHashMap(); private final CommandManager manager; CommandContexts(CommandManager manager) { @@ -105,8 +104,11 @@ public class CommandContexts { }); registerContext(String[].class, (c) -> { String val; + // Go home IDEA, you're drunk + //noinspection unchecked + List args = c.getArgs(); if (c.isLastArg() && c.getParam().getAnnotation(Single.class) == null) { - val = ACFUtil.join(c.getArgs()); + val = ACFUtil.join(args); } else { val = c.popFirstArg(); } @@ -117,16 +119,17 @@ public class CommandContexts { } return ACFPatterns.getPattern(split.value()).split(val); } else if (!c.isLastArg()) { - ACFUtil.sneaky(new InvalidConfigurationException("Weird Command signature... String[] should be last or @Split")); + ACFUtil.sneaky(new IllegalStateException("Weird Command signature... String[] should be last or @Split")); } - String[] result = c.getArgs().toArray(new String[c.getArgs().size()]); - c.getArgs().clear(); + String[] result = args.toArray(new String[args.size()]); + args.clear(); return result; }); registerContext(Enum.class, (c) -> { final String first = c.popFirstArg(); + //noinspection unchecked Class> enumCls = (Class>) c.getParam().getType(); Enum match = ACFUtil.simpleMatch(enumCls, first); if (match == null) { @@ -137,27 +140,27 @@ public class CommandContexts { }); } - public void registerSenderAwareContext(Class context, SenderAwareContextResolver supplier) { + void registerSenderAwareContext(Class context, SenderAwareContextResolver supplier) { contextMap.put(context, supplier); } - public void registerContext(Class context, ContextResolver supplier) { + void registerContext(Class context, ContextResolver supplier) { contextMap.put(context, supplier); } - public ContextResolver getResolver(Class type) { + public ContextResolver getResolver(Class type) { Class rootType = type; do { if (type == Object.class) { break; } - final ContextResolver resolver = contextMap.get(type); + final ContextResolver resolver = contextMap.get(type); if (resolver != null) { return resolver; } } while ((type = type.getSuperclass()) != null); - ACFLog.exception(new InvalidConfigurationException("No context resolver defined for " + rootType.getName())); + ACFLog.exception(new IllegalStateException("No context resolver defined for " + rootType.getName())); return null; } } diff --git a/core/src/main/java/co/aikar/commands/CommandExecutionContext.java b/core/src/main/java/co/aikar/commands/CommandExecutionContext.java index 5c3ca353..0aeb779f 100644 --- a/core/src/main/java/co/aikar/commands/CommandExecutionContext.java +++ b/core/src/main/java/co/aikar/commands/CommandExecutionContext.java @@ -30,28 +30,26 @@ import co.aikar.commands.contexts.ContextResolver; import co.aikar.commands.contexts.SenderAwareContextResolver; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; -import org.bukkit.command.CommandSender; - import java.lang.annotation.Annotation; import java.lang.reflect.Parameter; import java.util.List; import java.util.Map; @SuppressWarnings({"WeakerAccess", "unused"}) -public class CommandExecutionContext { +public class CommandExecutionContext { private final RegisteredCommand cmd; private final Parameter param; - private final CommandSender sender; + protected final CommandIssuer issuer; private final List args; private final int index; private final Map passedArgs; private final Map flags; - public CommandExecutionContext(RegisteredCommand cmd, Parameter param, CommandSender sender, List args, + CommandExecutionContext(RegisteredCommand cmd, Parameter param, CommandIssuer sender, List args, int index, Map passedArgs) { this.cmd = cmd; this.param = param; - this.sender = sender; + this.issuer = sender; this.args = args; this.index = index; this.passedArgs = passedArgs; @@ -87,7 +85,8 @@ public class CommandExecutionContext { int numRequired = getNumParams(); for (int i = 0; i < cmd.resolvers.length; i++) { Parameter parameter = cmd.parameters[i]; - ContextResolver resolver = cmd.resolvers[i]; + //noinspection unchecked + ContextResolver resolver = cmd.resolvers[i]; if (parameter.getAnnotation(Optional.class) != null || parameter.getAnnotation(Default.class) != null) { numRequired--; } else if (resolver instanceof SenderAwareContextResolver) { @@ -157,8 +156,8 @@ public class CommandExecutionContext { return this.param; } - public CommandSender getSender() { - return this.sender; + public CommandIssuer getIssuer() { + return this.issuer; } public List getArgs() { diff --git a/core/src/main/java/co/aikar/commands/ACF.java b/core/src/main/java/co/aikar/commands/CommandIssuer.java similarity index 68% rename from core/src/main/java/co/aikar/commands/ACF.java rename to core/src/main/java/co/aikar/commands/CommandIssuer.java index 6d9c9f0b..cc15d3d7 100644 --- a/core/src/main/java/co/aikar/commands/ACF.java +++ b/core/src/main/java/co/aikar/commands/CommandIssuer.java @@ -23,24 +23,30 @@ package co.aikar.commands; -import org.bukkit.plugin.Plugin; - -/** - * Aikar Command Framework - */ -public final class ACF { - private ACF() {} +public interface CommandIssuer { + /** + * Gets the issuer in the platforms native object + * @param + * @return + */ + T getIssuer(); /** - * Creates a manager for your current supported platform. - * @param plugin Bukkit Plugin - * @return Command Manager + * Is this issue a player, or server/console sender + * @return */ - public static CommandManager createManager(Plugin plugin) { - try { - Class.forName("com.destroystokyo.paper.PaperConfig"); - return new PaperCommandManager(plugin); - } catch (ClassNotFoundException ignored) {} - return new BukkitCommandManager(plugin); - } + boolean isPlayer(); + + /** + * Send the Command Issuer a message + * @param message + */ + void sendMessage(String message); + + /** + * Has permission node + * @param permission + * @return + */ + boolean hasPermission(String permission); } diff --git a/core/src/main/java/co/aikar/commands/CommandManager.java b/core/src/main/java/co/aikar/commands/CommandManager.java index 8273a9ea..64b524e8 100644 --- a/core/src/main/java/co/aikar/commands/CommandManager.java +++ b/core/src/main/java/co/aikar/commands/CommandManager.java @@ -25,7 +25,9 @@ package co.aikar.commands; import co.aikar.timings.lib.TimingManager; +import java.lang.reflect.Parameter; import java.util.HashMap; +import java.util.List; import java.util.Map; @SuppressWarnings("WeakerAccess") @@ -69,7 +71,10 @@ public abstract class CommandManager { public abstract TimingManager getTimings(); + public abstract RootCommand createRootCommand(String cmd); public synchronized RootCommand obtainRootCommand(String cmd) { - return rootCommands.computeIfAbsent(cmd.toLowerCase(), k -> new RootCommand(cmd)); + return rootCommands.computeIfAbsent(cmd.toLowerCase(), this::createRootCommand); } + + public abstract CommandExecutionContext createCommandContext(RegisteredCommand command, Parameter parameter, CommandIssuer sender, List args, int i, Map passedArgs); } diff --git a/core/src/main/java/co/aikar/commands/ForwardingCommand.java b/core/src/main/java/co/aikar/commands/ForwardingCommand.java index df84f825..5a380c65 100644 --- a/core/src/main/java/co/aikar/commands/ForwardingCommand.java +++ b/core/src/main/java/co/aikar/commands/ForwardingCommand.java @@ -23,30 +23,27 @@ package co.aikar.commands; -import org.apache.commons.lang.ArrayUtils; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; +import co.aikar.commands.apachecommonslang.ApacheCommonsLangUtil; import java.util.List; public class ForwardingCommand extends BaseCommand { private final BaseCommand command; private final String[] baseArgs; - private static final String[] NO_ARGS = new String[0]; ForwardingCommand(BaseCommand command, String[] baseArgs) { - super(command.getName()); + this.commandName = command.commandName; this.command = command; this.baseArgs = baseArgs; } @Override - public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { - return command.tabComplete(sender, alias, (String[]) ArrayUtils.addAll(baseArgs, args)); + public List tabComplete(CommandIssuer sender, String alias, String[] args) throws IllegalArgumentException { + return command.tabComplete(sender, alias, ApacheCommonsLangUtil.addAll(baseArgs, args)); } @Override - public boolean execute(CommandSender sender, String commandLabel, String[] args) { - return command.execute(sender, commandLabel, (String[]) ArrayUtils.addAll(baseArgs, args)); + public boolean execute(CommandIssuer sender, String commandLabel, String[] args) { + return command.execute(sender, commandLabel, ApacheCommonsLangUtil.addAll(baseArgs, args)); } } diff --git a/core/src/main/java/co/aikar/commands/RegisteredCommand.java b/core/src/main/java/co/aikar/commands/RegisteredCommand.java index 244a0408..febced29 100644 --- a/core/src/main/java/co/aikar/commands/RegisteredCommand.java +++ b/core/src/main/java/co/aikar/commands/RegisteredCommand.java @@ -36,8 +36,6 @@ import co.aikar.timings.lib.MCTiming; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; import java.lang.reflect.InvocationTargetException; @@ -55,7 +53,7 @@ public class RegisteredCommand { private final Method method; final String prefSubCommand; final Parameter[] parameters; - final ContextResolver[] resolvers; + final ContextResolver[] resolvers; final String syntaxText; private final String permission; @@ -90,11 +88,11 @@ public class RegisteredCommand { final Parameter parameter = parameters[i]; final Class type = parameter.getType(); - final ContextResolver resolver = commandContexts.getResolver(type); + final ContextResolver resolver = commandContexts.getResolver(type); if (resolver != null) { resolvers[i] = resolver; - if (!CommandSender.class.isAssignableFrom(parameter.getType())) { + if (!CommandIssuer.class.isAssignableFrom(parameter.getType())) { if (isOptionalResolver(resolver, parameter)) { optionalResolvers++; syntaxB.append('[').append(parameter.getName()).append("] "); @@ -118,13 +116,13 @@ public class RegisteredCommand { this.optionalResolvers = optionalResolvers; } - static boolean isOptionalResolver(ContextResolver resolver, Parameter parameter) { + static boolean isOptionalResolver(ContextResolver resolver, Parameter parameter) { return resolver instanceof SenderAwareContextResolver || parameter.getAnnotation(Optional.class) != null || parameter.getAnnotation(Default.class) != null; } - void invoke(CommandSender sender, List args) { + void invoke(CommandIssuer sender, List args) { if (!scope.canExecute(sender, this)) { return; } @@ -138,29 +136,29 @@ public class RegisteredCommand { } } - void handleException(CommandSender sender, List args, Exception e) { + void handleException(CommandIssuer sender, List args, Exception e) { if (e instanceof InvocationTargetException && e.getCause() instanceof InvalidCommandArgument) { e = (Exception) e.getCause(); } if (e instanceof InvalidCommandArgument) { if (e.getMessage() != null && !e.getMessage().isEmpty()) { - ACFUtil.sendMsg(sender, "&cError: " + e.getMessage()); + sender.sendMessage("&cError: " + e.getMessage()); } if (((InvalidCommandArgument) e).showSyntax) { scope.showSyntax(sender, this); } } else { - ACFUtil.sendMsg(sender, "&cI'm sorry, but there was an error performing this command."); + sender.sendMessage("&cI'm sorry, but there was an error performing this command."); ACFLog.exception("Exception in command: " + command + " " + ACFUtil.join(args), e); } } @Nullable - Map resolveContexts(CommandSender sender, List args) throws InvalidCommandArgument { + Map resolveContexts(CommandIssuer sender, List args) throws InvalidCommandArgument { return resolveContexts(sender, args, parameters.length); } @Nullable - Map resolveContexts(CommandSender sender, List args, int argLimit) throws InvalidCommandArgument { + Map resolveContexts(CommandIssuer sender, List args, int argLimit) throws InvalidCommandArgument { args = Lists.newArrayList(args); String[] origArgs = args.toArray(new String[args.size()]); Map passedArgs = Maps.newLinkedHashMap(); @@ -171,8 +169,8 @@ public class RegisteredCommand { final Parameter parameter = parameters[i]; final String parameterName = parameter.getName(); final Class type = parameter.getType(); - final ContextResolver resolver = resolvers[i]; - CommandExecutionContext context = new CommandExecutionContext(this, parameter, sender, args, i, passedArgs); + final ContextResolver resolver = resolvers[i]; + CommandExecutionContext context = this.scope.manager.createCommandContext(this, parameter, sender, args, i, passedArgs); if (!isOptionalResolver(resolver, parameter)) { remainingRequired--; } @@ -221,8 +219,8 @@ public class RegisteredCommand { return this.timing; } - boolean hasPermission(CommandSender check) { - return permission == null || !(check instanceof Player) || check.hasPermission(permission); + boolean hasPermission(CommandIssuer check) { + return permission == null || !check.isPlayer() || check.hasPermission(permission); } public String getPermission() { diff --git a/core/src/main/java/co/aikar/commands/RootCommand.java b/core/src/main/java/co/aikar/commands/RootCommand.java index 7a07c26e..9fdfe6d3 100644 --- a/core/src/main/java/co/aikar/commands/RootCommand.java +++ b/core/src/main/java/co/aikar/commands/RootCommand.java @@ -23,77 +23,7 @@ package co.aikar.commands; -import org.apache.commons.lang.StringUtils; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -public class RootCommand extends Command { - - private BaseCommand defCommand; - private Map subCommands = new HashMap<>(); - private List children = new ArrayList<>(); - boolean isRegistered = false; - - RootCommand(String name) { - super(name); - } - - @Override - public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { - Set completions = new HashSet<>(); - this.children.forEach(child -> completions.addAll(child.tabComplete(sender, alias, args))); - return new ArrayList<>(completions); - } - - @Override - public boolean execute(CommandSender sender, String commandLabel, String[] args) { - for (int i = args.length; i >= 0; i--) { - String checkSub = StringUtils.join(args, " ", 0, i).toLowerCase(); - BaseCommand subHandler = this.subCommands.get(checkSub); - if (subHandler != null) { - if (!subHandler.testPermission(sender)) { - return true; - } - subHandler.execute(sender, commandLabel, args); - return false; - } - } - if (!this.defCommand.testPermission(sender)) { - return true; - } - - this.defCommand.execute(sender, commandLabel, args); - return false; - } - - void addChild(BaseCommand command) { - if (this.defCommand == null || !command.subCommands.get("__default").isEmpty()) { - this.defCommand = command; - this.setDescription(command.getDescription()); - this.setUsage(command.getUsage()); - this.setAliases(command.getAliases()); - this.setPermission(command.getPermission()); - this.setPermissionMessage(command.getPermissionMessage()); - } - command.subCommands.keySet().forEach(key -> { - if (key.equals(BaseCommand.DEFAULT) || key.equals(BaseCommand.UNKNOWN)) { - return; - } - BaseCommand regged = this.subCommands.get(key); - if (regged != null) { - ACFLog.severe("ACF Error: " + command.getLabel() + " registered subcommand " + key + " for root command " + getName() + " - but it is already defined in " + regged.getLabel()); - ACFLog.severe("2 subcommands of the same prefix may not be spread over 2 different classes. Ignoring this."); - return; - } - this.subCommands.put(key, command); - }); - this.children.add(command); - } +interface RootCommand { + void addChild(BaseCommand command); + CommandManager getManager(); } diff --git a/core/src/main/java/co/aikar/commands/apachecommonslang/ApacheCommonsExceptionUtil.java b/core/src/main/java/co/aikar/commands/apachecommonslang/ApacheCommonsExceptionUtil.java new file mode 100644 index 00000000..2c18f61f --- /dev/null +++ b/core/src/main/java/co/aikar/commands/apachecommonslang/ApacheCommonsExceptionUtil.java @@ -0,0 +1,1014 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package co.aikar.commands.apachecommonslang; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.StringTokenizer; + +/** + *

Provides utilities for manipulating and examining + * Throwable objects.

+ * + * @author Daniel Rall + * @author Dmitri Plotnikov + * @author Stephen Colebourne + * @author Gary Gregory + * @author Pete Gieser + * @since 1.0 + * @version $Id$ + */ +public class ApacheCommonsExceptionUtil { + private static final String LINE_SEPARATOR = System.getProperty("line.separator"); + + /** + *

Used when printing stack frames to denote the start of a + * wrapped exception.

+ * + *

Package private for accessibility by test suite.

+ */ + static final String WRAPPED_MARKER = " [wrapped] "; + + /** + *

The names of methods commonly used to access a wrapped exception.

+ */ + private static String[] CAUSE_METHOD_NAMES = { + "getCause", + "getNextException", + "getTargetException", + "getException", + "getSourceException", + "getRootCause", + "getCausedByException", + "getNested", + "getLinkedException", + "getNestedException", + "getLinkedCause", + "getThrowable", + }; + + /** + *

The Method object for Java 1.4 getCause.

+ */ + private static final Method THROWABLE_CAUSE_METHOD; + + /** + *

The Method object for Java 1.4 initCause.

+ */ + private static final Method THROWABLE_INITCAUSE_METHOD; + + static { + Method causeMethod; + try { + causeMethod = Throwable.class.getMethod("getCause", null); + } catch (Exception e) { + causeMethod = null; + } + THROWABLE_CAUSE_METHOD = causeMethod; + try { + causeMethod = Throwable.class.getMethod("initCause", new Class[]{Throwable.class}); + } catch (Exception e) { + causeMethod = null; + } + THROWABLE_INITCAUSE_METHOD = causeMethod; + } + + /** + *

+ * Public constructor allows an instance of ExceptionUtils to be created, although that is not + * normally necessary. + *

+ */ + public ApacheCommonsExceptionUtil() { + super(); + } + + //----------------------------------------------------------------------- + /** + *

Adds to the list of method names used in the search for Throwable + * objects.

+ * + * @param methodName the methodName to add to the list, null + * and empty strings are ignored + * @since 2.0 + */ + public static void addCauseMethodName(String methodName) { + if (methodName != null && !methodName.isEmpty() && !isCauseMethodName(methodName)) { + List list = getCauseMethodNameList(); + if (list.add(methodName)) { + CAUSE_METHOD_NAMES = toArray(list); + } + } + } + + /** + *

Removes from the list of method names used in the search for Throwable + * objects.

+ * + * @param methodName the methodName to remove from the list, null + * and empty strings are ignored + * @since 2.1 + */ + public static void removeCauseMethodName(String methodName) { + if (methodName != null && !methodName.isEmpty()) { + List list = getCauseMethodNameList(); + if (list.remove(methodName)) { + CAUSE_METHOD_NAMES = toArray(list); + } + } + } + + /** + *

Sets the cause of a Throwable using introspection, allowing + * source code compatibility between pre-1.4 and post-1.4 Java releases.

+ * + *

The typical use of this method is inside a constructor as in + * the following example:

+ * + *
+     * import org.apache.commons.lang.exception.ExceptionUtils;
+     *
+     * public class MyException extends Exception {
+     *
+     *    public MyException(String msg) {
+     *       super(msg);
+     *    }
+     *
+     *    public MyException(String msg, Throwable cause) {
+     *       super(msg);
+     *       ExceptionUtils.setCause(this, cause);
+     *    }
+     * }
+     * 
+ * + * @param target the target Throwable + * @param cause the Throwable to set in the target + * @return a true if the target has been modified + * @since 2.2 + */ + public static boolean setCause(Throwable target, Throwable cause) { + if (target == null) { + throw new IllegalArgumentException("target"); + } + Object[] causeArgs = new Object[]{cause}; + boolean modifiedTarget = false; + if (THROWABLE_INITCAUSE_METHOD != null) { + try { + THROWABLE_INITCAUSE_METHOD.invoke(target, causeArgs); + modifiedTarget = true; + } catch (IllegalAccessException ignored) { + // Exception ignored. + } catch (InvocationTargetException ignored) { + // Exception ignored. + } + } + try { + Method setCauseMethod = target.getClass().getMethod("setCause", new Class[]{Throwable.class}); + setCauseMethod.invoke(target, causeArgs); + modifiedTarget = true; + } catch (NoSuchMethodException ignored) { + // Exception ignored. + } catch (IllegalAccessException ignored) { + // Exception ignored. + } catch (InvocationTargetException ignored) { + // Exception ignored. + } + return modifiedTarget; + } + + /** + * Returns the given list as a String[]. + * @param list a list to transform. + * @return the given list as a String[]. + */ + private static String[] toArray(List list) { + return (String[]) list.toArray(new String[list.size()]); + } + + /** + * Returns {@link #CAUSE_METHOD_NAMES} as a List. + * + * @return {@link #CAUSE_METHOD_NAMES} as a List. + */ + private static ArrayList getCauseMethodNameList() { + return new ArrayList(Arrays.asList(CAUSE_METHOD_NAMES)); + } + + /** + *

Tests if the list of method names used in the search for Throwable + * objects include the given name.

+ * + * @param methodName the methodName to search in the list. + * @return if the list of method names used in the search for Throwable + * objects include the given name. + * @since 2.1 + */ + public static boolean isCauseMethodName(String methodName) { + return ApacheCommonsLangUtil.indexOf(CAUSE_METHOD_NAMES, methodName) >= 0; + } + + //----------------------------------------------------------------------- + /** + *

Introspects the Throwable to obtain the cause.

+ * + *

The method searches for methods with specific names that return a + * Throwable object. This will pick up most wrapping exceptions, + * including those from JDK 1.4, and + * The method names can be added to using {@link #addCauseMethodName(String)}.

+ * + *

The default list searched for are:

+ *
    + *
  • getCause()
  • + *
  • getNextException()
  • + *
  • getTargetException()
  • + *
  • getException()
  • + *
  • getSourceException()
  • + *
  • getRootCause()
  • + *
  • getCausedByException()
  • + *
  • getNested()
  • + *
+ * + *

In the absence of any such method, the object is inspected for a + * detail field assignable to a Throwable.

+ * + *

If none of the above is found, returns null.

+ * + * @param throwable the throwable to introspect for a cause, may be null + * @return the cause of the Throwable, + * null if none found or null throwable input + * @since 1.0 + */ + public static Throwable getCause(Throwable throwable) { + return getCause(throwable, CAUSE_METHOD_NAMES); + } + + /** + *

Introspects the Throwable to obtain the cause.

+ * + *
    + *
  1. Try known exception types.
  2. + *
  3. Try the supplied array of method names.
  4. + *
  5. Try the field 'detail'.
  6. + *
+ * + *

A null set of method names means use the default set. + * A null in the set of method names will be ignored.

+ * + * @param throwable the throwable to introspect for a cause, may be null + * @param methodNames the method names, null treated as default set + * @return the cause of the Throwable, + * null if none found or null throwable input + * @since 1.0 + */ + public static Throwable getCause(Throwable throwable, String[] methodNames) { + if (throwable == null) { + return null; + } + Throwable cause = getCauseUsingWellKnownTypes(throwable); + if (cause == null) { + if (methodNames == null) { + methodNames = CAUSE_METHOD_NAMES; + } + for (int i = 0; i < methodNames.length; i++) { + String methodName = methodNames[i]; + if (methodName != null) { + cause = getCauseUsingMethodName(throwable, methodName); + if (cause != null) { + break; + } + } + } + + if (cause == null) { + cause = getCauseUsingFieldName(throwable, "detail"); + } + } + return cause; + } + + /** + *

Introspects the Throwable to obtain the root cause.

+ * + *

This method walks through the exception chain to the last element, + * "root" of the tree, using {@link #getCause(Throwable)}, and + * returns that exception.

+ * + *

From version 2.2, this method handles recursive cause structures + * that might otherwise cause infinite loops. If the throwable parameter + * has a cause of itself, then null will be returned. If the throwable + * parameter cause chain loops, the last element in the chain before the + * loop is returned.

+ * + * @param throwable the throwable to get the root cause for, may be null + * @return the root cause of the Throwable, + * null if none found or null throwable input + */ + public static Throwable getRootCause(Throwable throwable) { + List list = getThrowableList(throwable); + return (list.size() < 2 ? null : (Throwable)list.get(list.size() - 1)); + } + + /** + *

Finds a Throwable for known types.

+ * + *

Uses instanceof checks to examine the exception, + * looking for well known types which could contain chained or + * wrapped exceptions.

+ * + * @param throwable the exception to examine + * @return the wrapped exception, or null if not found + */ + private static Throwable getCauseUsingWellKnownTypes(Throwable throwable) { + if (throwable instanceof Nestable) { + return ((Nestable) throwable).getCause(); + } else if (throwable instanceof SQLException) { + return ((SQLException) throwable).getNextException(); + } else if (throwable instanceof InvocationTargetException) { + return ((InvocationTargetException) throwable).getTargetException(); + } else { + return null; + } + } + + /** + *

Finds a Throwable by method name.

+ * + * @param throwable the exception to examine + * @param methodName the name of the method to find and invoke + * @return the wrapped exception, or null if not found + */ + private static Throwable getCauseUsingMethodName(Throwable throwable, String methodName) { + Method method = null; + try { + method = throwable.getClass().getMethod(methodName, null); + } catch (NoSuchMethodException ignored) { + // exception ignored + } catch (SecurityException ignored) { + // exception ignored + } + + if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) { + try { + return (Throwable) method.invoke(throwable); + } catch (IllegalAccessException ignored) { + // exception ignored + } catch (IllegalArgumentException ignored) { + // exception ignored + } catch (InvocationTargetException ignored) { + // exception ignored + } + } + return null; + } + + /** + *

Finds a Throwable by field name.

+ * + * @param throwable the exception to examine + * @param fieldName the name of the attribute to examine + * @return the wrapped exception, or null if not found + */ + private static Throwable getCauseUsingFieldName(Throwable throwable, String fieldName) { + Field field = null; + try { + field = throwable.getClass().getField(fieldName); + } catch (NoSuchFieldException ignored) { + // exception ignored + } catch (SecurityException ignored) { + // exception ignored + } + + if (field != null && Throwable.class.isAssignableFrom(field.getType())) { + try { + return (Throwable) field.get(throwable); + } catch (IllegalAccessException ignored) { + // exception ignored + } catch (IllegalArgumentException ignored) { + // exception ignored + } + } + return null; + } + + //----------------------------------------------------------------------- + /** + *

Checks if the Throwable class has a getCause method.

+ * + *

This is true for JDK 1.4 and above.

+ * + * @return true if Throwable is nestable + * @since 2.0 + */ + public static boolean isThrowableNested() { + return THROWABLE_CAUSE_METHOD != null; + } + + /** + *

Checks whether this Throwable class can store a cause.

+ * + *

This method does not check whether it actually does store a cause.

+ * + * @param throwable the Throwable to examine, may be null + * @return boolean true if nested otherwise false + * @since 2.0 + */ + public static boolean isNestedThrowable(Throwable throwable) { + if (throwable == null) { + return false; + } + + if (throwable instanceof Nestable) { + return true; + } else if (throwable instanceof SQLException) { + return true; + } else if (throwable instanceof InvocationTargetException) { + return true; + } else if (isThrowableNested()) { + return true; + } + + Class cls = throwable.getClass(); + for (int i = 0, isize = CAUSE_METHOD_NAMES.length; i < isize; i++) { + try { + Method method = cls.getMethod(CAUSE_METHOD_NAMES[i], null); + if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) { + return true; + } + } catch (NoSuchMethodException ignored) { + // exception ignored + } catch (SecurityException ignored) { + // exception ignored + } + } + + try { + Field field = cls.getField("detail"); + if (field != null) { + return true; + } + } catch (NoSuchFieldException ignored) { + // exception ignored + } catch (SecurityException ignored) { + // exception ignored + } + + return false; + } + + //----------------------------------------------------------------------- + /** + *

Counts the number of Throwable objects in the + * exception chain.

+ * + *

A throwable without cause will return 1. + * A throwable with one cause will return 2 and so on. + * A null throwable will return 0.

+ * + *

From version 2.2, this method handles recursive cause structures + * that might otherwise cause infinite loops. The cause chain is + * processed until the end is reached, or until the next item in the + * chain is already in the result set.

+ * + * @param throwable the throwable to inspect, may be null + * @return the count of throwables, zero if null input + */ + public static int getThrowableCount(Throwable throwable) { + return getThrowableList(throwable).size(); + } + + /** + *

Returns the list of Throwable objects in the + * exception chain.

+ * + *

A throwable without cause will return an array containing + * one element - the input throwable. + * A throwable with one cause will return an array containing + * two elements. - the input throwable and the cause throwable. + * A null throwable will return an array of size zero.

+ * + *

From version 2.2, this method handles recursive cause structures + * that might otherwise cause infinite loops. The cause chain is + * processed until the end is reached, or until the next item in the + * chain is already in the result set.

+ * + * @see #getThrowableList(Throwable) + * @param throwable the throwable to inspect, may be null + * @return the array of throwables, never null + */ + public static Throwable[] getThrowables(Throwable throwable) { + List list = getThrowableList(throwable); + return (Throwable[]) list.toArray(new Throwable[list.size()]); + } + + /** + *

Returns the list of Throwable objects in the + * exception chain.

+ * + *

A throwable without cause will return a list containing + * one element - the input throwable. + * A throwable with one cause will return a list containing + * two elements. - the input throwable and the cause throwable. + * A null throwable will return a list of size zero.

+ * + *

This method handles recursive cause structures that might + * otherwise cause infinite loops. The cause chain is processed until + * the end is reached, or until the next item in the chain is already + * in the result set.

+ * + * @param throwable the throwable to inspect, may be null + * @return the list of throwables, never null + * @since Commons Lang 2.2 + */ + public static List getThrowableList(Throwable throwable) { + List list = new ArrayList(); + while (throwable != null && list.contains(throwable) == false) { + list.add(throwable); + throwable = getCause(throwable); + } + return list; + } + + //----------------------------------------------------------------------- + /** + *

Returns the (zero based) index of the first Throwable + * that matches the specified class (exactly) in the exception chain. + * Subclasses of the specified class do not match - see + * {@link #indexOfType(Throwable, Class)} for the opposite.

+ * + *

A null throwable returns -1. + * A null type returns -1. + * No match in the chain returns -1.

+ * + * @param throwable the throwable to inspect, may be null + * @param clazz the class to search for, subclasses do not match, null returns -1 + * @return the index into the throwable chain, -1 if no match or null input + */ + public static int indexOfThrowable(Throwable throwable, Class clazz) { + return indexOf(throwable, clazz, 0, false); + } + + /** + *

Returns the (zero based) index of the first Throwable + * that matches the specified type in the exception chain from + * a specified index. + * Subclasses of the specified class do not match - see + * {@link #indexOfType(Throwable, Class, int)} for the opposite.

+ * + *

A null throwable returns -1. + * A null type returns -1. + * No match in the chain returns -1. + * A negative start index is treated as zero. + * A start index greater than the number of throwables returns -1.

+ * + * @param throwable the throwable to inspect, may be null + * @param clazz the class to search for, subclasses do not match, null returns -1 + * @param fromIndex the (zero based) index of the starting position, + * negative treated as zero, larger than chain size returns -1 + * @return the index into the throwable chain, -1 if no match or null input + */ + public static int indexOfThrowable(Throwable throwable, Class clazz, int fromIndex) { + return indexOf(throwable, clazz, fromIndex, false); + } + + //----------------------------------------------------------------------- + /** + *

Returns the (zero based) index of the first Throwable + * that matches the specified class or subclass in the exception chain. + * Subclasses of the specified class do match - see + * {@link #indexOfThrowable(Throwable, Class)} for the opposite.

+ * + *

A null throwable returns -1. + * A null type returns -1. + * No match in the chain returns -1.

+ * + * @param throwable the throwable to inspect, may be null + * @param type the type to search for, subclasses match, null returns -1 + * @return the index into the throwable chain, -1 if no match or null input + * @since 2.1 + */ + public static int indexOfType(Throwable throwable, Class type) { + return indexOf(throwable, type, 0, true); + } + + /** + *

Returns the (zero based) index of the first Throwable + * that matches the specified type in the exception chain from + * a specified index. + * Subclasses of the specified class do match - see + * {@link #indexOfThrowable(Throwable, Class)} for the opposite.

+ * + *

A null throwable returns -1. + * A null type returns -1. + * No match in the chain returns -1. + * A negative start index is treated as zero. + * A start index greater than the number of throwables returns -1.

+ * + * @param throwable the throwable to inspect, may be null + * @param type the type to search for, subclasses match, null returns -1 + * @param fromIndex the (zero based) index of the starting position, + * negative treated as zero, larger than chain size returns -1 + * @return the index into the throwable chain, -1 if no match or null input + * @since 2.1 + */ + public static int indexOfType(Throwable throwable, Class type, int fromIndex) { + return indexOf(throwable, type, fromIndex, true); + } + + /** + *

Worker method for the indexOfType methods.

+ * + * @param throwable the throwable to inspect, may be null + * @param type the type to search for, subclasses match, null returns -1 + * @param fromIndex the (zero based) index of the starting position, + * negative treated as zero, larger than chain size returns -1 + * @param subclass if true, compares with {@link Class#isAssignableFrom(Class)}, otherwise compares + * using references + * @return index of the type within throwables nested withing the specified throwable + */ + private static int indexOf(Throwable throwable, Class type, int fromIndex, boolean subclass) { + if (throwable == null || type == null) { + return -1; + } + if (fromIndex < 0) { + fromIndex = 0; + } + Throwable[] throwables = getThrowables(throwable); + if (fromIndex >= throwables.length) { + return -1; + } + if (subclass) { + for (int i = fromIndex; i < throwables.length; i++) { + if (type.isAssignableFrom(throwables[i].getClass())) { + return i; + } + } + } else { + for (int i = fromIndex; i < throwables.length; i++) { + if (type.equals(throwables[i].getClass())) { + return i; + } + } + } + return -1; + } + + /** + *

Removes common frames from the cause trace given the two stack traces.

+ * + * @param causeFrames stack trace of a cause throwable + * @param wrapperFrames stack trace of a wrapper throwable + * @throws IllegalArgumentException if either argument is null + * @since 2.0 + */ + public static void removeCommonFrames(List causeFrames, List wrapperFrames) { + if (causeFrames == null || wrapperFrames == null) { + throw new IllegalArgumentException("The List must not be null"); + } + int causeFrameIndex = causeFrames.size() - 1; + int wrapperFrameIndex = wrapperFrames.size() - 1; + while (causeFrameIndex >= 0 && wrapperFrameIndex >= 0) { + // Remove the frame from the cause trace if it is the same + // as in the wrapper trace + String causeFrame = (String) causeFrames.get(causeFrameIndex); + String wrapperFrame = (String) wrapperFrames.get(wrapperFrameIndex); + if (causeFrame.equals(wrapperFrame)) { + causeFrames.remove(causeFrameIndex); + } + causeFrameIndex--; + wrapperFrameIndex--; + } + } + + //----------------------------------------------------------------------- + /** + *

A way to get the entire nested stack-trace of an throwable.

+ * + *

The result of this method is highly dependent on the JDK version + * and whether the exceptions override printStackTrace or not.

+ * + * @param throwable the Throwable to be examined + * @return the nested stack trace, with the root cause first + * @since 2.0 + */ + public static String getFullStackTrace(Throwable throwable) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw, true); + Throwable[] ts = getThrowables(throwable); + for (int i = 0; i < ts.length; i++) { + ts[i].printStackTrace(pw); + if (isNestedThrowable(ts[i])) { + break; + } + } + return sw.getBuffer().toString(); + } + + //----------------------------------------------------------------------- + /** + *

Gets the stack trace from a Throwable as a String.

+ * + *

The result of this method vary by JDK version as this method + * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}. + * On JDK1.3 and earlier, the cause exception will not be shown + * unless the specified throwable alters printStackTrace.

+ * + * @param throwable the Throwable to be examined + * @return the stack trace as generated by the exception's + * printStackTrace(PrintWriter) method + */ + public static String getStackTrace(Throwable throwable) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw, true); + throwable.printStackTrace(pw); + return sw.getBuffer().toString(); + } + + /** + *

Captures the stack trace associated with the specified + * Throwable object, decomposing it into a list of + * stack frames.

+ * + *

The result of this method vary by JDK version as this method + * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}. + * On JDK1.3 and earlier, the cause exception will not be shown + * unless the specified throwable alters printStackTrace.

+ * + * @param throwable the Throwable to examine, may be null + * @return an array of strings describing each stack frame, never null + */ +// public static String[] getStackFrames(Throwable throwable) { +// if (throwable == null) { +// return ArrayUtils.EMPTY_STRING_ARRAY; +// } +// return getStackFrames(getStackTrace(throwable)); +// } + + //----------------------------------------------------------------------- + /** + *

Returns an array where each element is a line from the argument.

+ * + *

The end of line is determined by the value of {@link SystemUtils#LINE_SEPARATOR}.

+ * + *

Functionality shared between the + * getStackFrames(Throwable) methods of this and the + * {@link org.apache.commons.lang.exception.NestableDelegate} classes.

+ * + * @param stackTrace a stack trace String + * @return an array where each element is a line from the argument + */ +// static String[] getStackFrames(String stackTrace) { +// String linebreak = SystemUtils.LINE_SEPARATOR; +// StringTokenizer frames = new StringTokenizer(stackTrace, linebreak); +// List list = new ArrayList(); +// while (frames.hasMoreTokens()) { +// list.add(frames.nextToken()); +// } +// return toArray(list); +// } + + /** + *

Produces a List of stack frames - the message + * is not included. Only the trace of the specified exception is + * returned, any caused by trace is stripped.

+ * + *

This works in most cases - it will only fail if the exception + * message contains a line that starts with: + * "   at".

+ * + * @param t is any throwable + * @return List of stack frames + */ + static List getStackFrameList(Throwable t) { + String stackTrace = getStackTrace(t); + String linebreak = LINE_SEPARATOR; + StringTokenizer frames = new StringTokenizer(stackTrace, linebreak); + List list = new ArrayList(); + boolean traceStarted = false; + while (frames.hasMoreTokens()) { + String token = frames.nextToken(); + // Determine if the line starts with at + int at = token.indexOf("at"); + if (at != -1 && token.substring(0, at).trim().length() == 0) { + traceStarted = true; + list.add(token); + } else if (traceStarted) { + break; + } + } + return list; + } + + //----------------------------------------------------------------------- + /** + * Gets a short message summarising the exception. + *

+ * The message returned is of the form + * {ClassNameWithoutPackage}: {ThrowableMessage} + * + * @param th the throwable to get a message for, null returns empty string + * @return the message, non-null + * @since Commons Lang 2.2 + */ +// public static String getMessage(Throwable th) { +// if (th == null) { +// return ""; +// } +// String clsName = ClassUtils.getShortClassName(th, null); +// String msg = th.getMessage(); +// return clsName + ": " + StringUtils.defaultString(msg); +// } + + //----------------------------------------------------------------------- + /** + * Gets a short message summarising the root cause exception. + *

+ * The message returned is of the form + * {ClassNameWithoutPackage}: {ThrowableMessage} + * + * @param th the throwable to get a message for, null returns empty string + * @return the message, non-null + * @since Commons Lang 2.2 + */ +// public static String getRootCauseMessage(Throwable th) { +// Throwable root = ExceptionUtils.getRootCause(th); +// root = (root == null ? th : root); +// return getMessage(root); +// } + + /** + * An interface to be implemented by {@link java.lang.Throwable} + * extensions which would like to be able to nest root exceptions + * inside themselves. + * + * @author Daniel Rall + * @author Kasper Nielsen + * @author Steven Caswell + * @author Pete Gieser + * @since 1.0 + * @version $Id$ + */ + public interface Nestable { + + /** + * Returns the reference to the exception or error that caused the + * exception implementing the Nestable to be thrown. + * + * @return throwable that caused the original exception + */ + public Throwable getCause(); + + /** + * Returns the error message of this and any nested + * Throwable. + * + * @return the error message + */ + public String getMessage(); + + /** + * Returns the error message of the Throwable in the chain + * of Throwables at the specified index, numbered from 0. + * + * @param index the index of the Throwable in the chain of + * Throwables + * @return the error message, or null if the Throwable at the + * specified index in the chain does not contain a message + * @throws IndexOutOfBoundsException if the index argument is + * negative or not less than the count of Throwables in the + * chain + */ + public String getMessage(int index); + + /** + * Returns the error message of this and any nested Throwables + * in an array of Strings, one element for each message. Any + * Throwable not containing a message is represented in the + * array by a null. This has the effect of cause the length of the returned + * array to be equal to the result of the {@link #getThrowableCount()} + * operation. + * + * @return the error messages + */ + public String[] getMessages(); + + /** + * Returns the Throwable in the chain of + * Throwables at the specified index, numbered from 0. + * + * @param index the index, numbered from 0, of the Throwable in + * the chain of Throwables + * @return the Throwable + * @throws IndexOutOfBoundsException if the index argument is + * negative or not less than the count of Throwables in the + * chain + */ + public Throwable getThrowable(int index); + + /** + * Returns the number of nested Throwables represented by + * this Nestable, including this Nestable. + * + * @return the throwable count + */ + public int getThrowableCount(); + + /** + * Returns this Nestable and any nested Throwables + * in an array of Throwables, one element for each + * Throwable. + * + * @return the Throwables + */ + public Throwable[] getThrowables(); + + /** + * Returns the index, numbered from 0, of the first occurrence of the + * specified type, or a subclass, in the chain of Throwables. + * The method returns -1 if the specified type is not found in the chain. + *

+ * NOTE: From v2.1, we have clarified the Nestable interface + * such that this method matches subclasses. + * If you want to NOT match subclasses, please use + * (which is avaiable in all versions of lang). + * + * @param type the type to find, subclasses match, null returns -1 + * @return index of the first occurrence of the type in the chain, or -1 if + * the type is not found + */ + public int indexOfThrowable(Class type); + + /** + * Returns the index, numbered from 0, of the first Throwable + * that matches the specified type, or a subclass, in the chain of Throwables + * with an index greater than or equal to the specified index. + * The method returns -1 if the specified type is not found in the chain. + *

+ * NOTE: From v2.1, we have clarified the Nestable interface + * such that this method matches subclasses. + * If you want to NOT match subclasses, please use + * (which is avaiable in all versions of lang). + * + * @param type the type to find, subclasses match, null returns -1 + * @param fromIndex the index, numbered from 0, of the starting position in + * the chain to be searched + * @return index of the first occurrence of the type in the chain, or -1 if + * the type is not found + * @throws IndexOutOfBoundsException if the fromIndex argument + * is negative or not less than the count of Throwables in the + * chain + */ + public int indexOfThrowable(Class type, int fromIndex); + + /** + * Prints the stack trace of this exception to the specified print + * writer. Includes information from the exception, if any, + * which caused this exception. + * + * @param out PrintWriter to use for output. + */ + public void printStackTrace(PrintWriter out); + + /** + * Prints the stack trace of this exception to the specified print + * stream. Includes information from the exception, if any, + * which caused this exception. + * + * @param out PrintStream to use for output. + */ + public void printStackTrace(PrintStream out); + + /** + * Prints the stack trace for this exception only--root cause not + * included--using the provided writer. Used by + * individual stack traces to a buffer. The implementation of + * this method should call + * super.printStackTrace(out); in most cases. + * + * @param out The writer to use. + */ + public void printPartialStackTrace(PrintWriter out); + + } +} diff --git a/core/src/main/java/co/aikar/commands/apachecommonslang/ApacheCommonsLangUtil.java b/core/src/main/java/co/aikar/commands/apachecommonslang/ApacheCommonsLangUtil.java new file mode 100644 index 00000000..1a956938 --- /dev/null +++ b/core/src/main/java/co/aikar/commands/apachecommonslang/ApacheCommonsLangUtil.java @@ -0,0 +1,1399 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package co.aikar.commands.apachecommonslang; + +import java.lang.reflect.Array; +import java.util.Iterator; + +/** + * Select methods copied from Apache Commons to avoid importing entire lib + * No changes to logic + */ +public class ApacheCommonsLangUtil { + + /** + * The empty String {@code ""}. + * @since 2.0 + */ + public static final String EMPTY = ""; + /** + *

Shallow clones an array returning a typecast result and handling + * {@code null}. + * + *

The objects in the array are not cloned, thus there is no special + * handling for multi-dimensional arrays. + * + *

This method returns {@code null} for a {@code null} input array. + * + * @param the component type of the array + * @param array the array to shallow clone, may be {@code null} + * @return the cloned array, {@code null} if {@code null} input + */ + public static T[] clone(final T[] array) { + if (array == null) { + return null; + } + return array.clone(); + } + + /** + *

Adds all the elements of the given arrays into a new array. + *

The new array contains all of the element of {@code array1} followed + * by all of the elements {@code array2}. When an array is returned, it is always + * a new array. + * + *

+     * ArrayUtils.addAll(null, null)     = null
+     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
+     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
+     * ArrayUtils.addAll([], [])         = []
+     * ArrayUtils.addAll([null], [null]) = [null, null]
+     * ArrayUtils.addAll(["a", "b", "c"], ["1", "2", "3"]) = ["a", "b", "c", "1", "2", "3"]
+     * 
+ * + * @param the component type of the array + * @param array1 the first array whose elements are added to the new array, may be {@code null} + * @param array2 the second array whose elements are added to the new array, may be {@code null} + * @return The new array, {@code null} if both arrays are {@code null}. + * The type of the new array is the type of the first array, + * unless the first array is null, in which case the type is the same as the second array. + * @since 2.1 + * @throws IllegalArgumentException if the array types are incompatible + */ + public static T[] addAll(final T[] array1, final T... array2) { + if (array1 == null) { + return clone(array2); + } else if (array2 == null) { + return clone(array1); + } + final Class type1 = array1.getClass().getComponentType(); + @SuppressWarnings("unchecked") // OK, because array is of type T + final T[] joinedArray = (T[]) Array.newInstance(type1, array1.length + array2.length); + System.arraycopy(array1, 0, joinedArray, 0, array1.length); + try { + System.arraycopy(array2, 0, joinedArray, array1.length, array2.length); + } catch (final ArrayStoreException ase) { + // Check if problem was due to incompatible types + /* + * We do this here, rather than before the copy because: + * - it would be a wasted check most of the time + * - safer, in case check turns out to be too strict + */ + final Class type2 = array2.getClass().getComponentType(); + if (!type1.isAssignableFrom(type2)) { + throw new IllegalArgumentException("Cannot store " + type2.getName() + " in an array of " + + type1.getName(), ase); + } + throw ase; // No, so rethrow original + } + return joinedArray; + } + + //----------------------------------------------------------------------- + /** + *

Converts all the whitespace separated words in a String into capitalized words, + * that is each word is made up of a titlecase character and then a series of + * lowercase characters.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}. + * A null input String returns null. + * Capitalization uses the Unicode title case, normally equivalent to + * upper case.

+ * + *
+     * WordUtils.capitalizeFully(null)        = null
+     * WordUtils.capitalizeFully("")          = ""
+     * WordUtils.capitalizeFully("i am FINE") = "I Am Fine"
+     * 
+ * + * @param str the String to capitalize, may be null + * @return capitalized String, null if null String input + */ + public static String capitalizeFully(final String str) { + return capitalizeFully(str, null); + } + + /** + *

Converts all the delimiter separated words in a String into capitalized words, + * that is each word is made up of a titlecase character and then a series of + * lowercase characters.

+ * + *

The delimiters represent a set of characters understood to separate words. + * The first string character and the first non-delimiter character after a + * delimiter will be capitalized.

+ * + *

A null input String returns null. + * Capitalization uses the Unicode title case, normally equivalent to + * upper case.

+ * + *
+     * WordUtils.capitalizeFully(null, *)            = null
+     * WordUtils.capitalizeFully("", *)              = ""
+     * WordUtils.capitalizeFully(*, null)            = *
+     * WordUtils.capitalizeFully(*, new char[0])     = *
+     * WordUtils.capitalizeFully("i aM.fine", {'.'}) = "I am.Fine"
+     * 
+ * + * @param str the String to capitalize, may be null + * @param delimiters set of characters to determine capitalization, null means whitespace + * @return capitalized String, null if null String input + * @since 2.1 + */ + public static String capitalizeFully(String str, final char... delimiters) { + final int delimLen = delimiters == null ? -1 : delimiters.length; + if (str == null || str.isEmpty() || delimLen == 0) { + return str; + } + str = str.toLowerCase(); + return capitalize(str, delimiters); + } + + // Capitalizing + //----------------------------------------------------------------------- + /** + *

Capitalizes all the whitespace separated words in a String. + * Only the first character of each word is changed. To convert the + * rest of each word to lowercase at the same time, + * use {@link #capitalizeFully(String)}.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}. + * A null input String returns null. + * Capitalization uses the Unicode title case, normally equivalent to + * upper case.

+ * + *
+     * WordUtils.capitalize(null)        = null
+     * WordUtils.capitalize("")          = ""
+     * WordUtils.capitalize("i am FINE") = "I Am FINE"
+     * 
+ * + * @param str the String to capitalize, may be null + * @return capitalized String, null if null String input + * @see #capitalizeFully(String) + */ + public static String capitalize(final String str) { + return capitalize(str, null); + } + + /** + *

Capitalizes all the delimiter separated words in a String. + * Only the first character of each word is changed. To convert the + * rest of each word to lowercase at the same time, + * use {@link #capitalizeFully(String, char[])}.

+ * + *

The delimiters represent a set of characters understood to separate words. + * The first string character and the first non-delimiter character after a + * delimiter will be capitalized.

+ * + *

A null input String returns null. + * Capitalization uses the Unicode title case, normally equivalent to + * upper case.

+ * + *
+     * WordUtils.capitalize(null, *)            = null
+     * WordUtils.capitalize("", *)              = ""
+     * WordUtils.capitalize(*, new char[0])     = *
+     * WordUtils.capitalize("i am fine", null)  = "I Am Fine"
+     * WordUtils.capitalize("i aM.fine", {'.'}) = "I aM.Fine"
+     * 
+ * + * @param str the String to capitalize, may be null + * @param delimiters set of characters to determine capitalization, null means whitespace + * @return capitalized String, null if null String input + * @see #capitalizeFully(String) + * @since 2.1 + */ + public static String capitalize(final String str, final char... delimiters) { + final int delimLen = delimiters == null ? -1 : delimiters.length; + if (str == null || str.isEmpty() || delimLen == 0) { + return str; + } + final char[] buffer = str.toCharArray(); + boolean capitalizeNext = true; + for (int i = 0; i < buffer.length; i++) { + final char ch = buffer[i]; + if (isDelimiter(ch, delimiters)) { + capitalizeNext = true; + } else if (capitalizeNext) { + buffer[i] = Character.toTitleCase(ch); + capitalizeNext = false; + } + } + return new String(buffer); + } + //----------------------------------------------------------------------- + /** + * Is the character a delimiter. + * + * @param ch the character to check + * @param delimiters the delimiters + * @return true if it is a delimiter + */ + public static boolean isDelimiter(final char ch, final char[] delimiters) { + if (delimiters == null) { + return Character.isWhitespace(ch); + } + for (final char delimiter : delimiters) { + if (ch == delimiter) { + return true; + } + } + return false; + } + + // Joining + //----------------------------------------------------------------------- + /** + *

Joins the elements of the provided array into a single String + * containing the provided list of elements.

+ * + *

No separator is added to the joined String. + * Null objects or empty strings within the array are represented by + * empty strings.

+ * + *
+     * StringUtils.join(null)            = null
+     * StringUtils.join([])              = ""
+     * StringUtils.join([null])          = ""
+     * StringUtils.join(["a", "b", "c"]) = "abc"
+     * StringUtils.join([null, "", "a"]) = "a"
+     * 
+ * + * @param the specific type of values to join together + * @param elements the values to join together, may be null + * @return the joined String, {@code null} if null array input + * @since 2.0 + * @since 3.0 Changed signature to use varargs + */ + @SafeVarargs + public static String join(final T... elements) { + return join(elements, null); + } + + /** + *

Joins the elements of the provided array into a single String + * containing the provided list of elements.

+ * + *

No delimiter is added before or after the list. + * Null objects or empty strings within the array are represented by + * empty strings.

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join(["a", "b", "c"], ';')  = "a;b;c"
+     * StringUtils.join(["a", "b", "c"], null) = "abc"
+     * StringUtils.join([null, "", "a"], ';')  = ";;a"
+     * 
+ * + * @param array the array of values to join together, may be null + * @param separator the separator character to use + * @return the joined String, {@code null} if null array input + * @since 2.0 + */ + public static String join(final Object[] array, final char separator) { + if (array == null) { + return null; + } + return join(array, separator, 0, array.length); + } + + /** + *

+ * Joins the elements of the provided array into a single String containing the provided list of elements. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final long[] array, final char separator) { + if (array == null) { + return null; + } + return join(array, separator, 0, array.length); + } + + /** + *

+ * Joins the elements of the provided array into a single String containing the provided list of elements. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final int[] array, final char separator) { + if (array == null) { + return null; + } + return join(array, separator, 0, array.length); + } + + /** + *

+ * Joins the elements of the provided array into a single String containing the provided list of elements. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final short[] array, final char separator) { + if (array == null) { + return null; + } + return join(array, separator, 0, array.length); + } + + /** + *

+ * Joins the elements of the provided array into a single String containing the provided list of elements. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final byte[] array, final char separator) { + if (array == null) { + return null; + } + return join(array, separator, 0, array.length); + } + + /** + *

+ * Joins the elements of the provided array into a single String containing the provided list of elements. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final char[] array, final char separator) { + if (array == null) { + return null; + } + return join(array, separator, 0, array.length); + } + + /** + *

+ * Joins the elements of the provided array into a single String containing the provided list of elements. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final float[] array, final char separator) { + if (array == null) { + return null; + } + return join(array, separator, 0, array.length); + } + + /** + *

+ * Joins the elements of the provided array into a single String containing the provided list of elements. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final double[] array, final char separator) { + if (array == null) { + return null; + } + return join(array, separator, 0, array.length); + } + + + /** + *

Joins the elements of the provided array into a single String + * containing the provided list of elements.

+ * + *

No delimiter is added before or after the list. + * Null objects or empty strings within the array are represented by + * empty strings.

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join(["a", "b", "c"], ';')  = "a;b;c"
+     * StringUtils.join(["a", "b", "c"], null) = "abc"
+     * StringUtils.join([null, "", "a"], ';')  = ";;a"
+     * 
+ * + * @param array the array of values to join together, may be null + * @param separator the separator character to use + * @param startIndex the first index to start joining from. It is + * an error to pass in an end index past the end of the array + * @param endIndex the index to stop joining from (exclusive). It is + * an error to pass in an end index past the end of the array + * @return the joined String, {@code null} if null array input + * @since 2.0 + */ + public static String join(final Object[] array, final char separator, final int startIndex, final int endIndex) { + if (array == null) { + return null; + } + final int noOfItems = endIndex - startIndex; + if (noOfItems <= 0) { + return EMPTY; + } + final StringBuilder buf = new StringBuilder(noOfItems * 16); + for (int i = startIndex; i < endIndex; i++) { + if (i > startIndex) { + buf.append(separator); + } + if (array[i] != null) { + buf.append(array[i]); + } + } + return buf.toString(); + } + + /** + *

+ * Joins the elements of the provided array into a single String containing the provided list of elements. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @param startIndex + * the first index to start joining from. It is an error to pass in an end index past the end of the + * array + * @param endIndex + * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of + * the array + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final long[] array, final char separator, final int startIndex, final int endIndex) { + if (array == null) { + return null; + } + final int noOfItems = endIndex - startIndex; + if (noOfItems <= 0) { + return EMPTY; + } + final StringBuilder buf = new StringBuilder(noOfItems * 16); + for (int i = startIndex; i < endIndex; i++) { + if (i > startIndex) { + buf.append(separator); + } + buf.append(array[i]); + } + return buf.toString(); + } + + /** + *

+ * Joins the elements of the provided array into a single String containing the provided list of elements. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @param startIndex + * the first index to start joining from. It is an error to pass in an end index past the end of the + * array + * @param endIndex + * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of + * the array + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final int[] array, final char separator, final int startIndex, final int endIndex) { + if (array == null) { + return null; + } + final int noOfItems = endIndex - startIndex; + if (noOfItems <= 0) { + return EMPTY; + } + final StringBuilder buf = new StringBuilder(noOfItems * 16); + for (int i = startIndex; i < endIndex; i++) { + if (i > startIndex) { + buf.append(separator); + } + buf.append(array[i]); + } + return buf.toString(); + } + + /** + *

+ * Joins the elements of the provided array into a single String containing the provided list of elements. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @param startIndex + * the first index to start joining from. It is an error to pass in an end index past the end of the + * array + * @param endIndex + * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of + * the array + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final byte[] array, final char separator, final int startIndex, final int endIndex) { + if (array == null) { + return null; + } + final int noOfItems = endIndex - startIndex; + if (noOfItems <= 0) { + return EMPTY; + } + final StringBuilder buf = new StringBuilder(noOfItems * 16); + for (int i = startIndex; i < endIndex; i++) { + if (i > startIndex) { + buf.append(separator); + } + buf.append(array[i]); + } + return buf.toString(); + } + + /** + *

+ * Joins the elements of the provided array into a single String containing the provided list of elements. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @param startIndex + * the first index to start joining from. It is an error to pass in an end index past the end of the + * array + * @param endIndex + * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of + * the array + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final short[] array, final char separator, final int startIndex, final int endIndex) { + if (array == null) { + return null; + } + final int noOfItems = endIndex - startIndex; + if (noOfItems <= 0) { + return EMPTY; + } + final StringBuilder buf = new StringBuilder(noOfItems * 16); + for (int i = startIndex; i < endIndex; i++) { + if (i > startIndex) { + buf.append(separator); + } + buf.append(array[i]); + } + return buf.toString(); + } + + /** + *

+ * Joins the elements of the provided array into a single String containing the provided list of elements. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @param startIndex + * the first index to start joining from. It is an error to pass in an end index past the end of the + * array + * @param endIndex + * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of + * the array + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final char[] array, final char separator, final int startIndex, final int endIndex) { + if (array == null) { + return null; + } + final int noOfItems = endIndex - startIndex; + if (noOfItems <= 0) { + return EMPTY; + } + final StringBuilder buf = new StringBuilder(noOfItems * 16); + for (int i = startIndex; i < endIndex; i++) { + if (i > startIndex) { + buf.append(separator); + } + buf.append(array[i]); + } + return buf.toString(); + } + + /** + *

+ * Joins the elements of the provided array into a single String containing the provided list of elements. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @param startIndex + * the first index to start joining from. It is an error to pass in an end index past the end of the + * array + * @param endIndex + * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of + * the array + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final double[] array, final char separator, final int startIndex, final int endIndex) { + if (array == null) { + return null; + } + final int noOfItems = endIndex - startIndex; + if (noOfItems <= 0) { + return EMPTY; + } + final StringBuilder buf = new StringBuilder(noOfItems * 16); + for (int i = startIndex; i < endIndex; i++) { + if (i > startIndex) { + buf.append(separator); + } + buf.append(array[i]); + } + return buf.toString(); + } + + /** + *

+ * Joins the elements of the provided array into a single String containing the provided list of elements. + *

+ * + *

+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

+ * + *
+     * StringUtils.join(null, *)               = null
+     * StringUtils.join([], *)                 = ""
+     * StringUtils.join([null], *)             = ""
+     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
+     * StringUtils.join([1, 2, 3], null) = "123"
+     * 
+ * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @param startIndex + * the first index to start joining from. It is an error to pass in an end index past the end of the + * array + * @param endIndex + * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of + * the array + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final float[] array, final char separator, final int startIndex, final int endIndex) { + if (array == null) { + return null; + } + final int noOfItems = endIndex - startIndex; + if (noOfItems <= 0) { + return EMPTY; + } + final StringBuilder buf = new StringBuilder(noOfItems * 16); + for (int i = startIndex; i < endIndex; i++) { + if (i > startIndex) { + buf.append(separator); + } + buf.append(array[i]); + } + return buf.toString(); + } + + + /** + *

Joins the elements of the provided array into a single String + * containing the provided list of elements.

+ * + *

No delimiter is added before or after the list. + * A {@code null} separator is the same as an empty String (""). + * Null objects or empty strings within the array are represented by + * empty strings.

+ * + *
+     * StringUtils.join(null, *)                = null
+     * StringUtils.join([], *)                  = ""
+     * StringUtils.join([null], *)              = ""
+     * StringUtils.join(["a", "b", "c"], "--")  = "a--b--c"
+     * StringUtils.join(["a", "b", "c"], null)  = "abc"
+     * StringUtils.join(["a", "b", "c"], "")    = "abc"
+     * StringUtils.join([null, "", "a"], ',')   = ",,a"
+     * 
+ * + * @param array the array of values to join together, may be null + * @param separator the separator character to use, null treated as "" + * @return the joined String, {@code null} if null array input + */ + public static String join(final Object[] array, final String separator) { + if (array == null) { + return null; + } + return join(array, separator, 0, array.length); + } + + /** + *

Joins the elements of the provided array into a single String + * containing the provided list of elements.

+ * + *

No delimiter is added before or after the list. + * A {@code null} separator is the same as an empty String (""). + * Null objects or empty strings within the array are represented by + * empty strings.

+ * + *
+     * StringUtils.join(null, *, *, *)                = null
+     * StringUtils.join([], *, *, *)                  = ""
+     * StringUtils.join([null], *, *, *)              = ""
+     * StringUtils.join(["a", "b", "c"], "--", 0, 3)  = "a--b--c"
+     * StringUtils.join(["a", "b", "c"], "--", 1, 3)  = "b--c"
+     * StringUtils.join(["a", "b", "c"], "--", 2, 3)  = "c"
+     * StringUtils.join(["a", "b", "c"], "--", 2, 2)  = ""
+     * StringUtils.join(["a", "b", "c"], null, 0, 3)  = "abc"
+     * StringUtils.join(["a", "b", "c"], "", 0, 3)    = "abc"
+     * StringUtils.join([null, "", "a"], ',', 0, 3)   = ",,a"
+     * 
+ * + * @param array the array of values to join together, may be null + * @param separator the separator character to use, null treated as "" + * @param startIndex the first index to start joining from. + * @param endIndex the index to stop joining from (exclusive). + * @return the joined String, {@code null} if null array input; or the empty string + * if {@code endIndex - startIndex <= 0}. The number of joined entries is given by + * {@code endIndex - startIndex} + * @throws ArrayIndexOutOfBoundsException ife
+ * {@code startIndex < 0} or
+ * {@code startIndex >= array.length()} or
+ * {@code endIndex < 0} or
+ * {@code endIndex > array.length()} + */ + public static String join(final Object[] array, String separator, final int startIndex, final int endIndex) { + if (array == null) { + return null; + } + if (separator == null) { + separator = EMPTY; + } + + // endIndex - startIndex > 0: Len = NofStrings *(len(firstString) + len(separator)) + // (Assuming that all Strings are roughly equally long) + final int noOfItems = endIndex - startIndex; + if (noOfItems <= 0) { + return EMPTY; + } + + final StringBuilder buf = new StringBuilder(noOfItems * 16); + + for (int i = startIndex; i < endIndex; i++) { + if (i > startIndex) { + buf.append(separator); + } + if (array[i] != null) { + buf.append(array[i]); + } + } + return buf.toString(); + } + + /** + *

Joins the elements of the provided {@code Iterator} into + * a single String containing the provided elements.

+ * + *

No delimiter is added before or after the list. Null objects or empty + * strings within the iteration are represented by empty strings.

+ * + *

See the examples here: {@link #join(Object[],char)}.

+ * + * @param iterator the {@code Iterator} of values to join together, may be null + * @param separator the separator character to use + * @return the joined String, {@code null} if null iterator input + * @since 2.0 + */ + public static String join(final Iterator iterator, final char separator) { + + // handle null, zero and one elements before building a buffer + if (iterator == null) { + return null; + } + if (!iterator.hasNext()) { + return EMPTY; + } + final Object first = iterator.next(); + if (!iterator.hasNext()) { + final String result = first != null ? first.toString() : ""; + return result; + } + + // two or more elements + final StringBuilder buf = new StringBuilder(256); // Java default is 16, probably too small + if (first != null) { + buf.append(first); + } + + while (iterator.hasNext()) { + buf.append(separator); + final Object obj = iterator.next(); + if (obj != null) { + buf.append(obj); + } + } + + return buf.toString(); + } + + /** + *

Joins the elements of the provided {@code Iterator} into + * a single String containing the provided elements.

+ * + *

No delimiter is added before or after the list. + * A {@code null} separator is the same as an empty String ("").

+ * + *

See the examples here: {@link #join(Object[],String)}.

+ * + * @param iterator the {@code Iterator} of values to join together, may be null + * @param separator the separator character to use, null treated as "" + * @return the joined String, {@code null} if null iterator input + */ + public static String join(final Iterator iterator, final String separator) { + + // handle null, zero and one elements before building a buffer + if (iterator == null) { + return null; + } + if (!iterator.hasNext()) { + return EMPTY; + } + final Object first = iterator.next(); + if (!iterator.hasNext()) { + final String result = first != null ? first.toString() : ""; + return result; + } + + // two or more elements + final StringBuilder buf = new StringBuilder(256); // Java default is 16, probably too small + if (first != null) { + buf.append(first); + } + + while (iterator.hasNext()) { + if (separator != null) { + buf.append(separator); + } + final Object obj = iterator.next(); + if (obj != null) { + buf.append(obj); + } + } + return buf.toString(); + } + + /** + *

Joins the elements of the provided {@code Iterable} into + * a single String containing the provided elements.

+ * + *

No delimiter is added before or after the list. Null objects or empty + * strings within the iteration are represented by empty strings.

+ * + *

See the examples here: {@link #join(Object[],char)}.

+ * + * @param iterable the {@code Iterable} providing the values to join together, may be null + * @param separator the separator character to use + * @return the joined String, {@code null} if null iterator input + * @since 2.3 + */ + public static String join(final Iterable iterable, final char separator) { + if (iterable == null) { + return null; + } + return join(iterable.iterator(), separator); + } + + /** + *

Joins the elements of the provided {@code Iterable} into + * a single String containing the provided elements.

+ * + *

No delimiter is added before or after the list. + * A {@code null} separator is the same as an empty String ("").

+ * + *

See the examples here: {@link #join(Object[],String)}.

+ * + * @param iterable the {@code Iterable} providing the values to join together, may be null + * @param separator the separator character to use, null treated as "" + * @return the joined String, {@code null} if null iterator input + * @since 2.3 + */ + public static String join(final Iterable iterable, final String separator) { + if (iterable == null) { + return null; + } + return join(iterable.iterator(), separator); + } + + + /** + *

Checks if the CharSequence contains only Unicode digits. + * A decimal point is not a Unicode digit and returns false.

+ * + *

{@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code false}.

+ * + *

Note that the method does not allow for a leading sign, either positive or negative. + * Also, if a String passes the numeric test, it may still generate a NumberFormatException + * when parsed by Integer.parseInt or Long.parseLong, e.g. if the value is outside the range + * for int or long respectively.

+ * + *
+     * StringUtils.isNumeric(null)   = false
+     * StringUtils.isNumeric("")     = false
+     * StringUtils.isNumeric("  ")   = false
+     * StringUtils.isNumeric("123")  = true
+     * StringUtils.isNumeric("\u0967\u0968\u0969")  = true
+     * StringUtils.isNumeric("12 3") = false
+     * StringUtils.isNumeric("ab2c") = false
+     * StringUtils.isNumeric("12-3") = false
+     * StringUtils.isNumeric("12.3") = false
+     * StringUtils.isNumeric("-123") = false
+     * StringUtils.isNumeric("+123") = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains digits, and is non-null + * @since 3.0 Changed signature from isNumeric(String) to isNumeric(CharSequence) + * @since 3.0 Changed "" to return false and not true + */ + public static boolean isNumeric(final CharSequence cs) { + if (cs == null || cs.length() == 0) { + return false; + } + final int sz = cs.length(); + for (int i = 0; i < sz; i++) { + if (!Character.isDigit(cs.charAt(i))) { + return false; + } + } + return true; + } + + + // startsWith + //----------------------------------------------------------------------- + + /** + *

Check if a CharSequence starts with a specified prefix.

+ * + *

{@code null}s are handled without exceptions. Two {@code null} + * references are considered to be equal. The comparison is case sensitive.

+ * + *
+     * StringUtils.startsWith(null, null)      = true
+     * StringUtils.startsWith(null, "abc")     = false
+     * StringUtils.startsWith("abcdef", null)  = false
+     * StringUtils.startsWith("abcdef", "abc") = true
+     * StringUtils.startsWith("ABCDEF", "abc") = false
+     * 
+ * + * @see java.lang.String#startsWith(String) + * @param str the CharSequence to check, may be null + * @param prefix the prefix to find, may be null + * @return {@code true} if the CharSequence starts with the prefix, case sensitive, or + * both {@code null} + * @since 2.4 + * @since 3.0 Changed signature from startsWith(String, String) to startsWith(CharSequence, CharSequence) + */ + public static boolean startsWith(final CharSequence str, final CharSequence prefix) { + return startsWith(str, prefix, false); + } + + /** + *

Case insensitive check if a CharSequence starts with a specified prefix.

+ * + *

{@code null}s are handled without exceptions. Two {@code null} + * references are considered to be equal. The comparison is case insensitive.

+ * + *
+     * StringUtils.startsWithIgnoreCase(null, null)      = true
+     * StringUtils.startsWithIgnoreCase(null, "abc")     = false
+     * StringUtils.startsWithIgnoreCase("abcdef", null)  = false
+     * StringUtils.startsWithIgnoreCase("abcdef", "abc") = true
+     * StringUtils.startsWithIgnoreCase("ABCDEF", "abc") = true
+     * 
+ * + * @see java.lang.String#startsWith(String) + * @param str the CharSequence to check, may be null + * @param prefix the prefix to find, may be null + * @return {@code true} if the CharSequence starts with the prefix, case insensitive, or + * both {@code null} + * @since 2.4 + * @since 3.0 Changed signature from startsWithIgnoreCase(String, String) to startsWithIgnoreCase(CharSequence, CharSequence) + */ + public static boolean startsWithIgnoreCase(final CharSequence str, final CharSequence prefix) { + return startsWith(str, prefix, true); + } + + /** + *

Check if a CharSequence starts with a specified prefix (optionally case insensitive).

+ * + * @see java.lang.String#startsWith(String) + * @param str the CharSequence to check, may be null + * @param prefix the prefix to find, may be null + * @param ignoreCase indicates whether the compare should ignore case + * (case insensitive) or not. + * @return {@code true} if the CharSequence starts with the prefix or + * both {@code null} + */ + private static boolean startsWith(final CharSequence str, final CharSequence prefix, final boolean ignoreCase) { + if (str == null || prefix == null) { + return str == null && prefix == null; + } + if (prefix.length() > str.length()) { + return false; + } + return regionMatches(str, ignoreCase, 0, prefix, 0, prefix.length()); + } + + /** + * Green implementation of regionMatches. + * + * @param cs the {@code CharSequence} to be processed + * @param ignoreCase whether or not to be case insensitive + * @param thisStart the index to start on the {@code cs} CharSequence + * @param substring the {@code CharSequence} to be looked for + * @param start the index to start on the {@code substring} CharSequence + * @param length character length of the region + * @return whether the region matched + */ + static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart, + final CharSequence substring, final int start, final int length) { + if (cs instanceof String && substring instanceof String) { + return ((String) cs).regionMatches(ignoreCase, thisStart, (String) substring, start, length); + } + int index1 = thisStart; + int index2 = start; + int tmpLen = length; + + // Extract these first so we detect NPEs the same as the java.lang.String version + final int srcLen = cs.length() - thisStart; + final int otherLen = substring.length() - start; + + // Check for invalid parameters + if (thisStart < 0 || start < 0 || length < 0) { + return false; + } + + // Check that the regions are long enough + if (srcLen < length || otherLen < length) { + return false; + } + + while (tmpLen-- > 0) { + final char c1 = cs.charAt(index1++); + final char c2 = substring.charAt(index2++); + + if (c1 == c2) { + continue; + } + + if (!ignoreCase) { + return false; + } + + // The same check as in String.regionMatches(): + if (Character.toUpperCase(c1) != Character.toUpperCase(c2) + && Character.toLowerCase(c1) != Character.toLowerCase(c2)) { + return false; + } + } + + return true; + } + + /** + * The index value when an element is not found in a list or array: -1. + * This value is returned by methods in this class and can also be used in comparisons with values returned by + * various method from {@link java.util.List}. + */ + public static final int INDEX_NOT_FOUND = -1; + + // IndexOf search + // ---------------------------------------------------------------------- + + // Object IndexOf + //----------------------------------------------------------------------- + /** + *

Finds the index of the given object in the array.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} (-1) for a null input array.

+ * + * @param array the array to search through for the object, may be null + * @param objectToFind the object to find, may be null + * @return the index of the object within the array, + * {@link #INDEX_NOT_FOUND} (-1) if not found or null array input + */ + public static int indexOf(Object[] array, Object objectToFind) { + return indexOf(array, objectToFind, 0); + } + + /** + *

Finds the index of the given object in the array starting at the given index.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} (-1) for a null input array.

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} (-1).

+ * + * @param array the array to search through for the object, may be null + * @param objectToFind the object to find, may be null + * @param startIndex the index to start searching at + * @return the index of the object within the array starting at the index, + * {@link #INDEX_NOT_FOUND} (-1) if not found or null array input + */ + public static int indexOf(Object[] array, Object objectToFind, int startIndex) { + if (array == null) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + startIndex = 0; + } + if (objectToFind == null) { + for (int i = startIndex; i < array.length; i++) { + if (array[i] == null) { + return i; + } + } + } else { + for (int i = startIndex; i < array.length; i++) { + if (objectToFind.equals(array[i])) { + return i; + } + } + } + return INDEX_NOT_FOUND; + } +} diff --git a/core/src/main/java/co/aikar/commands/contexts/ContextResolver.java b/core/src/main/java/co/aikar/commands/contexts/ContextResolver.java index 6887e581..9f194889 100644 --- a/core/src/main/java/co/aikar/commands/contexts/ContextResolver.java +++ b/core/src/main/java/co/aikar/commands/contexts/ContextResolver.java @@ -27,6 +27,6 @@ import co.aikar.commands.CommandExecutionContext; import co.aikar.commands.InvalidCommandArgument; @FunctionalInterface -public interface ContextResolver { - C getContext(CommandExecutionContext c) throws InvalidCommandArgument; +public interface ContextResolver { + C getContext(CommandExecutionContext c) throws InvalidCommandArgument; } diff --git a/core/src/main/java/co/aikar/commands/contexts/SenderAwareContextResolver.java b/core/src/main/java/co/aikar/commands/contexts/SenderAwareContextResolver.java index ca7f1b06..f9f533b2 100644 --- a/core/src/main/java/co/aikar/commands/contexts/SenderAwareContextResolver.java +++ b/core/src/main/java/co/aikar/commands/contexts/SenderAwareContextResolver.java @@ -23,4 +23,6 @@ package co.aikar.commands.contexts; -public interface SenderAwareContextResolver extends ContextResolver {} +import co.aikar.commands.CommandExecutionContext; + +public interface SenderAwareContextResolver extends ContextResolver {} diff --git a/example/acf-example.iml b/example/acf-example.iml index 3ff642d6..9b892ff5 100644 --- a/example/acf-example.iml +++ b/example/acf-example.iml @@ -34,8 +34,9 @@ + - +
\ No newline at end of file diff --git a/example/pom.xml b/example/pom.xml index 5ce18259..ac0fa778 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -32,12 +32,13 @@ 1.0-SNAPSHOT jar - ACFExample + ACF (Example) Example / Test for ACF UTF-8 + https://github.com/aikar/commands @@ -110,8 +111,8 @@ co.aikar - acf-core - 0.4.0-SNAPSHOT + acf-bukkit + 0.5.0 diff --git a/example/src/main/java/co/aikar/acfexample/ACFExample.java b/example/src/main/java/co/aikar/acfexample/ACFExample.java index 8e3b6d4c..12988bf7 100644 --- a/example/src/main/java/co/aikar/acfexample/ACFExample.java +++ b/example/src/main/java/co/aikar/acfexample/ACFExample.java @@ -23,7 +23,7 @@ package co.aikar.acfexample; -import co.aikar.commands.ACF; +import co.aikar.commands.BukkitCommandManager; import co.aikar.commands.CommandManager; import com.google.common.collect.Lists; import org.bukkit.plugin.java.JavaPlugin; @@ -39,7 +39,7 @@ public final class ACFExample extends JavaPlugin { } private void registerCommands() { - commandManager = ACF.createManager(this); + commandManager = new BukkitCommandManager(this); commandManager.getCommandReplacements().addReplacements("test", "foobar", "%foo", "barbaz"); commandManager.getCommandContexts().registerContext(SomeObject.class, SomeObject.getContextResolver()); commandManager.getCommandCompletions().registerCompletion("test", (sender, config, input, c) -> ( diff --git a/example/src/main/java/co/aikar/acfexample/SomeCommand_ExtraSubs.java b/example/src/main/java/co/aikar/acfexample/SomeCommand_ExtraSubs.java index ce200bd8..25db164d 100644 --- a/example/src/main/java/co/aikar/acfexample/SomeCommand_ExtraSubs.java +++ b/example/src/main/java/co/aikar/acfexample/SomeCommand_ExtraSubs.java @@ -27,14 +27,14 @@ import co.aikar.commands.BaseCommand; import co.aikar.commands.annotation.CommandAlias; import co.aikar.commands.annotation.CommandCompletion; import co.aikar.commands.annotation.Subcommand; -import org.bukkit.command.CommandSender; +import org.bukkit.command.CommandIssuer; @CommandAlias("acf") public class SomeCommand_ExtraSubs extends BaseCommand { @Subcommand("testsub test2") @CommandCompletion("Foo2") - public void onTestSub2(CommandSender sender, String hi) { + public void onTestSub2(CommandIssuer sender, String hi) { sender.sendMessage(hi); } } diff --git a/paper/acf-paper.iml b/paper/acf-paper.iml new file mode 100644 index 00000000..f67f0b96 --- /dev/null +++ b/paper/acf-paper.iml @@ -0,0 +1,34 @@ + + + + + + + BUKKIT + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/paper/pom.xml b/paper/pom.xml new file mode 100644 index 00000000..7edd0ffb --- /dev/null +++ b/paper/pom.xml @@ -0,0 +1,60 @@ + + + + + 4.0.0 + + + co.aikar + acf-parent + 0.5.0 + ../pom.xml + + + acf-paper + 0.5.0 + + ACF (Paper) + + + + co.aikar + acf-core + 0.5.0 + + + co.aikar + acf-bukkit + 0.5.0 + + + org.bukkit + bukkit + 1.12-pre2-SNAPSHOT + provided + + + diff --git a/core/src/main/java/co/aikar/commands/PaperCommandCompletions.java b/paper/src/main/java/co/aikar/commands/PaperCommandCompletions.java similarity index 100% rename from core/src/main/java/co/aikar/commands/PaperCommandCompletions.java rename to paper/src/main/java/co/aikar/commands/PaperCommandCompletions.java diff --git a/core/src/main/java/co/aikar/commands/PaperCommandContexts.java b/paper/src/main/java/co/aikar/commands/PaperCommandContexts.java similarity index 100% rename from core/src/main/java/co/aikar/commands/PaperCommandContexts.java rename to paper/src/main/java/co/aikar/commands/PaperCommandContexts.java diff --git a/core/src/main/java/co/aikar/commands/PaperCommandManager.java b/paper/src/main/java/co/aikar/commands/PaperCommandManager.java similarity index 100% rename from core/src/main/java/co/aikar/commands/PaperCommandManager.java rename to paper/src/main/java/co/aikar/commands/PaperCommandManager.java diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..238e5fde --- /dev/null +++ b/pom.xml @@ -0,0 +1,106 @@ + + + + 4.0.0 + co.aikar + acf-parent + 0.5.0 + pom + ACF (All) + https://acf.emc.gs + + + UTF-8 + + + + + aikar + http://ci.emc.gs/nexus/content/repositories/aikar-release/ + + + aikar + http://ci.emc.gs/nexus/content/repositories/aikar-snapshots/ + + + + + + aikar + http://ci.emc.gs/nexus/content/groups/aikar/ + + + + + clean install + ${project.artifactId} + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + 1.8 + 1.8 + false + false + + -parameters + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + + attach-sources + + jar + + + + + + + + + + org.jetbrains + annotations + 13.0 + + + + + core + bukkit + paper + + + example + +