diff --git a/CHANGELOG.txt b/CHANGELOG.txt index a704ccb7e..2e1949fe4 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,32 @@ +2.5: +- Fixed issues with permissions not being read correctly. +- WorldEdit is now world-aware (not that the Minecraft server is). +- Abstracted super pickaxe mode/tools and changed commands to /tree, /info, + /none, /single, /area, and /recur. +- New /repl super pickaxe tool. +- Now bundling JNBT. +- Add a very rudimentary command line program that will check the integrity + (a very basic integrity check) of a world. + +2.4: +- Added the ability to use (require) inventory with operations, preventing + people from setting blocks that they don't have. +- Simplified the max blocks change limit to be binary (either you have it or + not). Also separated the 'max' limit and the 'default' limit in terms + of configuration. This means that the WorldEdit restrictions file is + no longer used. +- A large part of the code was moved around (again) to make porting + WorldEdit to other modding APIs easier, but this means that something + may have broken. +- Chest handling was rewritten for Minecraft beta, so it should be + reliable now and not cause exceptions. +- Item damage is now managed by WorldEdit's chest handling APIs. +- Worked around an issue with the java.util.zip implementation that + caused ZIP files containing backslashes to not work correctly. +- Changed the TrueZip support to use the API for java.util.zip, meaning + some bugs should be fixed. +- Made all commands support double forward slashes as the command prefix. + 2.3.4: - Fixed issues with chests. - Added configuration option that disables the "//" prefix and lets you use diff --git a/INSTALL.txt b/INSTALL.txt index da9bd9a53..22fa78202 100644 --- a/INSTALL.txt +++ b/INSTALL.txt @@ -5,7 +5,7 @@ This plugin requires Hey0's server modification. 1. Create a "plugins" folder inside your "bin" folder. - 2. Copy WorldEdit.jar and jnbt.jar into "plugins". + 2. Copy WorldEdit.jar into "plugins". 3. Add "WorldEdit" to the "plugins" line of your server.properties file. If it's not already there, add the line. The line should look like this: diff --git a/LICENSE.txt b/LICENSE.txt index 4bb208699..b5eb8c954 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -202,3 +202,29 @@ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +JOpt Simple License +------------------- + +Copyright (c) 2009 Paul R. Holser, Jr. + +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. diff --git a/NOTICE.txt b/NOTICE.txt index 5faae2dd3..3ff67b24f 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,4 +1,6 @@ This product includes software from JNBT (http://jnbt.sourceforge.net/). This product includes software from WorldEdit -(http://www.sk89q.com), under GNU Lesser General Public License, version 3. \ No newline at end of file +(http://www.sk89q.com), under GNU Lesser General Public License, version 3. + +This product includes software from JOpt Simple, under the MIT license. \ No newline at end of file diff --git a/build.xml b/build.xml index 86f061d18..7e4625001 100644 --- a/build.xml +++ b/build.xml @@ -18,7 +18,6 @@ - @@ -34,7 +33,8 @@ - + + @@ -59,17 +59,12 @@ - - - - - - + diff --git a/src/HMConfiguration.java b/src/HMConfiguration.java new file mode 100644 index 000000000..44ff7b200 --- /dev/null +++ b/src/HMConfiguration.java @@ -0,0 +1,122 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import java.io.IOException; +import java.util.HashSet; +import java.util.logging.FileHandler; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.Logger; +import com.sk89q.util.StringUtil; +import com.sk89q.worldedit.LocalConfiguration; +import com.sk89q.worldedit.LogFormat; +import com.sk89q.worldedit.snapshots.SnapshotRepository; + +/** + * Configuration for hMod. + * + * @author sk89q + */ +public class HMConfiguration extends LocalConfiguration { + /** + * Logger. + */ + private static final Logger logger = Logger.getLogger("Minecraft.WorldEdit"); + + /** + * Properties file. + */ + private PropertiesFile properties; + + /** + * Construct the object. + */ + public HMConfiguration() { + properties = new PropertiesFile("worldedit.properties"); + } + + /** + * Loads the configuration. + */ + public void load() { + try { + properties.load(); + } catch (IOException e) { + logger.warning("worldedit.properties could not be loaded: " + + e.getMessage()); + } + + profile = properties.getBoolean("debug-profile", profile); + wandItem = properties.getInt("wand-item", wandItem); + defaultChangeLimit = Math.max(-1, properties.getInt( + "default-max-blocks-changed", defaultChangeLimit)); + maxChangeLimit = Math.max(-1, + properties.getInt("max-blocks-changed", maxChangeLimit)); + maxRadius = Math.max(-1, properties.getInt("max-radius", maxRadius)); + maxSuperPickaxeSize = Math.max(1, properties.getInt( + "max-super-pickaxe-size", maxSuperPickaxeSize)); + registerHelp = properties.getBoolean("register-help", registerHelp); + logComands = properties.getBoolean("log-commands", logComands); + superPickaxeDrop = properties.getBoolean("super-pickaxe-drop-items", + superPickaxeDrop); + superPickaxeManyDrop = properties.getBoolean( + "super-pickaxe-many-drop-items", superPickaxeManyDrop); + noDoubleSlash = properties.getBoolean("no-double-slash", noDoubleSlash); + useInventory = properties.getBoolean("use-inventory", useInventory); + useInventoryOverride = properties.getBoolean("use-inventory-override", + useInventoryOverride); + + // Get disallowed blocks + disallowedBlocks = new HashSet(); + String defdisallowedBlocks = StringUtil.joinString(defaultDisallowedBlocks, ",", 0); + for (String b : properties.getString("disallowed-blocks", + defdisallowedBlocks).split(",")) { + try { + disallowedBlocks.add(Integer.parseInt(b)); + } catch (NumberFormatException e) { + } + } + + String snapshotsDir = properties.getString("snapshots-dir", ""); + if (!snapshotsDir.trim().equals("")) { + snapshotRepo = new SnapshotRepository(snapshotsDir); + } else { + snapshotRepo = null; + } + + String type = properties.getString("shell-save-type", "").trim(); + shellSaveType = type.equals("") ? null : type; + + String logFile = properties.getString("log-file", ""); + if (!logFile.equals("")) { + try { + FileHandler handler = new FileHandler(logFile, true); + handler.setFormatter(new LogFormat()); + logger.addHandler(handler); + } catch (IOException e) { + logger.log(Level.WARNING, "Could not use log file " + logFile + ": " + + e.getMessage()); + } + } else { + for (Handler handler : logger.getHandlers()) { + logger.removeHandler(handler); + } + } + } +} diff --git a/src/HMPlayer.java b/src/HMPlayer.java index 46d09acb6..4c3df8dce 100644 --- a/src/HMPlayer.java +++ b/src/HMPlayer.java @@ -17,9 +17,11 @@ * along with this program. If not, see . */ +import com.sk89q.worldedit.LocalWorld; import com.sk89q.worldedit.ServerInterface; import com.sk89q.worldedit.Vector; -import com.sk89q.worldedit.WorldEditPlayer; +import com.sk89q.worldedit.LocalPlayer; +import com.sk89q.worldedit.WorldVector; import com.sk89q.worldedit.bags.BlockBag; import com.sk89q.worldedit.blocks.BlockType; @@ -27,7 +29,7 @@ import com.sk89q.worldedit.blocks.BlockType; * * @author sk89q */ -public class HMPlayer extends WorldEditPlayer { +public class HMPlayer extends LocalPlayer { /** * Stores the player. */ @@ -38,8 +40,8 @@ public class HMPlayer extends WorldEditPlayer { * * @param player */ - public HMPlayer(Player player) { - super(); + public HMPlayer(ServerInterface server, Player player) { + super(server); this.player = player; } @@ -58,13 +60,13 @@ public class HMPlayer extends WorldEditPlayer { * @param range * @return point */ - public Vector getBlockTrace(int range) { + public WorldVector getBlockTrace(int range) { HitBlox hitBlox = new HitBlox(player,range, 0.2); Block block = hitBlox.getTargetBlock(); if (block == null) { return null; } - return new Vector(block.getX(), block.getY(), block.getZ()); + return new WorldVector(null, block.getX(), block.getY(), block.getZ()); } /** @@ -73,7 +75,7 @@ public class HMPlayer extends WorldEditPlayer { * @param range * @return point */ - public Vector getSolidBlockTrace(int range) { + public WorldVector getSolidBlockTrace(int range) { HitBlox hitBlox = new HitBlox(player,range, 0.2); Block block = null; @@ -85,14 +87,9 @@ public class HMPlayer extends WorldEditPlayer { if (block == null) { return null; } - return new Vector(block.getX(), block.getY(), block.getZ()); + return new WorldVector(null, block.getX(), block.getY(), block.getZ()); } - /** - * Get the ID of the item that the player is holding. - * - * @return - */ /** * Get the ID of the item that the player is holding. * @@ -111,11 +108,6 @@ public class HMPlayer extends WorldEditPlayer { return player.getName(); } - /** - * Get the player's view pitch. - * - * @return pitch - */ /** * Get the player's view pitch. * @@ -130,15 +122,19 @@ public class HMPlayer extends WorldEditPlayer { * * @return point */ - public Vector getPosition() { - return new Vector(player.getX(), player.getY(), player.getZ()); + public WorldVector getPosition() { + return new WorldVector(null, player.getX(), player.getY(), player.getZ()); } /** - * Get the player's view yaw. + * Get the player's world. * - * @return yaw + * @return point */ + public LocalWorld getWorld() { + return null; + } + /** * Get the player's view yaw. * @@ -148,12 +144,6 @@ public class HMPlayer extends WorldEditPlayer { return player.getRotation(); } - /** - * Gives the player an item. - * - * @param type - * @param amt - */ /** * Gives the player an item. * @@ -174,6 +164,7 @@ public class HMPlayer extends WorldEditPlayer { boolean foundNext = false; int searchDist = 0; HitBlox hitBlox = new HitBlox(player,range, 0.2); + LocalWorld world = getPosition().getWorld(); Block block; while ((block = hitBlox.getNextBlock()) != null) { searchDist++; @@ -183,7 +174,7 @@ public class HMPlayer extends WorldEditPlayer { if (block.getType() == 0) { if (foundNext) { Vector v = new Vector(block.getX(), block.getY() - 1, block.getZ()); - if (server.getBlockType(v) == 0) { + if (server.getBlockType(world, v) == 0) { setPosition(v.add(0.5, 0, 0.5)); return true; } diff --git a/src/HMServerInterface.java b/src/HMServerInterface.java index 8c4812d24..c10ded3d7 100644 --- a/src/HMServerInterface.java +++ b/src/HMServerInterface.java @@ -43,7 +43,7 @@ public class HMServerInterface extends ServerInterface { * @param type * @return */ - public boolean setBlockType(Vector pt, int type) { + public boolean setBlockType(LocalWorld world, Vector pt, int type) { // Can't set colored cloth or crash if ((type >= 21 && type <= 34) || type == 36) { return false; @@ -58,7 +58,7 @@ public class HMServerInterface extends ServerInterface { * @param pt * @return */ - public int getBlockType(Vector pt) { + public int getBlockType(LocalWorld world, Vector pt) { return etc.getServer().getBlockIdAt(pt.getBlockX(), pt.getBlockY(), pt.getBlockZ()); } @@ -70,7 +70,7 @@ public class HMServerInterface extends ServerInterface { * @param data * @return */ - public void setBlockData(Vector pt, int data) { + public void setBlockData(LocalWorld world, Vector pt, int data) { etc.getServer().setBlockData(pt.getBlockX(), pt.getBlockY(), pt.getBlockZ(), data); } @@ -81,7 +81,7 @@ public class HMServerInterface extends ServerInterface { * @param pt * @return */ - public int getBlockData(Vector pt) { + public int getBlockData(LocalWorld world, Vector pt) { return etc.getServer().getBlockData(pt.getBlockX(), pt.getBlockY(), pt.getBlockZ()); } @@ -92,7 +92,7 @@ public class HMServerInterface extends ServerInterface { * @param pt * @param text */ - public void setSignText(Vector pt, String[] text) { + public void setSignText(LocalWorld world, Vector pt, String[] text) { Sign signData = (Sign)etc.getServer().getComplexBlock( pt.getBlockX(), pt.getBlockY(), pt.getBlockZ()); if (signData == null) { @@ -110,7 +110,7 @@ public class HMServerInterface extends ServerInterface { * @param pt * @return */ - public String[] getSignText(Vector pt) { + public String[] getSignText(LocalWorld world, Vector pt) { Sign signData = (Sign)etc.getServer().getComplexBlock( pt.getBlockX(), pt.getBlockY(), pt.getBlockZ()); if (signData == null) { @@ -130,7 +130,7 @@ public class HMServerInterface extends ServerInterface { * @param pt * @return */ - public BaseItemStack[] getChestContents(Vector pt) { + public BaseItemStack[] getChestContents(LocalWorld world, Vector pt) { ComplexBlock cblock = etc.getServer().getOnlyComplexBlock( pt.getBlockX(), pt.getBlockY(), pt.getBlockZ()); @@ -165,7 +165,7 @@ public class HMServerInterface extends ServerInterface { * @param contents * @return */ - public boolean setChestContents(Vector pt, + public boolean setChestContents(LocalWorld world, Vector pt, BaseItemStack[] contents) { ComplexBlock cblock = etc.getServer().getOnlyComplexBlock( @@ -197,7 +197,7 @@ public class HMServerInterface extends ServerInterface { * * @param pt */ - public boolean clearChest(Vector pt) { + public boolean clearChest(LocalWorld world, Vector pt) { ComplexBlock cblock = etc.getServer().getOnlyComplexBlock( pt.getBlockX(), pt.getBlockY(), pt.getBlockZ()); @@ -247,7 +247,7 @@ public class HMServerInterface extends ServerInterface { * @param pt * @param mobType */ - public void setMobSpawnerType(Vector pt, String mobType) { + public void setMobSpawnerType(LocalWorld world, Vector pt, String mobType) { ComplexBlock cblock = etc.getServer().getComplexBlock( pt.getBlockX(), pt.getBlockY(), pt.getBlockZ()); @@ -266,7 +266,7 @@ public class HMServerInterface extends ServerInterface { * @param pt * @param mobType */ - public String getMobSpawnerType(Vector pt) { + public String getMobSpawnerType(LocalWorld world, Vector pt) { try { return MinecraftServerInterface.getMobSpawnerType(pt); } catch (Throwable t) { @@ -282,7 +282,7 @@ public class HMServerInterface extends ServerInterface { * @param pt * @return */ - public boolean generateTree(EditSession editSession, Vector pt) { + public boolean generateTree(EditSession editSession, LocalWorld world, Vector pt) { try { return MinecraftServerInterface.generateTree(editSession, pt); } catch (Throwable t) { @@ -300,7 +300,7 @@ public class HMServerInterface extends ServerInterface { * @param count * @param times */ - public void dropItem(Vector pt, int type, int count, int times) { + public void dropItem(LocalWorld world, Vector pt, int type, int count, int times) { for (int i = 0; i < times; i++) { etc.getServer().dropItem(pt.getBlockX(), pt.getBlockY(), pt.getBlockZ(), type, count); @@ -315,7 +315,7 @@ public class HMServerInterface extends ServerInterface { * @param count * @param times */ - public void dropItem(Vector pt, int type, int count) { + public void dropItem(LocalWorld world, Vector pt, int type, int count) { etc.getServer().dropItem(pt.getBlockX(), pt.getBlockY(), pt.getBlockZ(), type, count); } @@ -328,7 +328,7 @@ public class HMServerInterface extends ServerInterface { * @param count * @param times */ - public void dropItem(Vector pt, int type) { + public void dropItem(LocalWorld world, Vector pt, int type) { etc.getServer().dropItem(pt.getBlockX(), pt.getBlockY(), pt.getBlockZ(), type, 1); } @@ -338,57 +338,57 @@ public class HMServerInterface extends ServerInterface { * * @param pt */ - public void simulateBlockMine(Vector pt) { - int type = getBlockType(pt); - setBlockType(pt, 0); + public void simulateBlockMine(LocalWorld world, Vector pt) { + int type = getBlockType(world, pt); + //setBlockType(world, pt, 0); - if (type == 1) { dropItem(pt, 4); } // Stone - else if (type == 2) { dropItem(pt, 3); } // Grass + if (type == 1) { dropItem(world, pt, 4); } // Stone + else if (type == 2) { dropItem(world, pt, 3); } // Grass else if (type == 7) { } // Bedrock else if (type == 8) { } // Water else if (type == 9) { } // Water else if (type == 10) { } // Lava else if (type == 11) { } // Lava else if (type == 13) { // Gravel - dropItem(pt, type); + dropItem(world, pt, type); if (random.nextDouble() >= 0.9) { - dropItem(pt, 318); + dropItem(world, pt, 318); } } - else if (type == 16) { dropItem(pt, 263); } // Coal ore + else if (type == 16) { dropItem(world, pt, 263); } // Coal ore else if (type == 18) { // Leaves if (random.nextDouble() > 0.95) { - dropItem(pt, 6); + dropItem(world, pt, 6); } } else if (type == 20) { } // Glass - else if (type == 43) { dropItem(pt, 44); } // Double step + else if (type == 43) { dropItem(world, pt, 44); } // Double step else if (type == 47) { } // Bookshelves else if (type == 51) { } // Fire else if (type == 52) { } // Mob spawner - else if (type == 53) { dropItem(pt, 5); } // Wooden stairs - else if (type == 55) { dropItem(pt, 331); } // Redstone wire - else if (type == 56) { dropItem(pt, 264); } // Diamond ore - else if (type == 59) { dropItem(pt, 295); } // Crops - else if (type == 60) { dropItem(pt, 3); } // Soil - else if (type == 62) { dropItem(pt, 61); } // Furnace - else if (type == 63) { dropItem(pt, 323); } // Sign post - else if (type == 64) { dropItem(pt, 324); } // Wood door - else if (type == 67) { dropItem(pt, 4); } // Cobblestone stairs - else if (type == 68) { dropItem(pt, 323); } // Wall sign - else if (type == 71) { dropItem(pt, 330); } // Iron door - else if (type == 73) { dropItem(pt, 331, 1, 4); } // Redstone ore - else if (type == 74) { dropItem(pt, 331, 1, 4); } // Glowing redstone ore - else if (type == 75) { dropItem(pt, 76); } // Redstone torch + else if (type == 53) { dropItem(world, pt, 5); } // Wooden stairs + else if (type == 55) { dropItem(world, pt, 331); } // Redstone wire + else if (type == 56) { dropItem(world, pt, 264); } // Diamond ore + else if (type == 59) { dropItem(world, pt, 295); } // Crops + else if (type == 60) { dropItem(world, pt, 3); } // Soil + else if (type == 62) { dropItem(world, pt, 61); } // Furnace + else if (type == 63) { dropItem(world, pt, 323); } // Sign post + else if (type == 64) { dropItem(world, pt, 324); } // Wood door + else if (type == 67) { dropItem(world, pt, 4); } // Cobblestone stairs + else if (type == 68) { dropItem(world, pt, 323); } // Wall sign + else if (type == 71) { dropItem(world, pt, 330); } // Iron door + else if (type == 73) { dropItem(world, pt, 331, 1, 4); } // Redstone ore + else if (type == 74) { dropItem(world, pt, 331, 1, 4); } // Glowing redstone ore + else if (type == 75) { dropItem(world, pt, 76); } // Redstone torch else if (type == 78) { } // Snow else if (type == 79) { } // Ice - else if (type == 82) { dropItem(pt, 337, 1, 4); } // Clay - else if (type == 83) { dropItem(pt, 338); } // Reed - else if (type == 89) { dropItem(pt, 348); } // Lightstone + else if (type == 82) { dropItem(world, pt, 337, 1, 4); } // Clay + else if (type == 83) { dropItem(world, pt, 338); } // Reed + else if (type == 89) { dropItem(world, pt, 348); } // Lightstone else if (type == 90) { } // Portal else if (type != 0) { - dropItem(pt, type); + dropItem(world, pt, type); } } @@ -409,7 +409,7 @@ public class HMServerInterface extends ServerInterface { * @param radius * @return */ - public int killMobs(Vector origin, int radius) { + public int killMobs(LocalWorld world, Vector origin, int radius) { int killed = 0; for (Mob mob : etc.getServer().getMobList()) { diff --git a/src/HMWorldEditListener.java b/src/HMWorldEditListener.java index a2acae535..91a9f57a1 100755 --- a/src/HMWorldEditListener.java +++ b/src/HMWorldEditListener.java @@ -17,53 +17,48 @@ * along with this program. If not, see . */ -import java.util.List; -import java.util.ArrayList; import java.util.Map; -import java.util.HashMap; -import java.util.Set; -import java.util.HashSet; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.logging.Handler; -import java.util.logging.FileHandler; -import java.io.*; import com.sk89q.worldedit.*; -import com.sk89q.worldedit.bags.BlockBag; -import com.sk89q.worldedit.blocks.*; -import com.sk89q.worldedit.data.*; -import com.sk89q.worldedit.filters.*; -import com.sk89q.worldedit.snapshots.*; -import com.sk89q.worldedit.regions.*; -import com.sk89q.worldedit.patterns.*; /** - * Plugin base. + * The event listener for WorldEdit in hMod. * * @author sk89q */ public class HMWorldEditListener extends PluginListener { - /** - * Logger. - */ - private static final Logger logger = Logger.getLogger("Minecraft.WorldEdit"); - - /** - * WorldEditLibrary's properties file. - */ - private PropertiesFile properties; - /** * Main WorldEdit controller. */ - private WorldEditController controller = new WorldEditController(); + private WorldEditController controller; + /** + * Configuration. + */ + private LocalConfiguration config; + /** + * A copy of the server instance. This is where all world<->WorldEdit calls + * will go through. + */ + private ServerInterface server; + + /** + * Constructs an instance. + * + * @param server + */ + public HMWorldEditListener(ServerInterface server) { + this.server = server; + + config = new HMConfiguration(); + controller = new WorldEditController(server, config); + } + /** * * @param player */ @Override public void onDisconnect(Player player) { - controller.handleDisconnect(new HMPlayer(player)); + controller.handleDisconnect(wrapPlayer(player)); } /** @@ -72,7 +67,7 @@ public class HMWorldEditListener extends PluginListener { * @param player */ public void onArmSwing(Player player) { - controller.handleArmSwing(new HMPlayer(player)); + controller.handleArmSwing(wrapPlayer(player)); } /** @@ -91,7 +86,7 @@ public class HMWorldEditListener extends PluginListener { Vector pos = new Vector(blockClicked.getX(), blockClicked.getY(), blockClicked.getZ()); - return controller.handleBlockRightClick(new HMPlayer(player), pos); + return controller.handleBlockRightClick(wrapPlayer(player), null, pos); } /** @@ -107,7 +102,7 @@ public class HMWorldEditListener extends PluginListener { Vector pos = new Vector(blockClicked.getX(), blockClicked.getY(), blockClicked.getZ()); - return controller.handleBlockLeftClick(new HMPlayer(player), pos); + return controller.handleBlockLeftClick(wrapPlayer(player), null, pos); } /** @@ -118,81 +113,21 @@ public class HMWorldEditListener extends PluginListener { */ @Override public boolean onCommand(Player player, String[] split) { - return controller.handleCommand(new HMPlayer(player), split); + return controller.handleCommand(wrapPlayer(player), split); } /** * Loads the configuration. */ public void loadConfiguration() { - if (properties == null) { - properties = new PropertiesFile("worldedit.properties"); - } else { - try { - properties.load(); - } catch (IOException e) { - logger.warning("worldedit.properties could not be loaded: " - + e.getMessage()); - } - } - - controller.profile = properties.getBoolean("debug-profile", false); - controller.wandItem = properties.getInt("wand-item", 271); - controller.defaultChangeLimit = Math.max(-1, properties.getInt("default-max-blocks-changed", -1)); - controller.maxChangeLimit = Math.max(-1, properties.getInt("max-blocks-changed", -1)); - controller.maxRadius = Math.max(-1, properties.getInt("max-radius", -1)); - controller.maxSuperPickaxeSize = Math.max(1, properties.getInt("max-super-pickaxe-size", 5)); - controller.registerHelp = properties.getBoolean("register-help", true); - controller.logComands = properties.getBoolean("log-commands", false); - controller.superPickaxeDrop = properties.getBoolean("super-pickaxe-drop-items", true); - controller.superPickaxeManyDrop = properties.getBoolean("super-pickaxe-many-drop-items", false); - controller.noDoubleSlash = properties.getBoolean("no-double-slash", false); - controller.useInventory = properties.getBoolean("use-inventory", false); - controller.useInventoryOverride = properties.getBoolean("use-inventory-override", false); - - // Get allowed blocks - controller.allowedBlocks = new HashSet(); - for (String b : properties.getString("allowed-blocks", - WorldEditController.getDefaultAllowedBlocks()).split(",")) { - try { - controller.allowedBlocks.add(Integer.parseInt(b)); - } catch (NumberFormatException e) { - } - } - - - String snapshotsDir = properties.getString("snapshots-dir", ""); - if (!snapshotsDir.trim().equals("")) { - controller.snapshotRepo = new SnapshotRepository(snapshotsDir); - } else { - controller.snapshotRepo = null; - } - - String type = properties.getString("shell-save-type", "").trim(); - controller.shellSaveType = type.equals("") ? null : type; - - String logFile = properties.getString("log-file", ""); - if (!logFile.equals("")) { - try { - FileHandler handler = new FileHandler(logFile, true); - handler.setFormatter(new LogFormat()); - logger.addHandler(handler); - } catch (IOException e) { - logger.log(Level.WARNING, "Could not use log file " + logFile + ": " - + e.getMessage()); - } - } else { - for (Handler handler : logger.getHandlers()) { - logger.removeHandler(handler); - } - } + config.load(); } /** * Register commands with help. */ public void registerCommands() { - if (controller.registerHelp) { + if (config.registerHelp) { for (Map.Entry entry : controller.getCommands().entrySet()) { etc.getInstance().addCommand(entry.getKey(), entry.getValue()); } @@ -221,7 +156,17 @@ public class HMWorldEditListener extends PluginListener { * @param player * @return */ - public WorldEditSession _bridgeSession(Player player) { - return controller.getBridgeSession(new HMPlayer(player)); + public LocalSession _bridgeSession(Player player) { + return controller.getBridgeSession(wrapPlayer(player)); + } + + /** + * Wrap a hMod player for WorldEdit. + * + * @param player + * @return + */ + private LocalPlayer wrapPlayer(Player player) { + return new HMPlayer(server, player); } } diff --git a/src/WorldEdit.java b/src/WorldEdit.java index b3ef627b9..1b202847c 100644 --- a/src/WorldEdit.java +++ b/src/WorldEdit.java @@ -15,13 +15,11 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . -*/ + */ import java.util.logging.Level; import java.util.logging.Logger; -import com.sk89q.worldedit.ServerInterface; - /** * Entry point for the plugin for hey0's mod. * @@ -31,18 +29,29 @@ public class WorldEdit extends Plugin { /** * Logger. */ - private static final Logger logger = Logger.getLogger("Minecraft.WorldEdit"); - /** - * WorldEditLibrary instance. - */ - private static final HMWorldEditListener listener = new HMWorldEditListener(); + private static final Logger logger = Logger + .getLogger("Minecraft.WorldEdit"); /** - * WorldEdit version, fetched from the .jar's manifest. Used to print the - * WorldEdit version in various places. + * The event listener for WorldEdit an hMod. Configuration and such is + * also loaded here as well, although the core of the WorldEdit is + * actually in com.sk89q.worldedit.WorldEditController and is merely + * loaded by this listener. + */ + private final HMWorldEditListener listener; + + /** + * WorldEdit version, fetched from the .jar's manifest. */ private String version; + /** + * Construct an instance of the plugin. + */ + public WorldEdit() { + listener = new HMWorldEditListener(new HMServerInterface()); + } + /** * Initializes the plugin. */ @@ -62,8 +71,6 @@ public class WorldEdit extends Plugin { PluginListener.Priority.MEDIUM); loader.addListener(PluginLoader.Hook.ARM_SWING, listener, this, PluginListener.Priority.MEDIUM); - - ServerInterface.setup(new HMServerInterface()); logger.log(Level.INFO, "WorldEdit version " + getVersion() + " loaded"); } @@ -88,25 +95,25 @@ public class WorldEdit extends Plugin { /** * Get the CraftBook version. - * + * * @return */ public String getVersion() { if (version != null) { return version; } - + Package p = WorldEdit.class.getPackage(); - + if (p == null) { p = Package.getPackage("com.sk89q.worldedit"); } - + if (p == null) { version = "(unknown)"; } else { version = p.getImplementationVersion(); - + if (version == null) { version = "(unknown)"; } @@ -114,13 +121,4 @@ public class WorldEdit extends Plugin { return version; } - - /** - * Returns the listener. - * - * @return - */ - public HMWorldEditListener getListener() { - return listener; - } } diff --git a/src/com/sk89q/util/StringUtil.java b/src/com/sk89q/util/StringUtil.java new file mode 100644 index 000000000..383542623 --- /dev/null +++ b/src/com/sk89q/util/StringUtil.java @@ -0,0 +1,101 @@ +// $Id$ +/* + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.util; + +/** + * String utilities. + * + * @author sk89q + */ +public class StringUtil { + /** + * Trim a string if it is longer than a certain length. + * + * @param str + * @param len + * @return + */ + public static String trimLength(String str, int len) { + if (str.length() > len) { + return str.substring(0, len); + } + + return str; + } + + /** + * Join an array of strings into a string. + * + * @param str + * @param delimiter + * @param initialIndex + * @return + */ + public static String joinString(String[] str, String delimiter, + int initialIndex) { + if (str.length == 0) { + return ""; + } + StringBuilder buffer = new StringBuilder(str[initialIndex]); + for (int i = initialIndex + 1; i < str.length; i++) { + buffer.append(delimiter).append(str[i]); + } + return buffer.toString(); + } + + /** + * Join an array of strings into a string. + * + * @param str + * @param delimiter + * @param initialIndex + * @return + */ + public static String joinString(Object[] str, String delimiter, + int initialIndex) { + if (str.length == 0) { + return ""; + } + StringBuilder buffer = new StringBuilder(str[initialIndex].toString()); + for (int i = initialIndex + 1; i < str.length; i++) { + buffer.append(delimiter).append(str[i].toString()); + } + return buffer.toString(); + } + + /** + * Join an array of strings into a string. + * + * @param str + * @param delimiter + * @param initialIndex + * @return + */ + public static String joinString(int[] str, String delimiter, + int initialIndex) { + if (str.length == 0) { + return ""; + } + StringBuilder buffer = new StringBuilder(Integer.toString(str[initialIndex])); + for (int i = initialIndex + 1; i < str.length; i++) { + buffer.append(delimiter).append(Integer.toString(str[i])); + } + return buffer.toString(); + } +} diff --git a/src/com/sk89q/worldedit/BlockWorldVector.java b/src/com/sk89q/worldedit/BlockWorldVector.java new file mode 100644 index 000000000..a7c4fe1c1 --- /dev/null +++ b/src/com/sk89q/worldedit/BlockWorldVector.java @@ -0,0 +1,101 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldedit; + +/** + * Extension of Vector that supports being compared as ints (for accuracy). + * + * @author sk89q + */ +public class BlockWorldVector extends WorldVector { + /** + * Construct the Vector object. + * + * @param pt + */ + public BlockWorldVector(WorldVector pt) { + super(pt.getWorld(), pt); + } + + /** + * Construct the Vector object. + * + * @param pt + */ + public BlockWorldVector(LocalWorld world, Vector pt) { + super(world, pt); + } + + /** + * Construct the Vector object. + * + * @param pt + */ + public BlockWorldVector(LocalWorld world, int x, int y, int z) { + super(world, x, y, z); + } + + /** + * Construct the Vector object. + * + * @param pt + */ + public BlockWorldVector(LocalWorld world, float x, float y, float z) { + super(world, x, y, z); + } + + /** + * Construct the Vector object. + * + * @param pt + */ + public BlockWorldVector(LocalWorld world, double x, double y, double z) { + super(world, x, y, z); + } + + /** + * Checks if another object is equivalent. + * + * @param obj + * @return whether the other object is equivalent + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof WorldVector)) { + return false; + } + WorldVector other = (WorldVector)obj; + return (int)other.x == (int)this.x && (int)other.y == (int)this.y + && (int)other.z == (int)this.z; + + } + + /** + * Gets the hash code. + * + * @return hash code + */ + @Override + public int hashCode() { + return (Integer.valueOf((int)x).hashCode() >> 13) ^ + (Integer.valueOf((int)y).hashCode() >> 7) ^ + Integer.valueOf((int)z).hashCode(); + } +} diff --git a/src/com/sk89q/worldedit/CuboidClipboard.java b/src/com/sk89q/worldedit/CuboidClipboard.java index 71a210ed2..c1664ab54 100644 --- a/src/com/sk89q/worldedit/CuboidClipboard.java +++ b/src/com/sk89q/worldedit/CuboidClipboard.java @@ -1,4 +1,3 @@ -package com.sk89q.worldedit; // $Id$ /* * WorldEdit @@ -18,7 +17,8 @@ package com.sk89q.worldedit; * along with this program. If not, see . */ -import com.sk89q.worldedit.*; +package com.sk89q.worldedit; + import com.sk89q.worldedit.blocks.*; import com.sk89q.worldedit.data.*; import org.jnbt.*; diff --git a/src/com/sk89q/worldedit/EditSession.java b/src/com/sk89q/worldedit/EditSession.java index 01fb0a42a..3c8e1d0c4 100755 --- a/src/com/sk89q/worldedit/EditSession.java +++ b/src/com/sk89q/worldedit/EditSession.java @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . -*/ + */ package com.sk89q.worldedit; @@ -29,7 +29,6 @@ import java.util.List; import java.util.ArrayList; import java.util.Collections; import java.util.Random; -import com.sk89q.worldedit.*; import com.sk89q.worldedit.regions.*; import com.sk89q.worldedit.bags.*; import com.sk89q.worldedit.blocks.*; @@ -37,12 +36,12 @@ import com.sk89q.worldedit.patterns.*; /** * This class can wrap all block editing operations into one "edit session" that - * stores the state of the blocks before modification. This allows for easy - * undo or redo. In addition to that, this class can use a "queue mode" that - * will know how to handle some special types of items such as signs and - * torches. For example, torches must be placed only after there is already - * a block below it, otherwise the torch will be placed as an item. - * + * stores the state of the blocks before modification. This allows for easy undo + * or redo. In addition to that, this class can use a "queue mode" that will + * know how to handle some special types of items such as signs and torches. For + * example, torches must be placed only after there is already a block below it, + * otherwise the torch will be placed as an item. + * * @author sk89q */ public class EditSession { @@ -50,36 +49,40 @@ public class EditSession { * Random number generator. */ private static Random prng = new Random(); - + /** * Server interface. */ private ServerInterface server; - + /** + * World. + */ + private LocalWorld world; + /** * Stores the original blocks before modification. */ - private DoubleArrayList original = - new DoubleArrayList(true); + private DoubleArrayList original = new DoubleArrayList( + true); /** * Stores the current blocks. */ - private DoubleArrayList current = - new DoubleArrayList(false); + private DoubleArrayList current = new DoubleArrayList( + false); /** * Blocks that should be placed before last. */ - private DoubleArrayList queueAfter = - new DoubleArrayList(false); + private DoubleArrayList queueAfter = new DoubleArrayList( + false); /** * Blocks that should be placed last. */ - private DoubleArrayList queueLast = - new DoubleArrayList(false); + private DoubleArrayList queueLast = new DoubleArrayList( + false); /** * The maximum number of blocks to change at a time. If this number is - * exceeded, a MaxChangedBlocksException exception will be - * raised. -1 indicates no limit. + * exceeded, a MaxChangedBlocksException exception will be raised. -1 + * indicates no limit. */ private int maxBlocks = -1; /** @@ -98,25 +101,38 @@ public class EditSession { /** * Construct the object with a maximum number of blocks. + * + * @param server + * @param world + * @param maxBlocks */ - public EditSession(int maxBlocks) { + public EditSession(ServerInterface server, LocalWorld world, int maxBlocks) { if (maxBlocks < -1) { throw new IllegalArgumentException("Max blocks must be >= -1"); } + this.maxBlocks = maxBlocks; - server = ServerInterface.getInstance(); + this.server = server; + this.world = world; } /** * Construct the object with a maximum number of blocks and a block bag. + * + * @param server + * @param maxBlocks + * @blockBag */ - public EditSession(int maxBlocks, BlockBag blockBag) { + public EditSession(ServerInterface server, LocalWorld world, int maxBlocks, + BlockBag blockBag) { if (maxBlocks < -1) { throw new IllegalArgumentException("Max blocks must be >= -1"); } + this.maxBlocks = maxBlocks; this.blockBag = blockBag; - server = ServerInterface.getInstance(); + this.server = server; + this.world = world; } /** @@ -133,15 +149,15 @@ public class EditSession { } // Clear the chest so that it doesn't drop items - if (server.getBlockType(pt) == 54 && blockBag == null) { - server.clearChest(pt); + if (server.getBlockType(world, pt) == 54 && blockBag == null) { + server.clearChest(world, pt); } int id = block.getID(); - + if (blockBag != null) { - int existing = server.getBlockType(pt); - + int existing = server.getBlockType(world, pt); + if (id > 0) { try { blockBag.fetchPlacedBlock(id); @@ -160,52 +176,52 @@ public class EditSession { } } } - - boolean result = server.setBlockType(pt, id); + + boolean result = server.setBlockType(world, pt, id); if (id != 0) { if (BlockType.usesData(id)) { - server.setBlockData(pt, block.getData()); + server.setBlockData(world, pt, block.getData()); } // Signs if (block instanceof SignBlock) { - SignBlock signBlock = (SignBlock)block; + SignBlock signBlock = (SignBlock) block; String[] text = signBlock.getText(); - server.setSignText(pt, text); - // Chests + server.setSignText(world, pt, text); + // Chests } else if (block instanceof ChestBlock && blockBag == null) { - ChestBlock chestBlock = (ChestBlock)block; - server.setChestContents(pt, chestBlock.getItems()); - // Mob spawners + ChestBlock chestBlock = (ChestBlock) block; + server.setChestContents(world, pt, chestBlock.getItems()); + // Mob spawners } else if (block instanceof MobSpawnerBlock) { - MobSpawnerBlock mobSpawnerblock = (MobSpawnerBlock)block; - server.setMobSpawnerType(pt, mobSpawnerblock.getMobType()); + MobSpawnerBlock mobSpawnerblock = (MobSpawnerBlock) block; + server.setMobSpawnerType(world, pt, mobSpawnerblock.getMobType()); } } - + return result; } /** * Sets the block at position x, y, z with a block type. If queue mode is - * enabled, blocks may not be actually set in world until flushQueue() - * is called. - * + * enabled, blocks may not be actually set in world until flushQueue() is + * called. + * * @param pt * @param block * @return Whether the block changed -- not entirely dependable */ public boolean setBlock(Vector pt, BaseBlock block) - throws MaxChangedBlocksException { + throws MaxChangedBlocksException { BlockVector blockPt = pt.toBlockVector(); - - //if (!original.containsKey(blockPt)) { - original.put(blockPt, getBlock(pt)); - if (maxBlocks != -1 && original.size() > maxBlocks) { - throw new MaxChangedBlocksException(maxBlocks); - } - //} + // if (!original.containsKey(blockPt)) { + original.put(blockPt, getBlock(pt)); + + if (maxBlocks != -1 && original.size() > maxBlocks) { + throw new MaxChangedBlocksException(maxBlocks); + } + // } current.put(pt.toBlockVector(), block); @@ -242,7 +258,7 @@ public class EditSession { if (BlockType.shouldPlaceLast(block.getID())) { queueLast.put(pt.toBlockVector(), block); return getBlock(pt).getID() != block.getID(); - // Destroy torches, etc. first + // Destroy torches, etc. first } else if (BlockType.shouldPlaceLast(getBlock(pt).getID())) { rawSetBlock(pt, new BaseBlock(0)); } else { @@ -256,7 +272,7 @@ public class EditSession { /** * Gets the block type at a position x, y, z. - * + * * @param pt * @return Block type */ @@ -264,38 +280,39 @@ public class EditSession { // In the case of the queue, the block may have not actually been // changed yet if (queued) { - /*BlockVector blockPt = pt.toBlockVector(); - - if (current.containsKey(blockPt)) { - return current.get(blockPt); - }*/ + /* + * BlockVector blockPt = pt.toBlockVector(); + * + * if (current.containsKey(blockPt)) { return current.get(blockPt); + * } + */ } - + return rawGetBlock(pt); } /** * Gets the block type at a position x, y, z. - * + * * @param pt * @return BaseBlock */ public BaseBlock rawGetBlock(Vector pt) { - int type = server.getBlockType(pt); - int data = server.getBlockData(pt); + int type = server.getBlockType(world, pt); + int data = server.getBlockData(world, pt); // Sign if (type == 63 || type == 68) { - String[] text = server.getSignText(pt); + String[] text = server.getSignText(world, pt); return new SignBlock(type, data, text); - // Chest + // Chest } else if (type == 54) { - BaseItemStack[] items = - server.getChestContents(pt); + BaseItemStack[] items = server.getChestContents(world, pt); return new ChestBlock(data, items); - // Mob spawner + // Mob spawner } else if (type == 52) { - return new MobSpawnerBlock(data, server.getMobSpawnerType(pt)); + return new MobSpawnerBlock(data, + server.getMobSpawnerType(world, pt)); } else { return new BaseBlock(type, data); } @@ -305,9 +322,9 @@ public class EditSession { * Restores all blocks to their initial state. */ public void undo() { - for (Map.Entry entry : original) { - BlockVector pt = (BlockVector)entry.getKey(); - smartSetBlock(pt, (BaseBlock)entry.getValue()); + for (Map.Entry entry : original) { + BlockVector pt = (BlockVector) entry.getKey(); + smartSetBlock(pt, (BaseBlock) entry.getValue()); } flushQueue(); } @@ -316,9 +333,9 @@ public class EditSession { * Sets to new state. */ public void redo() { - for (Map.Entry entry : current) { - BlockVector pt = (BlockVector)entry.getKey(); - smartSetBlock(pt, (BaseBlock)entry.getValue()); + for (Map.Entry entry : current) { + BlockVector pt = (BlockVector) entry.getKey(); + smartSetBlock(pt, (BaseBlock) entry.getValue()); } flushQueue(); } @@ -332,9 +349,9 @@ public class EditSession { } /** - * Get the maximum number of blocks that can be changed. -1 will be - * returned if disabled. - * + * Get the maximum number of blocks that can be changed. -1 will be returned + * if disabled. + * * @return block change limit */ public int getBlockChangeLimit() { @@ -344,7 +361,8 @@ public class EditSession { /** * Set the maximum number of blocks that can be changed. * - * @param maxBlocks -1 to disable + * @param maxBlocks + * -1 to disable */ public void setBlockChangeLimit(int maxBlocks) { if (maxBlocks < -1) { @@ -383,19 +401,21 @@ public class EditSession { * Finish off the queue. */ public void flushQueue() { - if (!queued) { return; } + if (!queued) { + return; + } - for (Map.Entry entry : queueAfter) { - BlockVector pt = (BlockVector)entry.getKey(); - rawSetBlock(pt, (BaseBlock)entry.getValue()); + for (Map.Entry entry : queueAfter) { + BlockVector pt = (BlockVector) entry.getKey(); + rawSetBlock(pt, (BaseBlock) entry.getValue()); } // We don't want to place these blocks if other blocks were missing // because it might cause the items to drop if (blockBag == null || missingBlocks.size() == 0) { - for (Map.Entry entry : queueLast) { - BlockVector pt = (BlockVector)entry.getKey(); - rawSetBlock(pt, (BaseBlock)entry.getValue()); + for (Map.Entry entry : queueLast) { + BlockVector pt = (BlockVector) entry.getKey(); + rawSetBlock(pt, (BaseBlock) entry.getValue()); } } @@ -405,7 +425,7 @@ public class EditSession { /** * Fills an area recursively in the X/Z directions. - * + * * @param origin * @param block * @param radius @@ -413,10 +433,9 @@ public class EditSession { * @param recursive * @return number of blocks affected */ - public int fillXZ(Vector origin, BaseBlock block, - int radius, int depth, boolean recursive) - throws MaxChangedBlocksException { - + public int fillXZ(Vector origin, BaseBlock block, int radius, int depth, + boolean recursive) throws MaxChangedBlocksException { + int affected = 0; int originX = origin.getBlockX(); int originY = origin.getBlockY(); @@ -481,7 +500,7 @@ public class EditSession { /** * Recursively fills a block and below until it hits another block. - * + * * @param x * @param cy * @param z @@ -491,12 +510,12 @@ public class EditSession { * @return */ private int fillY(int x, int cy, int z, BaseBlock block, int minY) - throws MaxChangedBlocksException { + throws MaxChangedBlocksException { int affected = 0; for (int y = cy; y >= minY; y--) { Vector pt = new Vector(x, y, z); - + if (getBlock(pt).isAir()) { setBlock(pt, block); affected++; @@ -510,7 +529,7 @@ public class EditSession { /** * Fills an area recursively in the X/Z directions. - * + * * @param origin * @param pattern * @param radius @@ -518,9 +537,8 @@ public class EditSession { * @param recursive * @return number of blocks affected */ - public int fillXZ(Vector origin, Pattern pattern, - int radius, int depth, boolean recursive) - throws MaxChangedBlocksException { + public int fillXZ(Vector origin, Pattern pattern, int radius, int depth, + boolean recursive) throws MaxChangedBlocksException { int affected = 0; int originX = origin.getBlockX(); @@ -586,7 +604,7 @@ public class EditSession { /** * Recursively fills a block and below until it hits another block. - * + * * @param x * @param cy * @param z @@ -596,7 +614,7 @@ public class EditSession { * @return */ private int fillY(int x, int cy, int z, Pattern pattern, int minY) - throws MaxChangedBlocksException { + throws MaxChangedBlocksException { int affected = 0; for (int y = cy; y >= minY; y--) { @@ -621,8 +639,8 @@ public class EditSession { * @param height * @return number of blocks affected */ - public int removeAbove(Vector pos, int size, int height) throws - MaxChangedBlocksException { + public int removeAbove(Vector pos, int size, int height) + throws MaxChangedBlocksException { int maxY = Math.min(127, pos.getBlockY() + height - 1); size--; int affected = 0; @@ -649,14 +667,14 @@ public class EditSession { /** * Remove blocks below. - * + * * @param pos * @param size * @param height * @return number of blocks affected */ - public int removeBelow(Vector pos, int size, int height) throws - MaxChangedBlocksException { + public int removeBelow(Vector pos, int size, int height) + throws MaxChangedBlocksException { int minY = Math.max(0, pos.getBlockY() - height); size--; int affected = 0; @@ -683,14 +701,14 @@ public class EditSession { /** * Remove nearby blocks of a type. - * + * * @param pos * @param blockType * @param size * @return number of blocks affected */ - public int removeNear(Vector pos, int blockType, int size) throws - MaxChangedBlocksException { + public int removeNear(Vector pos, int blockType, int size) + throws MaxChangedBlocksException { int affected = 0; BaseBlock air = new BaseBlock(0); @@ -713,7 +731,7 @@ public class EditSession { /** * Sets all the blocks inside a region to a certain block type. - * + * * @param region * @param block * @return number of blocks affected @@ -759,7 +777,7 @@ public class EditSession { /** * Sets all the blocks inside a region to a certain block type. - * + * * @param region * @param block * @return number of blocks affected @@ -805,15 +823,16 @@ public class EditSession { /** * Replaces all the blocks of a type inside a region to another block type. - * + * * @param region - * @param fromBlockType -1 for non-air + * @param fromBlockType + * -1 for non-air * @param toBlockType * @return number of blocks affected * @throws MaxChangedBlocksException */ - public int replaceBlocks(Region region, Set fromBlockTypes, BaseBlock toBlock) - throws MaxChangedBlocksException { + public int replaceBlocks(Region region, Set fromBlockTypes, + BaseBlock toBlock) throws MaxChangedBlocksException { int affected = 0; if (region instanceof CuboidRegion) { @@ -834,9 +853,9 @@ public class EditSession { Vector pt = new Vector(x, y, z); int curBlockType = getBlock(pt).getID(); - if ((fromBlockTypes == null && curBlockType != 0) || - (fromBlockTypes != null && - fromBlockTypes.contains(curBlockType))) { + if ((fromBlockTypes == null && curBlockType != 0) + || (fromBlockTypes != null && fromBlockTypes + .contains(curBlockType))) { if (setBlock(pt, toBlock)) { affected++; } @@ -848,8 +867,8 @@ public class EditSession { for (Vector pt : region) { int curBlockType = getBlock(pt).getID(); - if (fromBlockTypes == null && curBlockType != 0 || - fromBlockTypes.contains(curBlockType)) { + if (fromBlockTypes == null && curBlockType != 0 + || fromBlockTypes.contains(curBlockType)) { if (setBlock(pt, toBlock)) { affected++; } @@ -862,16 +881,16 @@ public class EditSession { /** * Replaces all the blocks of a type inside a region to another block type. - * + * * @param region - * @param fromBlockType -1 for non-air + * @param fromBlockType + * -1 for non-air * @param pattern * @return number of blocks affected * @throws MaxChangedBlocksException */ public int replaceBlocks(Region region, Set fromBlockTypes, - Pattern pattern) - throws MaxChangedBlocksException { + Pattern pattern) throws MaxChangedBlocksException { int affected = 0; if (region instanceof CuboidRegion) { @@ -892,9 +911,9 @@ public class EditSession { Vector pt = new Vector(x, y, z); int curBlockType = getBlock(pt).getID(); - if ((fromBlockTypes == null && curBlockType != 0) || - (fromBlockTypes != null && - fromBlockTypes.contains(curBlockType))) { + if ((fromBlockTypes == null && curBlockType != 0) + || (fromBlockTypes != null && fromBlockTypes + .contains(curBlockType))) { if (setBlock(pt, pattern.next(pt))) { affected++; } @@ -906,8 +925,8 @@ public class EditSession { for (Vector pt : region) { int curBlockType = getBlock(pt).getID(); - if (fromBlockTypes == null && curBlockType != 0 || - fromBlockTypes.contains(curBlockType)) { + if (fromBlockTypes == null && curBlockType != 0 + || fromBlockTypes.contains(curBlockType)) { if (setBlock(pt, pattern.next(pt))) { affected++; } @@ -920,7 +939,7 @@ public class EditSession { /** * Make faces of the region (as if it was a cuboid if it's not). - * + * * @param region * @param block * @return number of blocks affected @@ -942,23 +961,35 @@ public class EditSession { for (int x = minX; x <= maxX; x++) { for (int y = minY; y <= maxY; y++) { - if (setBlock(new Vector(x, y, minZ), block)) { affected++; } - if (setBlock(new Vector(x, y, maxZ), block)) { affected++; } + if (setBlock(new Vector(x, y, minZ), block)) { + affected++; + } + if (setBlock(new Vector(x, y, maxZ), block)) { + affected++; + } affected++; } } for (int y = minY; y <= maxY; y++) { for (int z = minZ; z <= maxZ; z++) { - if (setBlock(new Vector(minX, y, z), block)) { affected++; } - if (setBlock(new Vector(maxX, y, z), block)) { affected++; } + if (setBlock(new Vector(minX, y, z), block)) { + affected++; + } + if (setBlock(new Vector(maxX, y, z), block)) { + affected++; + } } } for (int z = minZ; z <= maxZ; z++) { for (int x = minX; x <= maxX; x++) { - if (setBlock(new Vector(x, minY, z), block)) { affected++; } - if (setBlock(new Vector(x, maxY, z), block)) { affected++; } + if (setBlock(new Vector(x, minY, z), block)) { + affected++; + } + if (setBlock(new Vector(x, maxY, z), block)) { + affected++; + } } } @@ -967,7 +998,7 @@ public class EditSession { /** * Make walls of the region (as if it was a cuboid if it's not). - * + * * @param region * @param block * @return number of blocks affected @@ -989,16 +1020,24 @@ public class EditSession { for (int x = minX; x <= maxX; x++) { for (int y = minY; y <= maxY; y++) { - if (setBlock(new Vector(x, y, minZ), block)) { affected++; } - if (setBlock(new Vector(x, y, maxZ), block)) { affected++; } + if (setBlock(new Vector(x, y, minZ), block)) { + affected++; + } + if (setBlock(new Vector(x, y, maxZ), block)) { + affected++; + } affected++; } } for (int y = minY; y <= maxY; y++) { for (int z = minZ; z <= maxZ; z++) { - if (setBlock(new Vector(minX, y, z), block)) { affected++; } - if (setBlock(new Vector(maxX, y, z), block)) { affected++; } + if (setBlock(new Vector(minX, y, z), block)) { + affected++; + } + if (setBlock(new Vector(maxX, y, z), block)) { + affected++; + } } } @@ -1017,9 +1056,9 @@ public class EditSession { throws MaxChangedBlocksException { Vector min = region.getMinimumPoint(); Vector max = region.getMaximumPoint(); - + int upperY = Math.min(127, max.getBlockY() + 1); - int lowerY = Math.max(0, min.getBlockY()- 1); + int lowerY = Math.max(0, min.getBlockY() - 1); int affected = 0; @@ -1032,7 +1071,7 @@ public class EditSession { for (int z = minZ; z <= maxZ; z++) { for (int y = upperY; y >= lowerY; y--) { Vector above = new Vector(x, y + 1, z); - + if (y + 1 <= 127 && !getBlock(new Vector(x, y, z)).isAir() && getBlock(above).isAir()) { if (setBlock(above, block)) { @@ -1057,9 +1096,8 @@ public class EditSession { * @return number of blocks affected * @throws MaxChangedBlocksException */ - public int stackCuboidRegion(Region region, Vector dir, - int count, boolean copyAir) - throws MaxChangedBlocksException { + public int stackCuboidRegion(Region region, Vector dir, int count, + boolean copyAir) throws MaxChangedBlocksException { int affected = 0; Vector min = region.getMinimumPoint(); @@ -1071,7 +1109,7 @@ public class EditSession { int maxX = max.getBlockX(); int maxY = max.getBlockY(); int maxZ = max.getBlockZ(); - + int xs = region.getWidth(); int ys = region.getHeight(); int zs = region.getLength(); @@ -1083,11 +1121,10 @@ public class EditSession { if (!block.isAir() || copyAir) { for (int i = 1; i <= count; i++) { - Vector pos = new Vector( - x + xs * dir.getBlockX() * i, - y + ys * dir.getBlockY() * i, - z + zs * dir.getBlockZ() * i); - + Vector pos = new Vector(x + xs * dir.getBlockX() + * i, y + ys * dir.getBlockY() * i, z + zs + * dir.getBlockZ() * i); + if (setBlock(pos, block)) { affected++; } @@ -1102,7 +1139,7 @@ public class EditSession { /** * Move a cuboid region. - * + * * @param region * @param dir * @param distance @@ -1111,8 +1148,8 @@ public class EditSession { * @return number of blocks moved * @throws MaxChangedBlocksException */ - public int moveCuboidRegion(Region region, Vector dir, - int distance, boolean copyAir, BaseBlock replace) + public int moveCuboidRegion(Region region, Vector dir, int distance, + boolean copyAir, BaseBlock replace) throws MaxChangedBlocksException { int affected = 0; @@ -1130,7 +1167,7 @@ public class EditSession { Vector newMin = min.add(shift); Vector newMax = min.add(shift); - Map delayed = new LinkedHashMap(); + Map delayed = new LinkedHashMap(); for (int x = minX; x <= maxX; x++) { for (int z = minZ; z <= maxZ; z++) { @@ -1146,8 +1183,10 @@ public class EditSession { // Don't want to replace the old block if it's in // the new area if (x >= newMin.getBlockX() && x <= newMax.getBlockX() - && y >= newMin.getBlockY() && y <= newMax.getBlockY() - && z >= newMin.getBlockZ() && z <= newMax.getBlockZ()) { + && y >= newMin.getBlockY() + && y <= newMax.getBlockY() + && z >= newMin.getBlockZ() + && z <= newMax.getBlockZ()) { } else { setBlock(pos, replace); } @@ -1156,7 +1195,7 @@ public class EditSession { } } - for (Map.Entry entry : delayed.entrySet()) { + for (Map.Entry entry : delayed.entrySet()) { setBlock(entry.getKey(), entry.getValue()); affected++; } @@ -1166,13 +1205,14 @@ public class EditSession { /** * Drain nearby pools of water or lava. - * + * * @param pos * @param radius * @return number of blocks affected * @throws MaxChangedBlocksException */ - public int drainArea(Vector pos, int radius) throws MaxChangedBlocksException { + public int drainArea(Vector pos, int radius) + throws MaxChangedBlocksException { int affected = 0; HashSet visited = new HashSet(); @@ -1230,7 +1270,7 @@ public class EditSession { /** * Level water. - * + * * @param pos * @param radius * @return number of blocks affected @@ -1303,10 +1343,10 @@ public class EditSession { * @param block * @throws MaxChangedBlocksException */ - private int makeHCylinderPoints(Vector center, int x, int z, - int height, BaseBlock block) throws MaxChangedBlocksException { + private int makeHCylinderPoints(Vector center, int x, int z, int height, + BaseBlock block) throws MaxChangedBlocksException { int affected = 0; - + if (x == 0) { for (int y = 0; y < height; y++) { setBlock(center.add(0, y, z), block); @@ -1350,8 +1390,8 @@ public class EditSession { * @return number of blocks set * @throws MaxChangedBlocksException */ - public int makeHollowCylinder(Vector pos, BaseBlock block, - int radius, int height) throws MaxChangedBlocksException { + public int makeHollowCylinder(Vector pos, BaseBlock block, int radius, + int height) throws MaxChangedBlocksException { int x = 0; int z = radius; int d = (5 - radius * 4) / 4; @@ -1374,7 +1414,7 @@ public class EditSession { while (x < z) { x++; - + if (d >= 0) { z--; d += 2 * (x - z) + 1; @@ -1390,7 +1430,7 @@ public class EditSession { /** * Helper method to draw the cylinder. - * + * * @param center * @param x * @param z @@ -1398,8 +1438,8 @@ public class EditSession { * @param block * @throws MaxChangedBlocksException */ - private int makeCylinderPoints(Vector center, int x, int z, - int height, BaseBlock block) throws MaxChangedBlocksException { + private int makeCylinderPoints(Vector center, int x, int z, int height, + BaseBlock block) throws MaxChangedBlocksException { int affected = 0; if (x == z) { @@ -1437,8 +1477,8 @@ public class EditSession { * @return number of blocks set * @throws MaxChangedBlocksException */ - public int makeCylinder(Vector pos, BaseBlock block, - int radius, int height) throws MaxChangedBlocksException { + public int makeCylinder(Vector pos, BaseBlock block, int radius, int height) + throws MaxChangedBlocksException { int x = 0; int z = radius; int d = (5 - radius * 4) / 4; @@ -1477,7 +1517,7 @@ public class EditSession { /** * Makes a sphere. - * + * * @param pos * @param block * @param radius @@ -1485,10 +1525,10 @@ public class EditSession { * @return number of blocks changed * @throws MaxChangedBlocksException */ - public int makeSphere(Vector pos, BaseBlock block, int radius, boolean filled) - throws MaxChangedBlocksException { + public int makeSphere(Vector pos, BaseBlock block, int radius, + boolean filled) throws MaxChangedBlocksException { int affected = 0; - + for (int x = 0; x <= radius; x++) { for (int y = 0; y <= radius; y++) { for (int z = 0; z <= radius; z++) { @@ -1496,14 +1536,30 @@ public class EditSession { double d = vec.distance(pos); if (d <= radius + 0.5 && (filled || d >= radius - 0.5)) { - if (setBlock(vec, block)) { affected++; } - if (setBlock(pos.add(-x, y, z), block)) { affected++; } - if (setBlock(pos.add(x, -y, z), block)) { affected++; } - if (setBlock(pos.add(x, y, -z), block)) { affected++; } - if (setBlock(pos.add(-x, -y, z), block)) { affected++; } - if (setBlock(pos.add(x, -y, -z), block)) { affected++; } - if (setBlock(pos.add(-x, y, -z), block)) { affected++; } - if (setBlock(pos.add(-x, -y, -z), block)) { affected++; } + if (setBlock(vec, block)) { + affected++; + } + if (setBlock(pos.add(-x, y, z), block)) { + affected++; + } + if (setBlock(pos.add(x, -y, z), block)) { + affected++; + } + if (setBlock(pos.add(x, y, -z), block)) { + affected++; + } + if (setBlock(pos.add(-x, -y, z), block)) { + affected++; + } + if (setBlock(pos.add(x, -y, -z), block)) { + affected++; + } + if (setBlock(pos.add(-x, y, -z), block)) { + affected++; + } + if (setBlock(pos.add(-x, -y, -z), block)) { + affected++; + } } } } @@ -1514,7 +1570,7 @@ public class EditSession { /** * Make snow. - * + * * @param pos * @param radius * @return number of blocks affected @@ -1536,7 +1592,7 @@ public class EditSession { if ((new Vector(x, oy, z)).distance(pos) > radius) { continue; } - + for (int y = 127; y >= 1; y--) { Vector pt = new Vector(x, y, z); int id = getBlock(pt).getID(); @@ -1555,8 +1611,8 @@ public class EditSession { || id == 53 // Wood steps || id == 55 // Redstone wire || id == 59 // Crops - || (id >= 63 && id <= 72) - || id == 75 // Redstone torch + || (id >= 63 && id <= 72) || id == 75 // Redstone + // torch || id == 76 // Redstone torch || id == 77 // Stone button || id == 78 // Snow @@ -1567,7 +1623,7 @@ public class EditSession { || id == 90) { // Portal break; } - + // Ice! if (id == 8 || id == 9) { if (setBlock(pt, ice)) { @@ -1581,7 +1637,7 @@ public class EditSession { if (y == 127) { // Too high! break; } - + if (setBlock(pt.add(0, 1, 0), snow)) { affected++; } @@ -1593,13 +1649,14 @@ public class EditSession { return affected; } - + /** * Set a block by chance. * * @param pos * @param block - * @param c 0-1 chance + * @param c + * 0-1 chance * @return whether a block was changed */ private boolean setChanceBlockIfAir(Vector pos, BaseBlock block, double c) @@ -1612,15 +1669,15 @@ public class EditSession { /** * Makes a pumpkin patch. - * + * * @param basePos */ private void makePumpkinPatch(Vector basePos) throws MaxChangedBlocksException { - //BaseBlock logBlock = new BaseBlock(17); + // BaseBlock logBlock = new BaseBlock(17); BaseBlock leavesBlock = new BaseBlock(18); - //setBlock(basePos.subtract(0, 1, 0), logBlock); + // setBlock(basePos.subtract(0, 1, 0), logBlock); setBlockIfAir(basePos, leavesBlock); makePumpkinPatchVine(basePos, basePos.add(0, 0, 1)); @@ -1637,8 +1694,10 @@ public class EditSession { */ private void makePumpkinPatchVine(Vector basePos, Vector pos) throws MaxChangedBlocksException { - if (pos.distance(basePos) > 4) return; - if (getBlock(pos).getID() != 0) return; + if (pos.distance(basePos) > 4) + return; + if (getBlock(pos).getID() != 0) + return; for (int i = -1; i > -3; i--) { Vector testPos = pos.add(0, i, 0); @@ -1655,27 +1714,35 @@ public class EditSession { int h = prng.nextInt(3) - 1; if (t == 0) { - if (prng.nextBoolean()) makePumpkinPatchVine(basePos, pos.add(1, 0, 0)); - if (prng.nextBoolean()) setBlockIfAir(pos.add(1, h, -1), new BaseBlock(18)); + if (prng.nextBoolean()) + makePumpkinPatchVine(basePos, pos.add(1, 0, 0)); + if (prng.nextBoolean()) + setBlockIfAir(pos.add(1, h, -1), new BaseBlock(18)); setBlockIfAir(pos.add(0, 0, -1), new BaseBlock(86)); } else if (t == 1) { - if (prng.nextBoolean()) makePumpkinPatchVine(basePos, pos.add(0, 0, 1)); - if (prng.nextBoolean()) setBlockIfAir(pos.add(1, h, 0), new BaseBlock(18)); + if (prng.nextBoolean()) + makePumpkinPatchVine(basePos, pos.add(0, 0, 1)); + if (prng.nextBoolean()) + setBlockIfAir(pos.add(1, h, 0), new BaseBlock(18)); setBlockIfAir(pos.add(1, 0, 1), new BaseBlock(86)); } else if (t == 2) { - if (prng.nextBoolean()) makePumpkinPatchVine(basePos, pos.add(0, 0, -1)); - if (prng.nextBoolean()) setBlockIfAir(pos.add(-1, h, 0), new BaseBlock(18)); + if (prng.nextBoolean()) + makePumpkinPatchVine(basePos, pos.add(0, 0, -1)); + if (prng.nextBoolean()) + setBlockIfAir(pos.add(-1, h, 0), new BaseBlock(18)); setBlockIfAir(pos.add(-1, 0, 1), new BaseBlock(86)); } else if (t == 3) { - if (prng.nextBoolean()) makePumpkinPatchVine(basePos, pos.add(-1, 0, 0)); - if (prng.nextBoolean()) setBlockIfAir(pos.add(-1, h, -1), new BaseBlock(18)); + if (prng.nextBoolean()) + makePumpkinPatchVine(basePos, pos.add(-1, 0, 0)); + if (prng.nextBoolean()) + setBlockIfAir(pos.add(-1, h, -1), new BaseBlock(18)); setBlockIfAir(pos.add(-1, 0, -1), new BaseBlock(86)); } } /** * Makes pumpkin patches. - * + * * @param basePos * @param size * @return number of trees created @@ -1684,13 +1751,17 @@ public class EditSession { throws MaxChangedBlocksException { int affected = 0; - for (int x = basePos.getBlockX() - size; x <= basePos.getBlockX() + size; x++) { - for (int z = basePos.getBlockZ() - size; z <= basePos.getBlockZ() + size; z++) { + for (int x = basePos.getBlockX() - size; x <= basePos.getBlockX() + + size; x++) { + for (int z = basePos.getBlockZ() - size; z <= basePos.getBlockZ() + + size; z++) { // Don't want to be in the ground if (!getBlock(new Vector(x, basePos.getBlockY(), z)).isAir()) continue; // The gods don't want a pumpkin patch here - if (Math.random() < 0.98) { continue; } + if (Math.random() < 0.98) { + continue; + } for (int y = basePos.getBlockY(); y >= basePos.getBlockY() - 10; y--) { // Check if we hit the ground @@ -1721,14 +1792,18 @@ public class EditSession { public int makeForest(Vector basePos, int size, double density, boolean pineTree) throws MaxChangedBlocksException { int affected = 0; - - for (int x = basePos.getBlockX() - size; x <= basePos.getBlockX() + size; x++) { - for (int z = basePos.getBlockZ() - size; z <= basePos.getBlockZ() + size; z++) { + + for (int x = basePos.getBlockX() - size; x <= basePos.getBlockX() + + size; x++) { + for (int z = basePos.getBlockZ() - size; z <= basePos.getBlockZ() + + size; z++) { // Don't want to be in the ground if (!getBlock(new Vector(x, basePos.getBlockY(), z)).isAir()) continue; // The gods don't want a tree here - if (Math.random() >= density) { continue; } // def 0.05 + if (Math.random() >= density) { + continue; + } // def 0.05 for (int y = basePos.getBlockY(); y >= basePos.getBlockY() - 10; y--) { // Check if we hit the ground @@ -1737,7 +1812,8 @@ public class EditSession { if (pineTree) { makePineTree(new Vector(x, y + 1, z)); } else { - server.generateTree(this, new Vector(x, y + 1, z)); + server.generateTree(this, world, + new Vector(x, y + 1, z)); } affected++; break; @@ -1753,13 +1829,12 @@ public class EditSession { /** * Makes a terrible looking pine tree. - * + * * @param basePos */ - private void makePineTree(Vector basePos) - throws MaxChangedBlocksException { - int trunkHeight = (int)Math.floor(Math.random() * 2) + 3; - int height = (int)Math.floor(Math.random() * 5) + 8; + private void makePineTree(Vector basePos) throws MaxChangedBlocksException { + int trunkHeight = (int) Math.floor(Math.random() * 2) + 3; + int height = (int) Math.floor(Math.random() * 5) + 8; BaseBlock logBlock = new BaseBlock(17); BaseBlock leavesBlock = new BaseBlock(18); @@ -1812,7 +1887,7 @@ public class EditSession { /** * Count the number of blocks of a list of types in a region. - * + * * @param region * @param searchIDs * @return @@ -1861,10 +1936,8 @@ public class EditSession { * @return */ public List> getBlockDistribution(Region region) { - List> distribution - = new ArrayList>(); - Map> map = - new HashMap>(); + List> distribution = new ArrayList>(); + Map> map = new HashMap>(); if (region instanceof CuboidRegion) { // Doing this for speed @@ -1909,23 +1982,25 @@ public class EditSession { } Collections.sort(distribution); - //Collections.reverse(distribution); + // Collections.reverse(distribution); return distribution; } - + /** * Returns the highest solid 'terrain' block which can occur naturally. * Looks at: 1, 2, 3, 7, 12, 13, 14, 15, 16, 56, 73, 74, 87, 88, 89 * * @param x * @param z - * @param minY minimal height - * @param maxY maximal height + * @param minY + * minimal height + * @param maxY + * maximal height * @return height of highest block found or 'minY' */ - public int getHighestTerrainBlock( int x , int z, int minY, int maxY) { + public int getHighestTerrainBlock(int x, int z, int minY, int maxY) { for (int y = maxY; y >= minY; y--) { Vector pt = new Vector(x, y, z); int id = getBlock(pt).getID(); @@ -1953,7 +2028,7 @@ public class EditSession { } return minY; } - + /** * Gets the list of missing blocks and clears the list for the next * operation. @@ -1974,7 +2049,8 @@ public class EditSession { } /** - * @param blockBag the blockBag to set + * @param blockBag + * the blockBag to set */ public void setBlockBag(BlockBag blockBag) { this.blockBag = blockBag; diff --git a/src/com/sk89q/worldedit/LocalConfiguration.java b/src/com/sk89q/worldedit/LocalConfiguration.java new file mode 100644 index 000000000..beb29d238 --- /dev/null +++ b/src/com/sk89q/worldedit/LocalConfiguration.java @@ -0,0 +1,59 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldedit; + +import java.util.Set; + +import com.sk89q.worldedit.snapshots.SnapshotRepository; + +/** + * Represents WorldEdit's configuration. + * + * @author sk89q + */ +public abstract class LocalConfiguration { + protected static final int[] defaultDisallowedBlocks = new int[] { + 6, 7, 14, 15, 16, 21, 22, 23, 24, 25, 26, 27, 28, 29, 39, 31, + 32, 33, 34, 36, 37, 38, 39, 40, 46, 50, 51, 56, 59, 69, 73, 74, + 75, 76, 77, 81, 83 + }; + + public boolean profile = false; + public Set disallowedBlocks = null; + public int defaultChangeLimit = -1; + public int maxChangeLimit = -1; + public String shellSaveType = null; + public SnapshotRepository snapshotRepo = null; + public int maxRadius = -1; + public int maxSuperPickaxeSize = 5; + public boolean logComands = false; + public boolean registerHelp = true; + public int wandItem = 271; + public boolean superPickaxeDrop = true; + public boolean superPickaxeManyDrop = true; + public boolean noDoubleSlash = false; + public boolean useInventory = false; + public boolean useInventoryOverride = false; + + /** + * Loads the configuration. + */ + public abstract void load(); +} diff --git a/src/com/sk89q/worldedit/WorldEditPlayer.java b/src/com/sk89q/worldedit/LocalPlayer.java similarity index 79% rename from src/com/sk89q/worldedit/WorldEditPlayer.java rename to src/com/sk89q/worldedit/LocalPlayer.java index bca863620..621cad592 100644 --- a/src/com/sk89q/worldedit/WorldEditPlayer.java +++ b/src/com/sk89q/worldedit/LocalPlayer.java @@ -26,7 +26,7 @@ import com.sk89q.worldedit.blocks.BlockType; * * @author sk89q */ -public abstract class WorldEditPlayer { +public abstract class LocalPlayer { /** * Directions. */ @@ -48,9 +48,11 @@ public abstract class WorldEditPlayer { /** * Construct the object. + * + * @param server */ - protected WorldEditPlayer() { - server = ServerInterface.getInstance(); + protected LocalPlayer(ServerInterface server) { + this.server = server; } /** @@ -72,7 +74,7 @@ public abstract class WorldEditPlayer { * * @param searchPos search position */ - public void findFreePosition(Vector searchPos) { + public void findFreePosition(LocalWorld world, Vector searchPos) { int x = searchPos.getBlockX(); int y = Math.max(0, searchPos.getBlockY()); int origY = y; @@ -81,7 +83,8 @@ public abstract class WorldEditPlayer { byte free = 0; while (y <= 129) { - if (BlockType.canPassThrough(server.getBlockType(new Vector(x, y, z)))) { + if (BlockType.canPassThrough(server.getBlockType(world, + new Vector(x, y, z)))) { free++; } else { free = 0; @@ -106,7 +109,7 @@ public abstract class WorldEditPlayer { * that free position. */ public void findFreePosition() { - findFreePosition(getBlockIn()); + findFreePosition(getPosition().getWorld(), getBlockIn()); } /** @@ -119,12 +122,13 @@ public abstract class WorldEditPlayer { int x = pos.getBlockX(); int y = Math.max(0, pos.getBlockY()); int z = pos.getBlockZ(); + LocalWorld world = getPosition().getWorld(); byte free = 0; byte spots = 0; while (y <= 129) { - if (BlockType.canPassThrough(server.getBlockType(new Vector(x, y, z)))) { + if (BlockType.canPassThrough(server.getBlockType(world, new Vector(x, y, z)))) { free++; } else { free = 0; @@ -133,7 +137,7 @@ public abstract class WorldEditPlayer { if (free == 2) { spots++; if (spots == 2) { - int type = server.getBlockType(new Vector(x, y - 2, z)); + int type = server.getBlockType(world, new Vector(x, y - 2, z)); // Don't get put in lava! if (type == 10 || type == 11) { @@ -161,11 +165,12 @@ public abstract class WorldEditPlayer { int x = pos.getBlockX(); int y = Math.max(0, pos.getBlockY() - 1); int z = pos.getBlockZ(); + LocalWorld world = getPosition().getWorld(); byte free = 0; while (y >= 1) { - if (BlockType.canPassThrough(server.getBlockType(new Vector(x, y, z)))) { + if (BlockType.canPassThrough(server.getBlockType(world, new Vector(x, y, z)))) { free++; } else { free = 0; @@ -176,7 +181,7 @@ public abstract class WorldEditPlayer { // lightly and also check to see if there's something to // stand upon while (y >= 0) { - int type = server.getBlockType(new Vector(x, y, z)); + int type = server.getBlockType(world, new Vector(x, y, z)); // Don't want to end up in lava if (type != 0 && type != 10 && type != 11) { @@ -209,17 +214,18 @@ public abstract class WorldEditPlayer { int initialY = Math.max(0, pos.getBlockY()); int y = Math.max(0, pos.getBlockY() + 2); int z = pos.getBlockZ(); + LocalWorld world = getPosition().getWorld(); // No free space above - if (server.getBlockType(new Vector(x, y, z)) != 0) { + if (server.getBlockType(world, new Vector(x, y, z)) != 0) { return false; } while (y <= 127) { // Found a ceiling! - if (!BlockType.canPassThrough(server.getBlockType(new Vector(x, y, z)))) { + if (!BlockType.canPassThrough(server.getBlockType(world, new Vector(x, y, z)))) { int platformY = Math.max(initialY, y - 3 - clearance); - server.setBlockType(new Vector(x, platformY, z), + server.setBlockType(world, new Vector(x, platformY, z), BlockType.GLASS.getID()); setPosition(new Vector(x + 0.5, platformY + 1, z + 0.5)); return true; @@ -244,14 +250,15 @@ public abstract class WorldEditPlayer { int y = Math.max(0, pos.getBlockY() + 1); int z = pos.getBlockZ(); int maxY = Math.min(128, initialY + distance); + LocalWorld world = getPosition().getWorld(); while (y <= 129) { - if (!BlockType.canPassThrough(server.getBlockType(new Vector(x, y, z)))) { + if (!BlockType.canPassThrough(server.getBlockType(world, new Vector(x, y, z)))) { break; // Hit something } else if (y > maxY + 1) { break; } else if (y == maxY + 1) { - server.setBlockType(new Vector(x, y - 2, z), + server.setBlockType(world, new Vector(x, y - 2, z), BlockType.GLASS.getID()); setPosition(new Vector(x + 0.5, y - 1, z + 0.5)); return true; @@ -268,8 +275,8 @@ public abstract class WorldEditPlayer { * * @return point */ - public Vector getBlockIn() { - return getPosition().toBlockVector(); + public WorldVector getBlockIn() { + return getPosition(); } /** @@ -277,8 +284,9 @@ public abstract class WorldEditPlayer { * * @return point */ - public Vector getBlockOn() { - return getPosition().subtract(0, 1, 0).toBlockVector(); + public WorldVector getBlockOn() { + WorldVector pos = getPosition(); + return new WorldVector(pos.getWorld(), pos.subtract(0, 1, 0)); } /** @@ -287,7 +295,7 @@ public abstract class WorldEditPlayer { * @param range * @return point */ - public abstract Vector getBlockTrace(int range); + public abstract WorldVector getBlockTrace(int range); /** * Get the point of the block being looked at. May return null. @@ -295,14 +303,14 @@ public abstract class WorldEditPlayer { * @param range * @return point */ - public abstract Vector getSolidBlockTrace(int range); + public abstract WorldVector getSolidBlockTrace(int range); /** * Get the player's cardinal direction (N, W, NW, etc.). May return null. * * @return */ - public WorldEditPlayer.DIRECTION getCardinalDirection() { + public LocalPlayer.DIRECTION getCardinalDirection() { // From hey0's code double rot = (getYaw() - 90) % 360; if (rot < 0) { @@ -317,25 +325,25 @@ public abstract class WorldEditPlayer { * @param rot * @return */ - private static WorldEditPlayer.DIRECTION getDirection(double rot) { + private static LocalPlayer.DIRECTION getDirection(double rot) { if (0 <= rot && rot < 22.5) { - return WorldEditPlayer.DIRECTION.NORTH; + return LocalPlayer.DIRECTION.NORTH; } else if (22.5 <= rot && rot < 67.5) { - return WorldEditPlayer.DIRECTION.NORTH_EAST; + return LocalPlayer.DIRECTION.NORTH_EAST; } else if (67.5 <= rot && rot < 112.5) { - return WorldEditPlayer.DIRECTION.EAST; + return LocalPlayer.DIRECTION.EAST; } else if (112.5 <= rot && rot < 157.5) { - return WorldEditPlayer.DIRECTION.SOUTH_EAST; + return LocalPlayer.DIRECTION.SOUTH_EAST; } else if (157.5 <= rot && rot < 202.5) { - return WorldEditPlayer.DIRECTION.SOUTH; + return LocalPlayer.DIRECTION.SOUTH; } else if (202.5 <= rot && rot < 247.5) { - return WorldEditPlayer.DIRECTION.SOUTH_WEST; + return LocalPlayer.DIRECTION.SOUTH_WEST; } else if (247.5 <= rot && rot < 292.5) { - return WorldEditPlayer.DIRECTION.WEST; + return LocalPlayer.DIRECTION.WEST; } else if (292.5 <= rot && rot < 337.5) { - return WorldEditPlayer.DIRECTION.NORTH_WEST; + return LocalPlayer.DIRECTION.NORTH_WEST; } else if (337.5 <= rot && rot < 360.0) { - return WorldEditPlayer.DIRECTION.NORTH; + return LocalPlayer.DIRECTION.NORTH; } else { return null; } @@ -360,7 +368,14 @@ public abstract class WorldEditPlayer { * * @return point */ - public abstract Vector getPosition(); + public abstract WorldVector getPosition(); + + /** + * Get the player's world. + * + * @return point + */ + public abstract LocalWorld getWorld(); /** * Get the player's view pitch. @@ -462,6 +477,15 @@ public abstract class WorldEditPlayer { * @return */ public abstract boolean hasPermission(String perm); + + /** + * Returns true if the player can destroy bedrock. + * + * @return + */ + public boolean canDestroyBedrock() { + return hasPermission("worldeditbedrock"); + } /** * Returns true if equal. @@ -471,10 +495,10 @@ public abstract class WorldEditPlayer { */ @Override public boolean equals(Object other) { - if (!(other instanceof WorldEditPlayer)) { + if (!(other instanceof LocalPlayer)) { return false; } - WorldEditPlayer other2 = (WorldEditPlayer)other; + LocalPlayer other2 = (LocalPlayer)other; return other2.getName().equals(getName()); } diff --git a/src/com/sk89q/worldedit/WorldEditSession.java b/src/com/sk89q/worldedit/LocalSession.java similarity index 87% rename from src/com/sk89q/worldedit/WorldEditSession.java rename to src/com/sk89q/worldedit/LocalSession.java index ec3b455d5..77843a379 100644 --- a/src/com/sk89q/worldedit/WorldEditSession.java +++ b/src/com/sk89q/worldedit/LocalSession.java @@ -21,6 +21,8 @@ package com.sk89q.worldedit; import java.util.LinkedList; import com.sk89q.worldedit.snapshots.Snapshot; +import com.sk89q.worldedit.superpickaxe.SinglePickaxe; +import com.sk89q.worldedit.superpickaxe.SuperPickaxeMode; import com.sk89q.worldedit.bags.BlockBag; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.CuboidRegion; @@ -29,25 +31,9 @@ import com.sk89q.worldedit.regions.CuboidRegion; * * @author sk89q */ -public class WorldEditSession { - /** - * List of super pick axe modes. - */ - public static enum SuperPickaxeMode { - SINGLE, - SAME_TYPE_RECURSIVE, - SAME_TYPE_AREA - }; - /** - * List of tools. - */ - public static enum Tool { - NONE, - INFO, - TREE, - } - +public class LocalSession { public static final int MAX_HISTORY_SIZE = 15; + private boolean placeAtPos1 = false; private Vector pos1, pos2; private Region region; @@ -55,10 +41,9 @@ public class WorldEditSession { private int historyPointer = 0; private CuboidClipboard clipboard; private boolean toolControl = true; - private boolean superPickAxe = false; - private SuperPickaxeMode superPickaxeMode = SuperPickaxeMode.SINGLE; - private Tool tool = Tool.NONE; - private int superPickaxeRange = 3; + private boolean superPickaxe = false; + private SuperPickaxeMode superPickaxeMode = new SinglePickaxe(); + private SuperPickaxeMode tool; private int maxBlocksChanged = -1; private boolean useInventory; private Snapshot snapshot; @@ -291,25 +276,25 @@ public class WorldEditSession { * @return status */ public boolean hasSuperPickAxe() { - return superPickAxe; + return superPickaxe; } /** * Enable super pick axe. * - * @param superPickAxe + * @param superPickaxe */ public void enableSuperPickAxe() { - superPickAxe = true; + superPickaxe = true; } /** * Disable super pick axe. * - * @param superPickAxe + * @param superPickaxe */ public void disableSuperPickAxe() { - superPickAxe = false; + superPickaxe = false; } /** @@ -318,15 +303,15 @@ public class WorldEditSession { * @return status */ public boolean toggleSuperPickAxe() { - superPickAxe = !superPickAxe; - return superPickAxe; + superPickaxe = !superPickaxe; + return superPickaxe; } /** * @return position * @throws IncompleteRegionException */ - public Vector getPlacementPosition(WorldEditPlayer player) + public Vector getPlacementPosition(LocalPlayer player) throws IncompleteRegionException { if (!placeAtPos1) { return player.getBlockIn(); @@ -350,7 +335,7 @@ public class WorldEditSession { * @param player * @return */ - public BlockBag getBlockBag(WorldEditPlayer player) { + public BlockBag getBlockBag(LocalPlayer player) { if (!useInventory) { return null; } @@ -385,31 +370,17 @@ public class WorldEditSession { this.superPickaxeMode = superPickaxeMode; } - /** - * @return the superPickaxeRange - */ - public int getSuperPickaxeRange() { - return superPickaxeRange; - } - - /** - * @param superPickaxeRange the superPickaxeRange to set - */ - public void setSuperPickaxeRange(int superPickaxeRange) { - this.superPickaxeRange = superPickaxeRange; - } - /** * @return the tool */ - public Tool getTool() { + public SuperPickaxeMode getTool() { return tool; } /** * @param tool the tool to set */ - public void setTool(Tool tool) { + public void setTool(SuperPickaxeMode tool) { this.tool = tool; } diff --git a/src/com/sk89q/worldedit/LocalWorld.java b/src/com/sk89q/worldedit/LocalWorld.java new file mode 100644 index 000000000..74bda7fef --- /dev/null +++ b/src/com/sk89q/worldedit/LocalWorld.java @@ -0,0 +1,41 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldedit; + +/** + * Represents a world. + * + * @author sk89q + */ +public abstract class LocalWorld { + /** + * Compare if the other world is equal. + * + * @param other + * @return + */ + public abstract boolean equals(Object other); + /** + * Hash code. + * + * @return + */ + public abstract int hashCode(); +} diff --git a/src/com/sk89q/worldedit/ServerInterface.java b/src/com/sk89q/worldedit/ServerInterface.java index 851213f3c..9934e69bc 100644 --- a/src/com/sk89q/worldedit/ServerInterface.java +++ b/src/com/sk89q/worldedit/ServerInterface.java @@ -15,113 +15,92 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . -*/ + */ package com.sk89q.worldedit; import com.sk89q.worldedit.blocks.BaseItemStack; /** - * + * * @author sk89q */ public abstract class ServerInterface { - /** - * Instance. - */ - private static ServerInterface instance; - - /** - * Get the current instance. - * - * @return - */ - public static ServerInterface getInstance() { - return instance; - } - - /** - * Set up an instance. - * @param instance - */ - public static void setup(ServerInterface instance) { - ServerInterface.instance = instance; - } - /** * Set block type. - * + * * @param pt * @param type * @return */ - public abstract boolean setBlockType(Vector pt, int type); - + public abstract boolean setBlockType(LocalWorld world, Vector pt, int type); + /** * Get block type. - * + * * @param pt * @return */ - public abstract int getBlockType(Vector pt); + public abstract int getBlockType(LocalWorld world, Vector pt); /** * Set block data. - * + * * @param pt * @param data * @return */ - public abstract void setBlockData(Vector pt, int data); + public abstract void setBlockData(LocalWorld world, Vector pt, int data); /** * Get block data. - * + * * @param pt * @return */ - public abstract int getBlockData(Vector pt); - + public abstract int getBlockData(LocalWorld world, Vector pt); + /** * Set sign text. - * + * * @param pt * @param text */ - public abstract void setSignText(Vector pt, String[] text); - + public abstract void setSignText(LocalWorld world, Vector pt, String[] text); + /** * Get sign text. - * + * * @param pt * @return */ - public abstract String[] getSignText(Vector pt); + public abstract String[] getSignText(LocalWorld world, Vector pt); /** * Gets the contents of chests. Will return null if the chest does not * really exist or it is the second block for a double chest. - * + * * @param pt * @return */ - public abstract BaseItemStack[] getChestContents(Vector pt); + public abstract BaseItemStack[] getChestContents(LocalWorld world, Vector pt); /** * Sets a chest slot. - * + * * @param pt * @param contents * @return */ - public abstract boolean setChestContents(Vector pt, BaseItemStack[] contents); + public abstract boolean setChestContents(LocalWorld world, Vector pt, + BaseItemStack[] contents); /** * Clear a chest's contents. * * @param pt */ - public abstract boolean clearChest(Vector pt); + public abstract boolean clearChest(LocalWorld world, Vector pt); /** * Checks if a mob type is valid. @@ -133,19 +112,20 @@ public abstract class ServerInterface { /** * Set mob spawner mob type. - * + * * @param pt * @param mobType */ - public abstract void setMobSpawnerType(Vector pt, String mobType); + public abstract void setMobSpawnerType(LocalWorld world, Vector pt, + String mobType); /** * Get mob spawner mob type. May return an empty string. - * + * * @param pt * @param mobType */ - public abstract String getMobSpawnerType(Vector pt); + public abstract String getMobSpawnerType(LocalWorld world, Vector pt); /** * Generate a tree at a location. @@ -153,45 +133,48 @@ public abstract class ServerInterface { * @param pt * @return */ - public abstract boolean generateTree(EditSession editSession, Vector pt); + public abstract boolean generateTree(EditSession editSession, + LocalWorld world, Vector pt); /** * Drop an item. - * + * * @param pt * @param type * @param count * @param times */ - public abstract void dropItem(Vector pt, int type, int count, int times); + public abstract void dropItem(LocalWorld world, Vector pt, int type, + int count, int times); /** * Drop an item. - * + * * @param pt * @param type * @param count * @param times */ - public abstract void dropItem(Vector pt, int type, int count); + public abstract void dropItem(LocalWorld world, Vector pt, int type, + int count); /** * Drop an item. - * + * * @param pt * @param type * @param count * @param times */ - public abstract void dropItem(Vector pt, int type); + public abstract void dropItem(LocalWorld world, Vector pt, int type); /** * Simulate a block being mined. * * @param pt */ - public abstract void simulateBlockMine(Vector pt); - + public abstract void simulateBlockMine(LocalWorld world, Vector pt); + /** * Resolves an item name to its ID. * @@ -207,5 +190,5 @@ public abstract class ServerInterface { * @param radius * @return */ - public abstract int killMobs(Vector origin, int radius); + public abstract int killMobs(LocalWorld world, Vector origin, int radius); } diff --git a/src/com/sk89q/worldedit/Vector.java b/src/com/sk89q/worldedit/Vector.java index f7126be4d..a499e4d07 100644 --- a/src/com/sk89q/worldedit/Vector.java +++ b/src/com/sk89q/worldedit/Vector.java @@ -21,7 +21,7 @@ package com.sk89q.worldedit; /** * - * @author Albert + * @author sk89q */ public class Vector { protected final double x, y, z; diff --git a/src/com/sk89q/worldedit/WorldEditController.java b/src/com/sk89q/worldedit/WorldEditController.java index b63037a80..1633fd4c1 100644 --- a/src/com/sk89q/worldedit/WorldEditController.java +++ b/src/com/sk89q/worldedit/WorldEditController.java @@ -32,6 +32,7 @@ import com.sk89q.worldedit.blocks.*; import com.sk89q.worldedit.data.*; import com.sk89q.worldedit.filters.*; import com.sk89q.worldedit.snapshots.*; +import com.sk89q.worldedit.superpickaxe.*; import com.sk89q.worldedit.regions.*; import com.sk89q.worldedit.patterns.*; @@ -59,6 +60,10 @@ public class WorldEditController { * Server interface. */ private ServerInterface server; + /** + * WorldEdit configuration. + */ + private LocalConfiguration config; /** * Stores a list of WorldEdit sessions, keyed by players' names. Sessions @@ -67,8 +72,8 @@ public class WorldEditController { * without any WorldEdit abilities or never use WorldEdit in a session will * not have a session object generated for them. */ - private HashMap sessions = - new HashMap(); + private HashMap sessions = + new HashMap(); /** * List of commands. These are checked when onCommand() is called, so @@ -76,29 +81,16 @@ public class WorldEditController { * will be loaded into help. On unload, they will be removed. */ private HashMap commands = new HashMap(); - - public boolean profile; - public HashSet allowedBlocks; - public int defaultChangeLimit = -1; - public int maxChangeLimit = -1; - public String shellSaveType; - public SnapshotRepository snapshotRepo; - public int maxRadius = -1; - public int maxSuperPickaxeSize = 5; - public boolean logComands = false; - public boolean registerHelp = true; - public int wandItem = 271; - public boolean superPickaxeDrop = true; - public boolean superPickaxeManyDrop = true; - public boolean noDoubleSlash = false; - public boolean useInventory = false; - public boolean useInventoryOverride = false; - + /** - * Construct an instance of the plugin. + * Construct an instance of the plugin + * + * @param server + * @param config */ - public WorldEditController() { - server = ServerInterface.getInstance(); + public WorldEditController(ServerInterface server, LocalConfiguration config) { + this.server = server; + this.config = config; // Note: Commands should only have the phrase 'air' at the end // for now (see SMWorldEditListener.canUseCommand) @@ -141,8 +133,13 @@ public class WorldEditController { commands.put("//fillr", "[ID] [Radius] - Fill a hole fully recursively"); commands.put("//drain", "[Radius] - Drain nearby water/lava pools"); commands.put("//limit", "[Num] - See documentation"); - commands.put("//mode", "[Mode] - Set super pickaxe mode (single/recursive/area)"); - commands.put("//tool", "[Tool] - Set pickaxe tool (none/tree/info)"); + commands.put("/single", "Switch to single block super pickaxe mode"); + commands.put("/area", "[Range] - Switch to area super pickaxe mode"); + commands.put("/recur", "[Range] - Switch to recursive super pickaxe mode"); + commands.put("/none", "Switch to no tool"); + commands.put("/info", "Switch to the info tool"); + commands.put("/tree", "Switch to the tree tool"); + commands.put("/repl", "[ID] - Switch to the block replacer tool"); commands.put("//expand", "[Num] - Expands the selection"); commands.put("//contract", "[Num] - Contracts the selection"); commands.put("//shift", "[Num] - Shift the selection"); @@ -177,35 +174,49 @@ public class WorldEditController { } /** - * Gets the WorldEditLibrary session for a player. + * Gets the WorldEdit session for a player. * * @param player * @return */ - public WorldEditSession getSession(WorldEditPlayer player) { + public LocalSession getSession(LocalPlayer player) { if (sessions.containsKey(player)) { return sessions.get(player); - } else { - WorldEditSession session = new WorldEditSession(); - if (!player.hasPermission("/worldeditnomax") - && maxChangeLimit > -1) { - if (defaultChangeLimit < 0) { - // No infinite! - session.setBlockChangeLimit(maxChangeLimit); - } else { - // Bound - session.setBlockChangeLimit( - Math.min(defaultChangeLimit, maxChangeLimit)); - } - } else { - session.setBlockChangeLimit(defaultChangeLimit); - } - session.setUseInventory(useInventory - && (!useInventoryOverride - || !player.hasPermission("/worldeditunlimited"))); - sessions.put(player, session); - return session; } + + LocalSession session = new LocalSession(); + + // Set the limit on the number of blocks that an operation can + // change at once, or don't if the player has an override or there + // is no limit. There is also a default limit + if (!player.hasPermission("worldeditnomax") + && config.maxChangeLimit > -1) { + + // If the default limit is infinite but there is a maximum + // limit, make sure to not have it be overridden + if (config.defaultChangeLimit < 0) { + session.setBlockChangeLimit(config.maxChangeLimit); + } else { + // Bound the change limit + int limit = Math.min(config.defaultChangeLimit, + config.maxChangeLimit); + session.setBlockChangeLimit(limit); + } + } else { + // No change limit or override + session.setBlockChangeLimit(config.defaultChangeLimit); + } + + // Have the session use inventory if it's enabled and the player + // doesn't have an override + session.setUseInventory(config.useInventory + && (!config.useInventoryOverride + || !player.hasPermission("worldeditunlimited"))); + + // Remember the session + sessions.put(player, session); + + return session; } /** @@ -214,19 +225,21 @@ public class WorldEditController { * @param player * @return */ - public boolean hasSession(WorldEditPlayer player) { + public boolean hasSession(LocalPlayer player) { return sessions.containsKey(player); } /** * Get an item ID from an item name or an item ID number. * + * @param player * @param arg + * @param allAllowed true to ignore blacklists * @return * @throws UnknownItemException * @throws DisallowedItemException */ - public BaseBlock getBlock(String arg, boolean allAllowed) + public BaseBlock getBlock(LocalPlayer player, String arg, boolean allAllowed) throws UnknownItemException, DisallowedItemException { BlockType blockType; arg = arg.replace("_", " "); @@ -237,6 +250,7 @@ public class WorldEditController { int data; + // Parse the block data (optional) try { data = args1.length > 1 ? Integer.parseInt(args1[1]) : 0; if (data > 15 || data < 0) { @@ -246,6 +260,8 @@ public class WorldEditController { data = 0; } + // Attempt to parse the item ID or otherwise resolve an item/block + // name to its numeric ID try { blockType = BlockType.fromID(Integer.parseInt(testID)); } catch (NumberFormatException e) { @@ -263,8 +279,10 @@ public class WorldEditController { } // Check if the item is allowed - if (allAllowed || allowedBlocks.isEmpty() - || allowedBlocks.contains(blockType.getID())) { + if (allAllowed || player.hasPermission("worldeditanyblock") + || !config.disallowedBlocks.contains(blockType.getID())) { + + // Allow special sign text syntax if (blockType == BlockType.SIGN_POST || blockType == BlockType.WALL_SIGN) { String[] text = new String[4]; @@ -273,6 +291,8 @@ public class WorldEditController { text[2] = args0.length > 3 ? args0[3] : ""; text[3] = args0.length > 4 ? args0[4] : ""; return new SignBlock(blockType.getID(), data, text); + + // Alow setting mob spawn type } else if (blockType == BlockType.MOB_SPAWNER) { if (args0.length > 1) { if (!server.isValidMobType(args0[1])) { @@ -293,29 +313,31 @@ public class WorldEditController { /** * Get a block. * + * @param player * @param id * @return * @throws UnknownItemException * @throws DisallowedItemException */ - public BaseBlock getBlock(String id) throws UnknownItemException, - DisallowedItemException { - return getBlock(id, false); + public BaseBlock getBlock(LocalPlayer player, String id) + throws UnknownItemException, DisallowedItemException { + return getBlock(player, id, false); } /** * Get a list of blocks as a set. This returns a Pattern. * + * @param player * @param list * @return pattern */ - public Pattern getBlockPattern(String list) + public Pattern getBlockPattern(LocalPlayer player, String list) throws UnknownItemException, DisallowedItemException { String[] items = list.split(","); if (items.length == 1) { - return new SingleBlockPattern(getBlock(items[0])); + return new SingleBlockPattern(getBlock(player, items[0])); } List blockChances = new ArrayList(); @@ -327,10 +349,10 @@ public class WorldEditController { if (s.matches("[0-9]+(?:\\.(?:[0-9]+)?)?%.*")) { String[] p = s.split("%"); chance = Double.parseDouble(p[0]); - block = getBlock(p[1]); + block = getBlock(player, p[1]); } else { chance = 1; - block = getBlock(s); + block = getBlock(player, s); } blockChances.add(new BlockChance(block, chance)); @@ -342,16 +364,19 @@ public class WorldEditController { /** * Get a list of blocks as a set. * + *@param player * @param list - * @params allBlocksAllowed + * @param allBlocksAllowed * @return set */ - public Set getBlockIDs(String list, boolean allBlocksAllowed) + public Set getBlockIDs(LocalPlayer player, + String list, boolean allBlocksAllowed) throws UnknownItemException, DisallowedItemException { + String[] items = list.split(","); Set blocks = new HashSet(); for (String s : items) { - blocks.add(getBlock(s, allBlocksAllowed).getID()); + blocks.add(getBlock(player, s, allBlocksAllowed).getID()); } return blocks; } @@ -384,7 +409,7 @@ public class WorldEditController { * @throws MaxRadiusException */ private void checkMaxRadius(int radius) throws MaxRadiusException { - if (maxRadius > 0 && radius > maxRadius) { + if (config.maxRadius > 0 && radius > config.maxRadius) { throw new MaxRadiusException(); } } @@ -403,11 +428,11 @@ public class WorldEditController { * @throws InsufficientArgumentsException * @throws DisallowedItemException */ - public boolean performCommand(WorldEditPlayer player, - WorldEditSession session, EditSession editSession, String[] split) + public boolean performCommand(LocalPlayer player, + LocalSession session, EditSession editSession, String[] split) throws WorldEditException { - if (logComands) { + if (config.logComands) { logger.log(Level.INFO, "WorldEdit: " + player.getName() + ": " + joinString(split, " ")); } @@ -442,9 +467,9 @@ public class WorldEditController { // Jump to the block in sight } else if (split[0].equalsIgnoreCase("/jumpto")) { checkArgs(split, 0, 0, split[0]); - Vector pos = player.getSolidBlockTrace(300); + WorldVector pos = player.getSolidBlockTrace(300); if (pos != null) { - player.findFreePosition(pos); + player.findFreePosition(pos.getWorld(), pos); player.print("Poof!"); } else { player.printError("No block in sight!"); @@ -563,7 +588,7 @@ public class WorldEditController { // Edit wand } else if (split[0].equalsIgnoreCase("//wand")) { checkArgs(split, 0, 0, split[0]); - player.giveItem(wandItem, 1); + player.giveItem(config.wandItem, 1); player.print("Left click: select pos #1; Right click: select pos #2"); return true; @@ -602,10 +627,11 @@ public class WorldEditController { } else if (split[0].equalsIgnoreCase("//limit")) { checkArgs(split, 1, 1, split[0]); int limit = Math.max(-1, Integer.parseInt(split[1])); - if (!player.hasPermission("/worldeditnomax") - && maxChangeLimit > -1) { - if (limit > maxChangeLimit) { - player.printError("Your maximum allowable limit is " + maxChangeLimit + "."); + if (!player.hasPermission("worldeditnomax") + && config.maxChangeLimit > -1) { + if (limit > config.maxChangeLimit) { + player.printError("Your maximum allowable limit is " + + config.maxChangeLimit + "."); return true; } } @@ -615,61 +641,71 @@ public class WorldEditController { return true; - // Set super pick axe mode - } else if (split[0].equalsIgnoreCase("//mode")) { - checkArgs(split, 1, 2, split[0]); - - if (split[1].equalsIgnoreCase("single")) { - session.setSuperPickaxeMode(WorldEditSession.SuperPickaxeMode.SINGLE); - player.print("Mode set to single block."); - } else if (split[1].equalsIgnoreCase("recursive") - || split[1].equalsIgnoreCase("area")) { - if (split.length == 3) { - int size = Math.max(1, Integer.parseInt(split[2])); - if (size <= maxSuperPickaxeSize) { - WorldEditSession.SuperPickaxeMode mode = - split[1].equalsIgnoreCase("recursive") ? - WorldEditSession.SuperPickaxeMode.SAME_TYPE_RECURSIVE : - WorldEditSession.SuperPickaxeMode.SAME_TYPE_AREA; - session.setSuperPickaxeMode(mode); - session.setSuperPickaxeRange(size); - player.print("Mode set to " + split[1].toLowerCase() + "."); - } else { - player.printError("Max size is " + maxSuperPickaxeSize + "."); - } - } else { - player.printError("Size argument required for mode " - + split[1].toLowerCase() + "."); - } - } else { - player.printError("Unknown super pick axe mode."); + // Single super pickaxe mode + } else if (split[0].equalsIgnoreCase("/single")) { + if (!canUseCommand(player, "//")) { + player.printError("You don't have permission for super pickaxe usage."); + return true; } + + checkArgs(split, 0, 0, split[0]); + session.setSuperPickaxeMode(new SinglePickaxe()); + session.enableSuperPickAxe(); + player.print("Mode changed. Left click with a pickaxe. // to disable."); return true; - // Set tool - } else if (split[0].equalsIgnoreCase("//tool")) { - checkArgs(split, 1, 1, split[0]); - - if (split[1].equalsIgnoreCase("none")) { - session.setTool(WorldEditSession.Tool.NONE); - player.print("No tool equipped. -3 XP, +10 Manliness"); - } else if (split[1].equalsIgnoreCase("tree")) { - if (!canUseCommand(player, "/treetool")) { - player.printError("You do not have the /treetool permission."); - return true; - } - session.setTool(WorldEditSession.Tool.TREE); - player.print("Tree planting tool equipped. +5 XP"); - } else if (split[1].equalsIgnoreCase("info")) { - if (!canUseCommand(player, "/infotool")) { - player.printError("You do not have the /infotool permission."); - return true; - } - session.setTool(WorldEditSession.Tool.INFO); - player.print("Block information tool equipped."); - } else { - player.printError("Unknown tool."); + // Area/recursive super pickaxe mode + } else if (split[0].equalsIgnoreCase("/area") + || split[0].equalsIgnoreCase("/recur")) { + + if (!canUseCommand(player, "//")) { + player.printError("You don't have permission for super pickaxe usage."); + return true; } + + checkArgs(split, 1, 1, split[0]); + + boolean recur = split[0].equalsIgnoreCase("/recur"); + int range = Integer.parseInt(split[1]); + + if (range > config.maxSuperPickaxeSize) { + player.printError("Maximum range: " + config.maxSuperPickaxeSize); + return true; + } + + session.setSuperPickaxeMode( + recur ? new RecursivePickaxe(range) : new AreaPickaxe(range)); + session.enableSuperPickAxe(); + player.print("Mode changed. Left click with a pickaxe. // to disable."); + return true; + + // Tree tool + } else if (split[0].equalsIgnoreCase("/tree")) { + checkArgs(split, 0, 0, split[0]); + session.setTool(new TreePlanter()); + player.print("Tree tool equipped. Right click with a pickaxe."); + return true; + + // Info tool + } else if (split[0].equalsIgnoreCase("/info")) { + checkArgs(split, 0, 0, split[0]); + session.setTool(new QueryTool()); + player.print("Info tool equipped. Right click with a pickaxe."); + return true; + + // Replace block tool + } else if (split[0].equalsIgnoreCase("/repl")) { + checkArgs(split, 1, 1, split[0]); + BaseBlock targetBlock = getBlock(player, split[1]); + session.setTool(new BlockReplacer(targetBlock)); + player.print("Block replacer tool equipped. Right click with a pickaxe."); + return true; + + // No tool + } else if (split[0].equalsIgnoreCase("/none")) { + checkArgs(split, 0, 0, split[0]); + session.setTool(null); + player.print("Now no longer equipping a tool."); return true; // Undo @@ -738,7 +774,7 @@ public class WorldEditController { } else if (split[0].equalsIgnoreCase("//hcyl") || split[0].equalsIgnoreCase("//cyl")) { checkArgs(split, 2, 3, split[0]); - BaseBlock block = getBlock(split[1]); + BaseBlock block = getBlock(player, split[1]); int radius = Math.max(1, Integer.parseInt(split[2])); int height = split.length > 3 ? Integer.parseInt(split[3]) : 1; boolean filled = split[0].equalsIgnoreCase("//cyl"); @@ -758,7 +794,7 @@ public class WorldEditController { } else if (split[0].equalsIgnoreCase("//sphere") || split[0].equalsIgnoreCase("//hsphere")) { checkArgs(split, 2, 3, split[0]); - BaseBlock block = getBlock(split[1]); + BaseBlock block = getBlock(player, split[1]); int radius = Math.max(1, Integer.parseInt(split[2])); boolean raised = split.length > 3 ? (split[3].equalsIgnoreCase("true") @@ -782,7 +818,7 @@ public class WorldEditController { || split[0].equalsIgnoreCase("//fillr")) { boolean recursive = split[0].equalsIgnoreCase("//fillr"); checkArgs(split, 2, recursive ? 2 : 3, split[0]); - Pattern pattern = getBlockPattern(split[1]); + Pattern pattern = getBlockPattern(player, split[1]); int radius = Math.max(1, Integer.parseInt(split[2])); checkMaxRadius(radius); int depth = split.length > 3 ? Math.max(1, Integer.parseInt(split[3])) : 1; @@ -829,7 +865,7 @@ public class WorldEditController { // Remove blocks near } else if (split[0].equalsIgnoreCase("/removenear")) { checkArgs(split, 2, 2, split[0]); - BaseBlock block = getBlock(split[1], true); + BaseBlock block = getBlock(player, split[1], true); int size = Math.max(1, Integer.parseInt(split[2])); checkMaxRadius(size); @@ -842,7 +878,7 @@ public class WorldEditController { // Extinguish } else if (split[0].equalsIgnoreCase("/ex")) { checkArgs(split, 0, 1, split[0]); - int defaultRadius = maxRadius != -1 ? Math.min(40, maxRadius) : 40; + int defaultRadius = config.maxRadius != -1 ? Math.min(40, config.maxRadius) : 40; int size = split.length > 1 ? Math.max(1, Integer.parseInt(split[1])) : defaultRadius; checkMaxRadius(size); @@ -946,7 +982,7 @@ public class WorldEditController { // Get count } else if (split[0].equalsIgnoreCase("//count")) { checkArgs(split, 1, 1, split[0]); - Set searchIDs = getBlockIDs(split[1], true); + Set searchIDs = getBlockIDs(player, split[1], true); player.print("Counted: " + editSession.countBlocks(session.getRegion(), searchIDs)); return true; @@ -975,7 +1011,7 @@ public class WorldEditController { // Replace all blocks in the region } else if(split[0].equalsIgnoreCase("//set")) { checkArgs(split, 1, 1, split[0]); - Pattern pattern = getBlockPattern(split[1]); + Pattern pattern = getBlockPattern(player, split[1]); int affected; if (pattern instanceof SingleBlockPattern) { affected = editSession.setBlocks(session.getRegion(), @@ -1005,7 +1041,7 @@ public class WorldEditController { // Set the outline of a region } else if(split[0].equalsIgnoreCase("//outline")) { checkArgs(split, 1, 1, split[0]); - BaseBlock block = getBlock(split[1]); + BaseBlock block = getBlock(player, split[1]); int affected = editSession.makeCuboidFaces(session.getRegion(), block); player.print(affected + " block(s) have been changed."); @@ -1014,7 +1050,7 @@ public class WorldEditController { // Set the walls of a region } else if(split[0].equalsIgnoreCase("//walls")) { checkArgs(split, 1, 1, split[0]); - BaseBlock block = getBlock(split[1]); + BaseBlock block = getBlock(player, split[1]); int affected = editSession.makeCuboidWalls(session.getRegion(), block); player.print(affected + " block(s) have been changed."); @@ -1061,10 +1097,10 @@ public class WorldEditController { Pattern to; if (split.length == 2) { from = null; - to = getBlockPattern(split[1]); + to = getBlockPattern(player, split[1]); } else { - from = getBlockIDs(split[1], true); - to = getBlockPattern(split[2]); + from = getBlockIDs(player, split[1], true); + to = getBlockPattern(player, split[2]); } int affected = 0; @@ -1086,10 +1122,10 @@ public class WorldEditController { BaseBlock to; if (split.length == 3) { from = null; - to = getBlock(split[2]); + to = getBlock(player, split[2]); } else { - from = getBlockIDs(split[2], true); - to = getBlock(split[3]); + from = getBlockIDs(player, split[2], true); + to = getBlock(player, split[3]); } Vector min = player.getBlockIn().subtract(size, size, size); @@ -1104,7 +1140,7 @@ public class WorldEditController { // Lay blocks over an area } else if (split[0].equalsIgnoreCase("//overlay")) { checkArgs(split, 1, 1, split[0]); - BaseBlock block = getBlock(split[1]); + BaseBlock block = getBlock(player, split[1]); Region region = session.getRegion(); int affected = editSession.overlayCuboidBlocks(region, block); @@ -1121,7 +1157,7 @@ public class WorldEditController { if (cut) { checkArgs(split, 0, 1, split[0]); if (split.length > 1) { - getBlock(split[1]); + getBlock(player, split[1]); } } else { checkArgs(split, 0, 0, split[0]); @@ -1202,7 +1238,7 @@ public class WorldEditController { // Replacement block argument if (split.length > 3) { - replace = getBlock(split[3]); + replace = getBlock(player, split[3]); } else { replace = new BaseBlock(0); } @@ -1323,7 +1359,7 @@ public class WorldEditController { Math.max(1, Integer.parseInt(split[1])) : -1; Vector origin = session.getPlacementPosition(player); - int killed = server.killMobs(origin, radius); + int killed = server.killMobs(player.getWorld(), origin, radius); player.print("Killed " + killed + " mobs."); return true; @@ -1365,9 +1401,9 @@ public class WorldEditController { Set chunks = session.getRegion().getChunks(); FileOutputStream out = null; - if (shellSaveType == null) { + if (config.shellSaveType == null) { player.printError("shell-save-type has to be configured in worldedit.properties"); - } else if (shellSaveType.equalsIgnoreCase("bat")) { + } else if (config.shellSaveType.equalsIgnoreCase("bat")) { try { out = new FileOutputStream("worldedit-delchunks.bat"); OutputStreamWriter writer = new OutputStreamWriter(out, "UTF-8"); @@ -1396,7 +1432,7 @@ public class WorldEditController { try { out.close(); } catch (IOException ie) {} } } - } else if (shellSaveType.equalsIgnoreCase("bash")) { + } else if (config.shellSaveType.equalsIgnoreCase("bash")) { try { out = new FileOutputStream("worldedit-delchunks.sh"); OutputStreamWriter writer = new OutputStreamWriter(out, "UTF-8"); @@ -1439,8 +1475,8 @@ public class WorldEditController { int num = split.length > 1 ? Math.min(40, Math.max(5, Integer.parseInt(split[1]))) : 5; - if (snapshotRepo != null) { - Snapshot[] snapshots = snapshotRepo.getSnapshots(); + if (config.snapshotRepo != null) { + Snapshot[] snapshots = config.snapshotRepo.getSnapshots(); if (snapshots.length > 0) { for (byte i = 0; i < Math.min(num, snapshots.length); i++) { @@ -1461,7 +1497,7 @@ public class WorldEditController { } else if (split[0].equalsIgnoreCase("//use")) { checkArgs(split, 1, 1, split[0]); - if (snapshotRepo == null) { + if (config.snapshotRepo == null) { player.printError("Snapshot/backup restore is not configured."); return true; } @@ -1470,7 +1506,7 @@ public class WorldEditController { // Want the latest snapshot? if (name.equalsIgnoreCase("latest")) { - Snapshot snapshot = snapshotRepo.getDefaultSnapshot(); + Snapshot snapshot = config.snapshotRepo.getDefaultSnapshot(); if (snapshot != null) { session.setSnapshot(null); @@ -1480,7 +1516,7 @@ public class WorldEditController { } } else { try { - session.setSnapshot(snapshotRepo.getSnapshot(name)); + session.setSnapshot(config.snapshotRepo.getSnapshot(name)); player.print("Snapshot set to: " + name); } catch (InvalidSnapshotException e) { player.printError("That snapshot does not exist or is not available."); @@ -1493,7 +1529,7 @@ public class WorldEditController { } else if (split[0].equalsIgnoreCase("//restore")) { checkArgs(split, 0, 1, split[0]); - if (snapshotRepo == null) { + if (config.snapshotRepo == null) { player.printError("Snapshot/backup restore is not configured."); return true; } @@ -1503,7 +1539,7 @@ public class WorldEditController { if (split.length > 1) { try { - snapshot = snapshotRepo.getSnapshot(split[1]); + snapshot = config.snapshotRepo.getSnapshot(split[1]); } catch (InvalidSnapshotException e) { player.printError("That snapshot does not exist or is not available."); return true; @@ -1516,7 +1552,7 @@ public class WorldEditController { // No snapshot set? if (snapshot == null) { - snapshot = snapshotRepo.getDefaultSnapshot(); + snapshot = config.snapshotRepo.getDefaultSnapshot(); if (snapshot == null) { player.printError("No snapshots were found."); @@ -1589,25 +1625,25 @@ public class WorldEditController { * @param dir * @return */ - public Vector getDirection(WorldEditPlayer player, String dirStr) + public Vector getDirection(LocalPlayer player, String dirStr) throws UnknownDirectionException { int xm = 0; int ym = 0; int zm = 0; - WorldEditPlayer.DIRECTION dir = null; + LocalPlayer.DIRECTION dir = null; if (dirStr.equals("me")) { dir = player.getCardinalDirection(); } - if (dirStr.charAt(0) == 'u' || dir == WorldEditPlayer.DIRECTION.WEST) { + if (dirStr.charAt(0) == 'u' || dir == LocalPlayer.DIRECTION.WEST) { zm += 1; - } else if (dirStr.charAt(0) == 'e' || dir == WorldEditPlayer.DIRECTION.EAST) { + } else if (dirStr.charAt(0) == 'e' || dir == LocalPlayer.DIRECTION.EAST) { zm -= 1; - } else if (dirStr.charAt(0) == 's' || dir == WorldEditPlayer.DIRECTION.SOUTH) { + } else if (dirStr.charAt(0) == 's' || dir == LocalPlayer.DIRECTION.SOUTH) { xm += 1; - } else if (dirStr.charAt(0) == 'n' || dir == WorldEditPlayer.DIRECTION.NORTH) { + } else if (dirStr.charAt(0) == 'n' || dir == LocalPlayer.DIRECTION.NORTH) { xm -= 1; } else if (dirStr.charAt(0) == 'u') { ym += 1; @@ -1629,21 +1665,21 @@ public class WorldEditController { * @return */ public CuboidClipboard.FlipDirection getFlipDirection( - WorldEditPlayer player, String dirStr) + LocalPlayer player, String dirStr) throws UnknownDirectionException { - WorldEditPlayer.DIRECTION dir = null; + LocalPlayer.DIRECTION dir = null; if (dirStr.equals("me")) { dir = player.getCardinalDirection(); } - if (dirStr.charAt(0) == 'w' || dir == WorldEditPlayer.DIRECTION.EAST) { + if (dirStr.charAt(0) == 'w' || dir == LocalPlayer.DIRECTION.EAST) { return CuboidClipboard.FlipDirection.WEST_EAST; - } else if (dirStr.charAt(0) == 'e' || dir == WorldEditPlayer.DIRECTION.EAST) { + } else if (dirStr.charAt(0) == 'e' || dir == LocalPlayer.DIRECTION.EAST) { return CuboidClipboard.FlipDirection.WEST_EAST; - } else if (dirStr.charAt(0) == 's' || dir == WorldEditPlayer.DIRECTION.SOUTH) { + } else if (dirStr.charAt(0) == 's' || dir == LocalPlayer.DIRECTION.SOUTH) { return CuboidClipboard.FlipDirection.NORTH_SOUTH; - } else if (dirStr.charAt(0) == 'n' || dir == WorldEditPlayer.DIRECTION.SOUTH) { + } else if (dirStr.charAt(0) == 'n' || dir == LocalPlayer.DIRECTION.SOUTH) { return CuboidClipboard.FlipDirection.NORTH_SOUTH; } else if (dirStr.charAt(0) == 'u') { return CuboidClipboard.FlipDirection.UP_DOWN; @@ -1659,7 +1695,7 @@ public class WorldEditController { * * @param player */ - public void removeSession(WorldEditPlayer player) { + public void removeSession(LocalPlayer player) { sessions.remove(player); } @@ -1687,7 +1723,7 @@ public class WorldEditController { * * @param player */ - public void handleDisconnect(WorldEditPlayer player) { + public void handleDisconnect(LocalPlayer player) { removeSession(player); } @@ -1696,7 +1732,7 @@ public class WorldEditController { * * @param player */ - public void handleArmSwing(WorldEditPlayer player) { + public void handleArmSwing(LocalPlayer player) { if (!canUseCommand(player, "//")) return; } @@ -1705,20 +1741,21 @@ public class WorldEditController { * Called on right click. * * @param player + * @param world * @param clicked * @return false if you want the action to go through */ - @SuppressWarnings("deprecation") - public boolean handleBlockRightClick(WorldEditPlayer player, Vector clicked) { + public boolean handleBlockRightClick(LocalPlayer player, LocalWorld world, + Vector clicked) { int itemInHand = player.getItemInHand(); // This prevents needless sessions from being created - if (!hasSession(player) && !(itemInHand == wandItem && + if (!hasSession(player) && !(itemInHand == config.wandItem && canUseCommand(player, "//pos2"))) { return false; } - WorldEditSession session = getSession(player); + LocalSession session = getSession(player); - if (itemInHand == wandItem && session.isToolControlEnabled() + if (itemInHand == config.wandItem && session.isToolControlEnabled() && canUseCommand(player, "//pos2")) { session.setPos2(clicked); try { @@ -1729,36 +1766,9 @@ public class WorldEditController { } return true; - } else if (player.isHoldingPickAxe() - && session.getTool() == WorldEditSession.Tool.TREE) { - EditSession editSession = - new EditSession(session.getBlockChangeLimit()); - - try { - if (!server.generateTree(editSession, clicked)) { - player.printError("Notch won't let you put a tree there."); - } - } finally { - session.remember(editSession); - } - - return true; - } else if (player.isHoldingPickAxe() - && session.getTool() == WorldEditSession.Tool.INFO) { - BaseBlock block = (new EditSession(0)).rawGetBlock(clicked); - - player.print("\u00A79@" + clicked + ": " + "\u00A7e" - + "Type: " + block.getID() + "\u00A77" + " (" - + BlockType.fromID(block.getID()).getName() + ") " - + "\u00A7f" - + "[" + block.getData() + "]"); - - if (block instanceof MobSpawnerBlock) { - player.printRaw("\u00A7e" + "Mob Type: " - + ((MobSpawnerBlock)block).getMobType()); - } - - return true; + } else if (player.isHoldingPickAxe() && session.getTool() != null) { + return session.getTool().act(server, config, player, session, + world, clicked); } return false; @@ -1768,16 +1778,19 @@ public class WorldEditController { * Called on left click. * * @param player + * @param world * @param clicked * @return false if you want the action to go through */ - public boolean handleBlockLeftClick(WorldEditPlayer player, Vector clicked) { + public boolean handleBlockLeftClick(LocalPlayer player, + LocalWorld world, Vector clicked) { + if (!canUseCommand(player, "//pos1") && !canUseCommand(player, "//")) { return false; } - WorldEditSession session = getSession(player); + LocalSession session = getSession(player); - if (player.getItemInHand() == wandItem) { + if (player.getItemInHand() == config.wandItem) { if (session.isToolControlEnabled()) { // Bug workaround if (clicked.getBlockX() == 0 && clicked.getBlockY() == 0 @@ -1802,124 +1815,23 @@ public class WorldEditController { return true; } - } else if (player.isHoldingPickAxe()) { - if (session.hasSuperPickAxe()) { - boolean canBedrock = canUseCommand(player, "/worldeditbedrock"); - - // Single block super pickaxe - if (session.getSuperPickaxeMode() == - WorldEditSession.SuperPickaxeMode.SINGLE) { - if (server.getBlockType(clicked) == 7 && !canBedrock) { - return true; - } else if (server.getBlockType(clicked) == 46) { - return false; - } - - if (superPickaxeDrop) { - server.simulateBlockMine(clicked); - } else { - server.setBlockType(clicked, 0); - } - - // Area super pickaxe - } else if (session.getSuperPickaxeMode() == - WorldEditSession.SuperPickaxeMode.SAME_TYPE_AREA) { - int ox = clicked.getBlockX(); - int oy = clicked.getBlockY(); - int oz = clicked.getBlockZ(); - int size = session.getSuperPickaxeRange(); - int initialType = server.getBlockType(clicked); - - if (initialType == 7 && !canBedrock) { - return true; - } - - for (int x = ox - size; x <= ox + size; x++) { - for (int y = oy - size; y <= oy + size; y++) { - for (int z = oz - size; z <= oz + size; z++) { - Vector pos = new Vector(x, y, z); - if (server.getBlockType(pos) == initialType) { - if (superPickaxeManyDrop) { - server.simulateBlockMine(pos); - } else { - server.setBlockType(pos, 0); - } - } - } - } - } - - return true; - - // Area super pickaxe - } else if (session.getSuperPickaxeMode() == - WorldEditSession.SuperPickaxeMode.SAME_TYPE_RECURSIVE) { - int size = session.getSuperPickaxeRange(); - int initialType = server.getBlockType(clicked); - - if (initialType == 7 && !canBedrock) { - return true; - } - - recursiveSuperPickaxe(clicked.toBlockVector(), clicked, size, - initialType, new HashSet()); - - return true; - } - - return true; + } else if (player.isHoldingPickAxe() && session.hasSuperPickAxe()) { + if (session.getSuperPickaxeMode() != null) { + return session.getSuperPickaxeMode().act(server, config, + player, session, world, clicked); } } return false; } - /** - * Helper method for the recursive super pickaxe. - * - * @param pos - * @param canBedrock - * @return - */ - private void recursiveSuperPickaxe(BlockVector pos, Vector origin, - int size, int initialType, Set visited) { - if (origin.distance(pos) > size || visited.contains(pos)) { - return; - } - - visited.add(pos); - - if (server.getBlockType(pos) == initialType) { - if (superPickaxeManyDrop) { - server.simulateBlockMine(pos); - } else { - server.setBlockType(pos, 0); - } - } else { - return; - } - - recursiveSuperPickaxe(pos.add(1, 0, 0).toBlockVector(), origin, size, - initialType, visited); - recursiveSuperPickaxe(pos.add(-1, 0, 0).toBlockVector(), origin, size, - initialType, visited); - recursiveSuperPickaxe(pos.add(0, 0, 1).toBlockVector(), origin, size, - initialType, visited); - recursiveSuperPickaxe(pos.add(0, 0, -1).toBlockVector(), origin, size, - initialType, visited); - recursiveSuperPickaxe(pos.add(0, 1, 0).toBlockVector(), origin, size, - initialType, visited); - recursiveSuperPickaxe(pos.add(0, -1, 0).toBlockVector(), origin, size, - initialType, visited); - } - /** * * @param player * @param split * @return whether the command was processed */ - public boolean handleCommand(WorldEditPlayer player, String[] split) { + public boolean handleCommand(LocalPlayer player, String[] split) { try { // Legacy /, command if (split[0].equals("/,")) { @@ -1929,21 +1841,22 @@ public class WorldEditController { String searchCmd = split[0].toLowerCase(); if (commands.containsKey(searchCmd) - || (noDoubleSlash && commands.containsKey("/" + searchCmd)) + || (config.noDoubleSlash && commands.containsKey("/" + searchCmd)) || ((searchCmd.length() < 3 || searchCmd.charAt(2) != '/') && commands.containsKey(searchCmd.substring(1)))) { - if (noDoubleSlash && commands.containsKey("/" + searchCmd)) { + if (config.noDoubleSlash && commands.containsKey("/" + searchCmd)) { split[0] = "/" + split[0]; } else if (commands.containsKey(searchCmd.substring(1))) { split[0] = split[0].substring(1); } if (canUseCommand(player, split[0])) { - WorldEditSession session = getSession(player); + LocalSession session = getSession(player); BlockBag blockBag = session.getBlockBag(player); EditSession editSession = - new EditSession(session.getBlockChangeLimit(), blockBag); + new EditSession(server, player.getWorld(), + session.getBlockChangeLimit(), blockBag); editSession.enableQueue(); long start = System.currentTimeMillis(); @@ -1954,9 +1867,9 @@ public class WorldEditController { session.remember(editSession); editSession.flushQueue(); - if (profile) { + if (config.profile) { long time = System.currentTimeMillis() - start; - player.print(( time / 1000.0) + "s elapsed"); + player.print((time / 1000.0) + "s elapsed"); } flushBlockBag(player, editSession); @@ -1979,7 +1892,7 @@ public class WorldEditController { player.printError("Max blocks changed in an operation reached (" + e5.getBlockLimit() + ")."); } catch (MaxRadiusException e) { - player.printError("Maximum radius: " + maxRadius); + player.printError("Maximum radius: " + config.maxRadius); } catch (UnknownDirectionException ue) { player.printError("Unknown direction: " + ue.getDirection()); } catch (InsufficientArgumentsException e6) { @@ -2004,7 +1917,7 @@ public class WorldEditController { * @param blockBag * @param editSession */ - private static void flushBlockBag(WorldEditPlayer player, + private static void flushBlockBag(LocalPlayer player, EditSession editSession) { BlockBag blockBag = editSession.getBlockBag(); @@ -2046,19 +1959,19 @@ public class WorldEditController { * @param command * @return */ - private boolean canUseCommand(WorldEditPlayer player, String command) { + private boolean canUseCommand(LocalPlayer player, String command) { // Allow the /worldeditselect permission if (command.equalsIgnoreCase("//pos1") || command.equalsIgnoreCase("//pos2") || command.equalsIgnoreCase("//hpos1") || command.equalsIgnoreCase("//hpos2")) { return player.hasPermission(command) - || player.hasPermission("/worldeditselect") - || player.hasPermission("/worldedit"); + || player.hasPermission("worldeditselect") + || player.hasPermission("worldedit"); } return player.hasPermission(command.replace("air", "")) - || player.hasPermission("/worldedit"); + || player.hasPermission("worldedit"); } /** @@ -2092,12 +2005,12 @@ public class WorldEditController { * @param player * @return */ - public WorldEditSession getBridgeSession(WorldEditPlayer player) { + public LocalSession getBridgeSession(LocalPlayer player) { if (sessions.containsKey(player)) { return sessions.get(player); } else { - WorldEditSession session = new WorldEditSession(); - session.setBlockChangeLimit(defaultChangeLimit); + LocalSession session = new LocalSession(); + session.setBlockChangeLimit(config.defaultChangeLimit); sessions.put(player, session); return session; } diff --git a/src/com/sk89q/worldedit/WorldVector.java b/src/com/sk89q/worldedit/WorldVector.java new file mode 100644 index 000000000..469cba030 --- /dev/null +++ b/src/com/sk89q/worldedit/WorldVector.java @@ -0,0 +1,104 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldedit; + +/** + * A vector with a world component. + * + * @author sk89q + */ +public class WorldVector extends Vector { + /** + * Represents the world. + */ + private LocalWorld world; + + /** + * Construct the Vector object. + * + * @param x + * @param y + * @param z + */ + public WorldVector(LocalWorld world, double x, double y, double z) { + super(x, y, z); + this.world = world; + } + + /** + * Construct the Vector object. + * + * @param x + * @param y + * @param z + */ + public WorldVector(LocalWorld world, int x, int y, int z) { + super(x, y, z); + this.world = world; + } + + /** + * Construct the Vector object. + * + * @param x + * @param y + * @param z + */ + public WorldVector(LocalWorld world, float x, float y, float z) { + super(x, y, z); + this.world = world; + } + + /** + * Construct the Vector object. + * + * @param pt + */ + public WorldVector(LocalWorld world, Vector pt) { + super(pt); + this.world = world; + } + + /** + * Construct the Vector object. + */ + public WorldVector(LocalWorld world) { + super(); + this.world = world; + } + + /** + * Get the world. + * + * @return + */ + public LocalWorld getWorld() { + return world; + } + + /** + * Gets a BlockVector version. + * + * @return BlockWorldVector + */ + public BlockWorldVector toWorldBlockVector() { + return new BlockWorldVector(this); + } +} diff --git a/src/com/sk89q/worldedit/cli/Main.java b/src/com/sk89q/worldedit/cli/Main.java new file mode 100644 index 000000000..d4c4e9551 --- /dev/null +++ b/src/com/sk89q/worldedit/cli/Main.java @@ -0,0 +1,94 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldedit.cli; + +import static java.util.Arrays.*; +import java.util.List; +import joptsimple.OptionParser; +import joptsimple.OptionSet; +import joptsimple.OptionSpec; + +/** + * + * @author sk89q + */ +public class Main { + /** + * @param args + */ + public static void main(String[] _args) throws Throwable { + OptionParser parser = new OptionParser(); + OptionSpec world = + parser.accepts("world").withRequiredArg().ofType(String.class) + .describedAs("world directory").defaultsTo("world"); + parser.acceptsAll(asList("?", "h"), "show help"); + + OptionSet options = parser.parse(_args); + List args = options.nonOptionArguments(); + + if (args.size() != 1 || options.has("?")) { + System.err.println("worldedit "); + System.err.println(); + parser.printHelpOn(System.err); + return; + } + + System.err.println("WorldEdit v" + getVersion()); + System.err.println("Copyright (c) 2010-2011 sk89q "); + System.err.println(); + + String act = args.get(0); + String worldPath = options.valueOf(world); + + if (act.equalsIgnoreCase("check")) { + new WorldChecker(worldPath); + } else { + System.err.println("Only valid action is 'check'."); + } + } + + /** + * Get the version. + * + * @return + */ + public static String getVersion() { + Package p = com.sk89q.worldedit.cli.Main.class.getPackage(); + + if (p == null) { + p = Package.getPackage("com.sk89q.worldedit"); + } + + String version; + + if (p == null) { + return "(unknown)"; + } else { + version = p.getImplementationVersion(); + + if (version == null) { + return "(unknown)"; + } + } + + return version; + } + +} diff --git a/src/com/sk89q/worldedit/cli/WorldChecker.java b/src/com/sk89q/worldedit/cli/WorldChecker.java new file mode 100644 index 000000000..eb4b3f35b --- /dev/null +++ b/src/com/sk89q/worldedit/cli/WorldChecker.java @@ -0,0 +1,92 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldedit.cli; + +import java.io.*; +import java.nio.*; +import java.util.regex.Pattern; + +import org.jnbt.NBTInputStream; +import org.jnbt.Tag; + +public class WorldChecker { + private File worldPath; + + public WorldChecker(String path) { + worldPath = new File(path); + + checkLevelDat(); + checkFiles(); + + System.out.println("Done."); + } + + public void checkLevelDat() { + try { + checkNBT(new File(worldPath, "level.dat")); + } catch (IOException e) { + System.out.println("BAD: level.dat: " + e.getMessage()); + } + } + + public void checkFiles() { + final Pattern chunkFilePattern = Pattern.compile("^c\\..*\\.dat$"); + + FileFilter folderFilter = new FileFilter() { + @Override + public boolean accept(File f) { + return f.isDirectory(); + } + }; + + FileFilter chunkFilter = new FileFilter() { + @Override + public boolean accept(File f) { + return f.isFile() + && chunkFilePattern.matcher(f.getName()).matches(); + } + }; + + for (File l1 : worldPath.listFiles(folderFilter)) { + for (File l2 : l1.listFiles(folderFilter)) { + for (File chunkFile : l2.listFiles(chunkFilter)) { + checkChunkFile(chunkFile, + l1.getName(), l2.getName(), chunkFile.getName()); + } + } + } + } + + public void checkChunkFile(File f, String a, String b, String c) { + String id = a + "/" + b + "/" + c; + + try { + checkNBT(f); + } catch (IOException e) { + System.out.println("BAD: " + id); + } + } + + public void checkNBT(File file) throws IOException { + FileInputStream stream = new FileInputStream(file); + NBTInputStream nbt = new NBTInputStream(stream); + Tag tag = nbt.readTag(); + } +} diff --git a/src/com/sk89q/worldedit/superpickaxe/AreaPickaxe.java b/src/com/sk89q/worldedit/superpickaxe/AreaPickaxe.java new file mode 100644 index 000000000..7f3d6fbec --- /dev/null +++ b/src/com/sk89q/worldedit/superpickaxe/AreaPickaxe.java @@ -0,0 +1,82 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldedit.superpickaxe; + +import com.sk89q.worldedit.*; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.blocks.BlockID; + +/** + * A super pickaxe mode that will remove blocks in an area. + * + * @author sk89q + */ +public class AreaPickaxe implements SuperPickaxeMode { + private static final BaseBlock air = new BaseBlock(0); + private int range; + + public AreaPickaxe(int range) { + this.range = range; + } + + @Override + public boolean act(ServerInterface server, LocalConfiguration config, + LocalPlayer player, LocalSession session, LocalWorld world, + Vector clicked) { + int ox = clicked.getBlockX(); + int oy = clicked.getBlockY(); + int oz = clicked.getBlockZ(); + int initialType = server.getBlockType(world, clicked); + + if (initialType == 0) { + return true; + } + + if (initialType == BlockID.BEDROCK && !player.canDestroyBedrock()) { + return true; + } + + EditSession editSession = new EditSession(server, world, + session.getBlockChangeLimit()); + + try { + for (int x = ox - range; x <= ox + range; x++) { + for (int y = oy - range; y <= oy + range; y++) { + for (int z = oz - range; z <= oz + range; z++) { + Vector pos = new Vector(x, y, z); + if (server.getBlockType(world, pos) == initialType) { + if (config.superPickaxeManyDrop) { + server.simulateBlockMine(world, pos); + } + + editSession.setBlock(pos, air); + } + } + } + } + } catch (MaxChangedBlocksException e) { + player.printError("Max blocks change limit reached."); + } finally { + session.remember(editSession); + } + + return true; + } +} diff --git a/src/com/sk89q/worldedit/superpickaxe/BlockReplacer.java b/src/com/sk89q/worldedit/superpickaxe/BlockReplacer.java new file mode 100644 index 000000000..d803ffac7 --- /dev/null +++ b/src/com/sk89q/worldedit/superpickaxe/BlockReplacer.java @@ -0,0 +1,54 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldedit.superpickaxe; + +import com.sk89q.worldedit.*; +import com.sk89q.worldedit.blocks.BaseBlock; + +/** + * A smode that replaces one block. + * + * @author sk89q + */ +public class BlockReplacer implements SuperPickaxeMode { + private BaseBlock targetBlock; + + public BlockReplacer(BaseBlock targetBlock) { + this.targetBlock = targetBlock; + } + + @Override + public boolean act(ServerInterface server, LocalConfiguration config, + LocalPlayer player, LocalSession session, LocalWorld world, + Vector clicked) { + + EditSession editSession = new EditSession(server, world, -1); + + try { + editSession.setBlock(clicked, targetBlock); + } catch (MaxChangedBlocksException e) { + } finally { + session.remember(editSession); + } + + return true; + } + +} diff --git a/src/com/sk89q/worldedit/superpickaxe/QueryTool.java b/src/com/sk89q/worldedit/superpickaxe/QueryTool.java new file mode 100644 index 000000000..524c4695f --- /dev/null +++ b/src/com/sk89q/worldedit/superpickaxe/QueryTool.java @@ -0,0 +1,52 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldedit.superpickaxe; + +import com.sk89q.worldedit.*; +import com.sk89q.worldedit.blocks.*; + +/** + * Plants a tree. + * + * @author sk89q + */ +public class QueryTool implements SuperPickaxeMode { + + @Override + public boolean act(ServerInterface server, LocalConfiguration config, + LocalPlayer player, LocalSession session, LocalWorld world, + Vector clicked) { + BaseBlock block = (new EditSession(server, world, 0)).rawGetBlock(clicked); + + player.print("\u00A79@" + clicked + ": " + "\u00A7e" + + "Type: " + block.getID() + "\u00A77" + " (" + + BlockType.fromID(block.getID()).getName() + ") " + + "\u00A7f" + + "[" + block.getData() + "]"); + + if (block instanceof MobSpawnerBlock) { + player.printRaw("\u00A7e" + "Mob Type: " + + ((MobSpawnerBlock)block).getMobType()); + } + + return true; + } + +} diff --git a/src/com/sk89q/worldedit/superpickaxe/RecursivePickaxe.java b/src/com/sk89q/worldedit/superpickaxe/RecursivePickaxe.java new file mode 100644 index 000000000..077050c68 --- /dev/null +++ b/src/com/sk89q/worldedit/superpickaxe/RecursivePickaxe.java @@ -0,0 +1,119 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldedit.superpickaxe; + +import java.util.HashSet; +import java.util.Set; +import com.sk89q.worldedit.*; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.blocks.BlockID; + +/** + * A pickaxe mode that recursively finds adjacent blocks within range of + * an initial block and of the same type. + * + * @author sk89q + */ +public class RecursivePickaxe implements SuperPickaxeMode { + private static final BaseBlock air = new BaseBlock(0); + private int range; + + public RecursivePickaxe(int range) { + this.range = range; + } + + @Override + public boolean act(ServerInterface server, LocalConfiguration config, + LocalPlayer player, LocalSession session, LocalWorld world, + Vector clicked) { + int initialType = server.getBlockType(world, clicked); + + if (initialType == 0) { + return true; + } + + if (initialType == BlockID.BEDROCK && !player.canDestroyBedrock()) { + return true; + } + + EditSession editSession = new EditSession(server, world, + session.getBlockChangeLimit()); + + try { + recurse(server, editSession, world, clicked.toBlockVector(), + clicked, range, initialType, new HashSet(), + config.superPickaxeManyDrop); + } catch (MaxChangedBlocksException e) { + player.printError("Max blocks change limit reached."); + } finally { + session.remember(editSession); + } + + return true; + } + + /** + * Helper method. + * + * @param server + * @param superPickaxeManyDrop + * @param world + * @param pos + * @param origin + * @param size + * @param initialType + * @param visited + */ + private void recurse(ServerInterface server, EditSession editSession, + LocalWorld world, BlockVector pos, + Vector origin, int size, int initialType, + Set visited, boolean drop) + throws MaxChangedBlocksException { + + if (origin.distance(pos) > size || visited.contains(pos)) { + return; + } + + visited.add(pos); + + if (editSession.getBlock(pos).getID() == initialType) { + if (drop) { + server.simulateBlockMine(world, pos); + } + editSession.setBlock(pos, air); + } else { + return; + } + + recurse(server, editSession, world, pos.add(1, 0, 0).toBlockVector(), + origin, size, initialType, visited, drop); + recurse(server, editSession, world, pos.add(-1, 0, 0).toBlockVector(), + origin, size, initialType, visited, drop); + recurse(server, editSession, world, pos.add(0, 0, 1).toBlockVector(), + origin, size, initialType, visited, drop); + recurse(server, editSession, world, pos.add(0, 0, -1).toBlockVector(), + origin, size, initialType, visited, drop); + recurse(server, editSession, world, pos.add(0, 1, 0).toBlockVector(), + origin, size, initialType, visited, drop); + recurse(server, editSession, world, pos.add(0, -1, 0).toBlockVector(), + origin, size, initialType, visited, drop); + } + +} diff --git a/src/com/sk89q/worldedit/superpickaxe/SinglePickaxe.java b/src/com/sk89q/worldedit/superpickaxe/SinglePickaxe.java new file mode 100644 index 000000000..fe85aa562 --- /dev/null +++ b/src/com/sk89q/worldedit/superpickaxe/SinglePickaxe.java @@ -0,0 +1,52 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldedit.superpickaxe; + +import com.sk89q.worldedit.*; +import com.sk89q.worldedit.blocks.BlockID; + +/** + * A super pickaxe mode that removes one block. + * + * @author sk89q + */ +public class SinglePickaxe implements SuperPickaxeMode { + @Override + public boolean act(ServerInterface server, LocalConfiguration config, + LocalPlayer player, LocalSession session, LocalWorld world, + Vector clicked) { + + if (server.getBlockType(world, clicked) == BlockID.BEDROCK + && !player.canDestroyBedrock()) { + return true; + } else if (server.getBlockType(world, clicked) == BlockID.TNT) { + return false; + } + + if (config.superPickaxeDrop) { + server.simulateBlockMine(world, clicked); + } + + server.setBlockType(world, clicked, 0); + + return true; + } + +} diff --git a/src/com/sk89q/worldedit/superpickaxe/SuperPickaxeMode.java b/src/com/sk89q/worldedit/superpickaxe/SuperPickaxeMode.java new file mode 100644 index 000000000..f8835c549 --- /dev/null +++ b/src/com/sk89q/worldedit/superpickaxe/SuperPickaxeMode.java @@ -0,0 +1,42 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldedit.superpickaxe; + +import com.sk89q.worldedit.*; + +/** + * Represents a super pickaxe mode. + * + * @author sk89q + */ +public interface SuperPickaxeMode { + /** + * Perform the action. Should return true to deny the default + * action. + * + * @param player + * @param session + * @param clicked + * @return true to deny + */ + public boolean act(ServerInterface server, LocalConfiguration config, + LocalPlayer player, LocalSession session, LocalWorld world, + Vector clicked); +} diff --git a/src/com/sk89q/worldedit/superpickaxe/TreePlanter.java b/src/com/sk89q/worldedit/superpickaxe/TreePlanter.java new file mode 100644 index 000000000..67a40ce4b --- /dev/null +++ b/src/com/sk89q/worldedit/superpickaxe/TreePlanter.java @@ -0,0 +1,49 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldedit.superpickaxe; + +import com.sk89q.worldedit.*; + +/** + * Plants a tree. + * + * @author sk89q + */ +public class TreePlanter implements SuperPickaxeMode { + + @Override + public boolean act(ServerInterface server, LocalConfiguration config, + LocalPlayer player, LocalSession session, LocalWorld world, + Vector clicked) { + EditSession editSession = + new EditSession(server, world, session.getBlockChangeLimit()); + + try { + if (!server.generateTree(editSession, player.getWorld(), clicked)) { + player.printError("Notch won't let you put a tree there."); + } + } finally { + session.remember(editSession); + } + + return true; + } + +} diff --git a/src/joptsimple/AbstractOptionSpec.java b/src/joptsimple/AbstractOptionSpec.java new file mode 100644 index 000000000..74be60a83 --- /dev/null +++ b/src/joptsimple/AbstractOptionSpec.java @@ -0,0 +1,124 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import static java.util.Collections.*; + +import static joptsimple.internal.Strings.*; + +/** + * @param represents the type of the arguments this option accepts + * @author Paul Holser + * @version $Id: AbstractOptionSpec.java,v 1.19 2009/11/28 21:31:53 pholser Exp $ + */ +abstract class AbstractOptionSpec implements OptionSpec { + private final List options = new ArrayList(); + private final String description; + + protected AbstractOptionSpec( String option ) { + this( singletonList( option ), EMPTY ); + } + + protected AbstractOptionSpec( Collection options, String description ) { + arrangeOptions( options ); + + this.description = description; + } + + public final Collection options() { + return unmodifiableCollection( options ); + } + + public final List values( OptionSet detectedOptions ) { + return detectedOptions.valuesOf( this ); + } + + public final V value( OptionSet detectedOptions ) { + return detectedOptions.valueOf( this ); + } + + abstract List defaultValues(); + + String description() { + return description; + } + + protected abstract V convert( String argument ); + + abstract void handleOption( OptionParser parser, ArgumentList arguments, OptionSet detectedOptions, + String detectedArgument ); + + abstract boolean acceptsArguments(); + + abstract boolean requiresArgument(); + + abstract void accept( OptionSpecVisitor visitor ); + + private void arrangeOptions( Collection unarranged ) { + if ( unarranged.size() == 1 ) { + options.addAll( unarranged ); + return; + } + + List shortOptions = new ArrayList(); + List longOptions = new ArrayList(); + + for ( String each : unarranged ) { + if ( each.length() == 1 ) + shortOptions.add( each ); + else + longOptions.add( each ); + } + + sort( shortOptions ); + sort( longOptions ); + + options.addAll( shortOptions ); + options.addAll( longOptions ); + } + + @Override + public boolean equals( Object that ) { + if ( !( that instanceof AbstractOptionSpec ) ) + return false; + + AbstractOptionSpec other = (AbstractOptionSpec) that; + return options.equals( other.options ); + } + + @Override + public int hashCode() { + return options.hashCode(); + } + + @Override + public String toString() { + return options.toString(); + } +} diff --git a/src/joptsimple/AlternativeLongOptionSpec.java b/src/joptsimple/AlternativeLongOptionSpec.java new file mode 100644 index 000000000..2b5b4ffac --- /dev/null +++ b/src/joptsimple/AlternativeLongOptionSpec.java @@ -0,0 +1,57 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +import static java.util.Collections.*; + +import static joptsimple.ParserRules.*; + +/** + *

Represents the "-W" form of long option specification.

+ * + * @author Paul Holser + * @version $Id: AlternativeLongOptionSpec.java,v 1.13 2009/09/28 01:12:48 pholser Exp $ + */ +class AlternativeLongOptionSpec extends ArgumentAcceptingOptionSpec { + AlternativeLongOptionSpec() { + super( singletonList( RESERVED_FOR_EXTENSIONS ), true, "Alternative form of long options" ); + + describedAs( "opt=value" ); + } + + @Override + protected void detectOptionArgument( OptionParser parser, ArgumentList arguments, OptionSet detectedOptions ) { + if ( !arguments.hasMore() ) + throw new OptionMissingRequiredArgumentException( options() ); + + arguments.treatNextAsLongOption(); + } + + @Override + void accept( OptionSpecVisitor visitor ) { + visitor.visit( this ); + } +} diff --git a/src/joptsimple/ArgumentAcceptingOptionSpec.java b/src/joptsimple/ArgumentAcceptingOptionSpec.java new file mode 100644 index 000000000..60cb209ce --- /dev/null +++ b/src/joptsimple/ArgumentAcceptingOptionSpec.java @@ -0,0 +1,301 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.StringTokenizer; +import static java.util.Collections.*; + +import joptsimple.internal.ReflectionException; +import static joptsimple.internal.Objects.*; +import static joptsimple.internal.Reflection.*; +import static joptsimple.internal.Strings.*; + +/** + *

Specification of an option that accepts an argument.

+ * + *

Instances are returned from {@link OptionSpecBuilder} methods to allow the formation + * of parser directives as sentences in a "fluent interface" language. For example:

+ * + *
+ *   
+ *   OptionParser parser = new OptionParser();
+ *   parser.accepts( "c" ).withRequiredArg().ofType( Integer.class );
+ *   
+ * 
+ * + *

If no methods are invoked on an instance of this class, then that instance's option + * will treat its argument as a {@link String}.

+ * + * @param represents the type of the arguments this option accepts + * @author Paul Holser + * @version $Id: ArgumentAcceptingOptionSpec.java,v 1.43 2009/10/25 18:37:06 pholser Exp $ + */ +public abstract class ArgumentAcceptingOptionSpec extends AbstractOptionSpec { + private static final char NIL_VALUE_SEPARATOR = '\u0000'; + + private final boolean argumentRequired; + private ValueConverter converter; + private String argumentDescription = ""; + private String valueSeparator = String.valueOf( NIL_VALUE_SEPARATOR ); + private final List defaultValues = new ArrayList(); + + ArgumentAcceptingOptionSpec( String option, boolean argumentRequired ) { + super( option ); + + this.argumentRequired = argumentRequired; + } + + ArgumentAcceptingOptionSpec( Collection options, boolean argumentRequired, String description ) { + super( options, description ); + + this.argumentRequired = argumentRequired; + } + + /** + *

Specifies a type to which arguments of this spec's option are to be + * converted.

+ * + *

JOpt Simple accepts types that have either:

+ * + *
    + *
  1. a public static method called {@code valueOf} which accepts a single + * argument of type {@link String} and whose return type is the same as the class + * on which the method is declared. The {@code java.lang} primitive wrapper + * classes have such methods.
  2. + * + *
  3. a public constructor which accepts a single argument of type + * {@link String}.
  4. + *
+ * + *

This class converts arguments using those methods in that order; that is, + * {@code valueOf} would be invoked before a one-{@link String}-arg constructor + * would.

+ * + *

Invoking this method will trump any previous calls to this method or to + * {@link #withValuesConvertedBy(ValueConverter)}. + * + * @param represents the runtime class of the desired option argument type + * @param argumentType desired type of arguments to this spec's option + * @return self, so that the caller can add clauses to the fluent interface sentence + * @throws NullPointerException if the type is {@code null} + * @throws IllegalArgumentException if the type does not have the standard conversion + * methods + */ + public final ArgumentAcceptingOptionSpec ofType( Class argumentType ) { + return withValuesConvertedBy( findConverter( argumentType ) ); + } + + /** + *

Specifies a converter to use to translate arguments of this spec's option into + * Java objects. This is useful when converting to types that do not have the + * requisite factory method or constructor for {@link #ofType(Class)}.

+ * + *

Invoking this method will trump any previous calls to this method or to + * {@link #ofType(Class)}. + * + * @param represents the runtime class of the desired option argument type + * @param aConverter the converter to use + * @return self, so that the caller can add clauses to the fluent interface sentence + * @throws NullPointerException if the converter is {@code null} + */ + @SuppressWarnings( "unchecked" ) + public final ArgumentAcceptingOptionSpec withValuesConvertedBy( ValueConverter aConverter ) { + if ( aConverter == null ) + throw new NullPointerException( "illegal null converter" ); + + converter = (ValueConverter) aConverter; + return (ArgumentAcceptingOptionSpec) this; + } + + /** + *

Specifies a description for the argument of the option that this spec + * represents. This description is used when generating help information about + * the parser.

+ * + * @param description describes the nature of the argument of this spec's + * option + * @return self, so that the caller can add clauses to the fluent interface sentence + */ + public final ArgumentAcceptingOptionSpec describedAs( String description ) { + argumentDescription = description; + return this; + } + + /** + *

Specifies a value separator for the argument of the option that this spec + * represents. This allows a single option argument to represent multiple values + * for the option. For example:

+ * + *
+     *   
+     *   parser.accepts( "z" ).withRequiredArg()
+     *       .withValuesSeparatedBy( ',' );
+     *   OptionSet options = parser.parse( new String[] { "-z", "foo,bar,baz", "-z",
+     *       "fizz", "-z", "buzz" } );
+     *   
+     * 
+ * + *

Then {@code options.valuesOf( "z" )} would yield the list {@code [foo, bar, + * baz, fizz, buzz]}.

+ * + *

You cannot use Unicode U+0000 as the separator.

+ * + * @param separator a character separator + * @return self, so that the caller can add clauses to the fluent interface sentence + * @throws IllegalArgumentException if the separator is Unicode U+0000 + */ + public final ArgumentAcceptingOptionSpec withValuesSeparatedBy( char separator ) { + if ( separator == NIL_VALUE_SEPARATOR ) + throw new IllegalArgumentException( "cannot use U+0000 as separator" ); + + valueSeparator = String.valueOf( separator ); + return this; + } + + /** + *

Specifies a set of default values for the argument of the option that this spec + * represents.

+ * + * @param value the first in the set of default argument values for this spec's option + * @param values the (optional) remainder of the set of default argument values for this spec's option + * @return self, so that the caller can add clauses to the fluent interface sentence + * @throws NullPointerException if {@code value}, {@code values}, or any elements of {@code values} are + * {@code null} + */ + public ArgumentAcceptingOptionSpec defaultsTo( V value, V... values ) { + addDefaultValue( value ); + for ( V each : values ) + addDefaultValue( each ); + + return this; + } + + private void addDefaultValue( V value ) { + ensureNotNull( value ); + defaultValues.add( value ); + } + + @Override + final void handleOption( OptionParser parser, ArgumentList arguments, OptionSet detectedOptions, + String detectedArgument ) { + + if ( isNullOrEmpty( detectedArgument ) ) + detectOptionArgument( parser, arguments, detectedOptions ); + else + addArguments( detectedOptions, detectedArgument ); + } + + protected void addArguments( OptionSet detectedOptions, String detectedArgument ) { + StringTokenizer lexer = new StringTokenizer( detectedArgument, valueSeparator ); + if ( !lexer.hasMoreTokens() ) + detectedOptions.addWithArgument( this, detectedArgument ); + else { + while ( lexer.hasMoreTokens() ) + detectedOptions.addWithArgument( this, lexer.nextToken() ); + } + } + + protected abstract void detectOptionArgument( OptionParser parser, ArgumentList arguments, + OptionSet detectedOptions ); + + @SuppressWarnings( "unchecked" ) + @Override + protected final V convert( String argument ) { + if ( converter == null ) + return (V) argument; + + try { + return converter.convert( argument ); + } + catch ( ReflectionException ex ) { + throw new OptionArgumentConversionException( options(), argument, converter.valueType(), ex ); + } + catch ( ValueConversionException ex ) { + throw new OptionArgumentConversionException( options(), argument, converter.valueType(), ex ); + } + } + + protected boolean canConvertArgument( String argument ) { + StringTokenizer lexer = new StringTokenizer( argument, valueSeparator ); + + try { + while ( lexer.hasMoreTokens() ) + convert( lexer.nextToken() ); + return true; + } + catch ( OptionException ignored ) { + return false; + } + } + + protected boolean isArgumentOfNumberType() { + return converter != null && Number.class.isAssignableFrom( converter.valueType() ); + } + + @Override + boolean acceptsArguments() { + return true; + } + + @Override + boolean requiresArgument() { + return argumentRequired; + } + + String argumentDescription() { + return argumentDescription; + } + + String typeIndicator() { + if ( converter == null ) + return null; + + String pattern = converter.valuePattern(); + return pattern == null ? converter.valueType().getName() : pattern; + } + + @Override + List defaultValues() { + return unmodifiableList( defaultValues ); + } + + @Override + public boolean equals( Object that ) { + if ( !super.equals( that ) ) + return false; + + ArgumentAcceptingOptionSpec other = (ArgumentAcceptingOptionSpec) that; + return requiresArgument() == other.requiresArgument(); + } + + @Override + public int hashCode() { + return super.hashCode() ^ ( argumentRequired ? 0 : 1 ); + } +} diff --git a/src/joptsimple/ArgumentList.java b/src/joptsimple/ArgumentList.java new file mode 100644 index 000000000..53c04224b --- /dev/null +++ b/src/joptsimple/ArgumentList.java @@ -0,0 +1,60 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +import static joptsimple.ParserRules.*; + +/** + *

Wrapper for an array of command line arguments.

+ * + * @author Paul Holser + * @version $Id: ArgumentList.java,v 1.7 2009/08/13 00:34:35 pholser Exp $ + */ +class ArgumentList { + private final String[] arguments; + private int currentIndex; + + ArgumentList( String... arguments ) { + this.arguments = arguments.clone(); + } + + boolean hasMore() { + return currentIndex < arguments.length; + } + + String next() { + return arguments[ currentIndex++ ]; + } + + String peek() { + return arguments[ currentIndex ]; + } + + void treatNextAsLongOption() { + if ( HYPHEN_CHAR != arguments[ currentIndex ].charAt( 0 ) ) + arguments[ currentIndex ] = DOUBLE_HYPHEN + arguments[ currentIndex ]; + } +} diff --git a/src/joptsimple/HelpFormatter.java b/src/joptsimple/HelpFormatter.java new file mode 100644 index 000000000..e04ea38b1 --- /dev/null +++ b/src/joptsimple/HelpFormatter.java @@ -0,0 +1,149 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import joptsimple.internal.ColumnarData; +import static joptsimple.ParserRules.*; +import static joptsimple.internal.Classes.*; +import static joptsimple.internal.Strings.*; + +/** + *

Produces text for a help screen given a set of options.

+ * + * @author Paul Holser + * @version $Id: HelpFormatter.java,v 1.19 2009/10/04 00:13:41 pholser Exp $ + */ +class HelpFormatter implements OptionSpecVisitor { + private final ColumnarData grid; + + HelpFormatter() { + grid = new ColumnarData( "Option", "Description" ); + } + + String format( Map> options ) { + if ( options.isEmpty() ) + return "No options specified"; + + grid.clear(); + + Comparator> comparator = + new Comparator>() { + public int compare( AbstractOptionSpec first, AbstractOptionSpec second ) { + return first.options().iterator().next().compareTo( second.options().iterator().next() ); + } + }; + + Set> sorted = new TreeSet>( comparator ); + sorted.addAll( options.values() ); + + for ( AbstractOptionSpec each : sorted ) + each.accept( this ); + + return grid.format(); + } + + void addHelpLineFor( AbstractOptionSpec spec, String additionalInfo ) { + grid.addRow( createOptionDisplay( spec ) + additionalInfo, createDescriptionDisplay( spec ) ); + } + + public void visit( NoArgumentOptionSpec spec ) { + addHelpLineFor( spec, "" ); + } + + public void visit( RequiredArgumentOptionSpec spec ) { + visit( spec, '<', '>' ); + } + + public void visit( OptionalArgumentOptionSpec spec ) { + visit( spec, '[', ']' ); + } + + public void visit( AlternativeLongOptionSpec spec ) { + addHelpLineFor( spec, ' ' + surround( spec.argumentDescription(), '<', '>' ) ); + } + + private void visit( ArgumentAcceptingOptionSpec spec, char begin, char end ) { + String argDescription = spec.argumentDescription(); + String typeIndicator = typeIndicator( spec ); + StringBuilder collector = new StringBuilder(); + + if ( typeIndicator.length() > 0 ) { + collector.append( typeIndicator ); + + if ( argDescription.length() > 0 ) + collector.append( ": " ).append( argDescription ); + } + else if ( argDescription.length() > 0 ) + collector.append( argDescription ); + + String helpLine = collector.length() == 0 + ? "" + : ' ' + surround( collector.toString(), begin, end ); + addHelpLineFor( spec, helpLine ); + } + + private String createOptionDisplay( AbstractOptionSpec spec ) { + StringBuilder buffer = new StringBuilder(); + + for ( Iterator iter = spec.options().iterator(); iter.hasNext(); ) { + String option = iter.next(); + buffer.append( option.length() > 1 ? DOUBLE_HYPHEN : HYPHEN ); + buffer.append( option ); + + if ( iter.hasNext() ) + buffer.append( ", " ); + } + + return buffer.toString(); + } + + private String createDescriptionDisplay( AbstractOptionSpec spec ) { + List defaultValues = spec.defaultValues(); + if ( defaultValues.isEmpty() ) + return spec.description(); + + String defaultValuesDisplay = createDefaultValuesDisplay( defaultValues ); + return spec.description() + ' ' + surround( "default: " + defaultValuesDisplay, '(', ')' ); + } + + private String createDefaultValuesDisplay( List defaultValues ) { + return defaultValues.size() == 1 ? defaultValues.get( 0 ).toString() : defaultValues.toString(); + } + + private static String typeIndicator( ArgumentAcceptingOptionSpec spec ) { + String indicator = spec.typeIndicator(); + return indicator == null || String.class.getName().equals( indicator ) + ? "" + : shortNameOf( indicator ); + } +} diff --git a/src/joptsimple/IllegalOptionClusterException.java b/src/joptsimple/IllegalOptionClusterException.java new file mode 100644 index 000000000..09a40abb7 --- /dev/null +++ b/src/joptsimple/IllegalOptionClusterException.java @@ -0,0 +1,48 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +import static java.util.Collections.*; + +/** + *

Thrown when the option parser discovers a cluster of short options in which + * at least one of the short options can accept arguments.

+ * + * @author Paul Holser + * @version $Id: IllegalOptionClusterException.java,v 1.11 2009/10/25 18:37:06 pholser Exp $ + */ +class IllegalOptionClusterException extends OptionException { + private static final long serialVersionUID = -1L; + + IllegalOptionClusterException( String option ) { + super( singletonList( option ) ); + } + + @Override + public String getMessage() { + return "Option cluster containing " + singleOptionMessage() + " is illegal"; + } +} diff --git a/src/joptsimple/IllegalOptionSpecificationException.java b/src/joptsimple/IllegalOptionSpecificationException.java new file mode 100644 index 000000000..677dec777 --- /dev/null +++ b/src/joptsimple/IllegalOptionSpecificationException.java @@ -0,0 +1,48 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +import static java.util.Collections.*; + +/** + *

Thrown when the option parser is asked to recognize an option with illegal + * characters in it.

+ * + * @author Paul Holser + * @version $Id: IllegalOptionSpecificationException.java,v 1.11 2009/10/25 18:37:06 pholser Exp $ + */ +class IllegalOptionSpecificationException extends OptionException { + private static final long serialVersionUID = -1L; + + IllegalOptionSpecificationException( String option ) { + super( singletonList( option ) ); + } + + @Override + public String getMessage() { + return singleOptionMessage() + " is not a legal option character"; + } +} diff --git a/src/joptsimple/MultipleArgumentsForOptionException.java b/src/joptsimple/MultipleArgumentsForOptionException.java new file mode 100644 index 000000000..69105c3bd --- /dev/null +++ b/src/joptsimple/MultipleArgumentsForOptionException.java @@ -0,0 +1,48 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +import java.util.Collection; + +/** + *

Thrown when asking an {@link OptionSet} for a single argument of an option when + * many have been specified.

+ * + * @author Paul Holser + * @version $Id: MultipleArgumentsForOptionException.java,v 1.14 2009/10/25 18:37:06 pholser Exp $ + */ +class MultipleArgumentsForOptionException extends OptionException { + private static final long serialVersionUID = -1L; + + MultipleArgumentsForOptionException( Collection options ) { + super( options ); + } + + @Override + public String getMessage() { + return "Found multiple arguments for option " + multipleOptionMessage() + ", but you asked for only one"; + } +} diff --git a/src/joptsimple/NoArgumentOptionSpec.java b/src/joptsimple/NoArgumentOptionSpec.java new file mode 100644 index 000000000..69685ae3e --- /dev/null +++ b/src/joptsimple/NoArgumentOptionSpec.java @@ -0,0 +1,79 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +import java.util.Collection; +import java.util.List; + +import static java.util.Collections.*; + +/** + *

A specification for an option that does not accept arguments.

+ * + * @author Paul Holser + * @version $Id: NoArgumentOptionSpec.java,v 1.16 2009/10/04 00:13:41 pholser Exp $ + */ +class NoArgumentOptionSpec extends AbstractOptionSpec { + NoArgumentOptionSpec( String option ) { + this( singletonList( option ), "" ); + } + + NoArgumentOptionSpec( Collection options, String description ) { + super( options, description ); + } + + @Override + void handleOption( OptionParser parser, ArgumentList arguments, + OptionSet detectedOptions, String detectedArgument ) { + + detectedOptions.add( this ); + } + + @Override + boolean acceptsArguments() { + return false; + } + + @Override + boolean requiresArgument() { + return false; + } + + @Override + void accept( OptionSpecVisitor visitor ) { + visitor.visit( this ); + } + + @Override + protected Void convert( String argument ) { + return null; + } + + @Override + List defaultValues() { + return emptyList(); + } +} diff --git a/src/joptsimple/OptionArgumentConversionException.java b/src/joptsimple/OptionArgumentConversionException.java new file mode 100644 index 000000000..9defed612 --- /dev/null +++ b/src/joptsimple/OptionArgumentConversionException.java @@ -0,0 +1,56 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +import java.util.Collection; + +/** + *

Thrown when a problem occurs converting an argument of an option from {@link String} + * to another type.

+ * + * @author Paul Holser + * @version $Id: OptionArgumentConversionException.java,v 1.16 2009/10/25 18:37:06 pholser Exp $ + */ +class OptionArgumentConversionException extends OptionException { + private static final long serialVersionUID = -1L; + + private final String argument; + private final Class valueType; + + OptionArgumentConversionException( Collection options, String argument, Class valueType, + Throwable cause ) { + + super( options, cause ); + + this.argument = argument; + this.valueType = valueType; + } + + @Override + public String getMessage() { + return "Cannot convert argument '" + argument + "' of option " + multipleOptionMessage() + " to " + valueType; + } +} diff --git a/src/joptsimple/OptionException.java b/src/joptsimple/OptionException.java new file mode 100644 index 000000000..3d9d0cc2d --- /dev/null +++ b/src/joptsimple/OptionException.java @@ -0,0 +1,95 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import static java.util.Collections.*; + +import static joptsimple.internal.Strings.*; + +/** + *

Thrown when a problem occurs during option parsing.

+ * + * @author Paul Holser + * @version $Id: OptionException.java,v 1.21 2009/08/13 00:34:35 pholser Exp $ + */ +public abstract class OptionException extends RuntimeException { + private static final long serialVersionUID = -1L; + + private final List options = new ArrayList(); + + protected OptionException( Collection options ) { + this.options.addAll( options ); + } + + protected OptionException( Collection options, Throwable cause ) { + super( cause ); + + this.options.addAll( options ); + } + + /** + *

Gives the option being considered when the exception was created.

+ * + * @return the option being considered when the exception was created + */ + public Collection options() { + return unmodifiableCollection( options ); + } + + protected final String singleOptionMessage() { + return singleOptionMessage( options.get( 0 ) ); + } + + protected final String singleOptionMessage( String option ) { + return SINGLE_QUOTE + option + SINGLE_QUOTE; + } + + protected final String multipleOptionMessage() { + StringBuilder buffer = new StringBuilder( "[" ); + + for ( Iterator iter = options.iterator(); iter.hasNext(); ) { + buffer.append( singleOptionMessage( iter.next() ) ); + if ( iter.hasNext() ) + buffer.append( ", " ); + } + + buffer.append( ']' ); + + return buffer.toString(); + } + + static OptionException illegalOptionCluster( String option ) { + return new IllegalOptionClusterException( option ); + } + + static OptionException unrecognizedOption( String option ) { + return new UnrecognizedOptionException( option ); + } +} diff --git a/src/joptsimple/OptionMissingRequiredArgumentException.java b/src/joptsimple/OptionMissingRequiredArgumentException.java new file mode 100644 index 000000000..201ee9d44 --- /dev/null +++ b/src/joptsimple/OptionMissingRequiredArgumentException.java @@ -0,0 +1,48 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +import java.util.Collection; + +/** + *

Thrown when the option parser discovers an option that requires an argument, + * but that argument is missing.

+ * + * @author Paul Holser + * @version $Id: OptionMissingRequiredArgumentException.java,v 1.12 2009/10/25 18:37:06 pholser Exp $ + */ +class OptionMissingRequiredArgumentException extends OptionException { + private static final long serialVersionUID = -1L; + + OptionMissingRequiredArgumentException( Collection options ) { + super( options ); + } + + @Override + public String getMessage() { + return "Option " + multipleOptionMessage() + " requires an argument"; + } +} diff --git a/src/joptsimple/OptionParser.java b/src/joptsimple/OptionParser.java new file mode 100644 index 000000000..c0029e5b2 --- /dev/null +++ b/src/joptsimple/OptionParser.java @@ -0,0 +1,496 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static java.util.Collections.*; + +import joptsimple.internal.AbbreviationMap; +import joptsimple.util.KeyValuePair; +import static joptsimple.OptionException.*; +import static joptsimple.OptionParserState.*; +import static joptsimple.ParserRules.*; + +/** + *

Parses command line arguments, using a syntax that attempts to take from the best + * of POSIX {@code getopt()} and GNU {@code getopt_long()}.

+ * + *

This parser supports short options and long options.

+ * + *
    + *
  • Short options begin with a single hyphen ("-") followed + * by a single letter or digit, or question mark ("?"), or dot + * (".").
  • + * + *
  • Short options can accept single arguments. The argument can be made required or + * optional. The option's argument can occur: + *
      + *
    • in the slot after the option, as in -d /tmp
    • + *
    • right up against the option, as in -d/tmp
    • + *
    • right up against the option separated by an equals sign ("="), + * as in -d=/tmp
    • + *
    + * To specify n arguments for an option, specify the option n + * times, once for each argument, as in -d /tmp -d /var -d /opt; or, when + * using the {@linkplain ArgumentAcceptingOptionSpec#withValuesSeparatedBy(char) + * "separated values"} clause of the "fluent interface" (see below), give multiple + * values separated by a given character as a single argument to the option.
  • + * + *
  • Short options can be clustered, so that -abc is treated as + * -a -b -c, if none of those options can accept arguments.
  • + * + *
  • An argument consisting only of two hyphens ("--") signals that the + * remaining arguments are to be treated as non-options.
  • + * + *
  • An argument consisting only of a single hyphen is considered a non-option + * argument (though it can be an argument of an option). Many Unix programs treat + * single hyphens as stand-ins for the standard input or standard output streams.
  • + * + *
  • Long options begin with two hyphens ("--"), followed + * by multiple letters, digits, hyphens, question marks, or dots. A hyphen cannot be + * the first character of a long option specification when configuring the parser.
  • + * + *
  • You can abbreviate long options, so long as the abbreviation is unique.
  • + * + *
  • Long options can accept single arguments. The argument can be made required or + * optional. The option's argument can occur: + *
      + *
    • in the slot after the option, as in --directory /tmp
    • + *
    • right up against the option separated by an equals sign ("="), + * as in --directory=/tmp + *
    + * Specify multiple arguments for a long option in the same manner as for short options + * (see above).
  • + * + *
  • You can use a single hyphen ("-") instead of a double hyphen + * ("--") for a long option.
  • + * + *
  • The option -W is reserved. If you tell the parser to {@linkplain + * #recognizeAlternativeLongOptions(boolean) recognize alternative long options}, then + * it will treat, for example, -W foo=bar as the long option + * foo with argument bar, as though you had written + * --foo=bar.
  • + * + *
  • You can specify -W as a valid short option, or use it as an + * abbreviation for a long option, but {@linkplain + * #recognizeAlternativeLongOptions(boolean) recognizing alternative long options} will + * always supersede this behavior.
  • + * + *
  • You can specify a given short or long option multiple times on a single command + * line. The parser collects any arguments specified for those options as a list.
  • + * + *
  • If the parser detects an option whose argument is optional, and the next argument + * "looks like" an option, that argument is not treated as the argument to the option, + * but as a potentially valid option. If, on the other hand, the optional argument is + * typed as a derivative of {@link Number}, then that argument is treated as the + * negative number argument of the option, even if the parser recognizes the + * corresponding numeric option. For example: + *
    
    + *     OptionParser parser = new OptionParser();
    + *     parser.accepts( "a" ).withOptionalArg().ofType( Integer.class );
    + *     parser.accepts( "2" );
    + *     OptionSet options = parser.parse( "-a", "-2" );
    + *   
    + * In this case, the option set contains "a" with argument -2, + * not both "a" and "2". Swapping the elements in the + * args array gives the latter.
  • + *
+ * + *

There are two ways to tell the parser what options to recognize:

+ * + *
    + *
  1. A "fluent interface"-style API for specifying options, available since + * version 2. Sentences in this fluent interface language begin with a call to + * {@link #accepts(String) accepts} or {@link #acceptsAll(Collection) acceptsAll} + * methods; calls on the ensuing chain of objects describe whether the options can take + * an argument, whether the argument is required or optional, to what type arguments of + * the options should be converted if any, etc. Since version 3, these calls return + * an instance of {@link OptionSpec}, which can subsequently be used to retrieve the + * arguments of the associated option in a type-safe manner.
  2. + * + *
  3. Since version 1, a more concise way of specifying short options has been to use + * the special {@linkplain #OptionParser(String) constructor}. Arguments of options + * specified in this manner will be of type {@link String}. Here are the rules for the + * format of the specification strings this constructor accepts: + * + *
      + *
    • Any letter or digit is treated as an option character.
    • + * + *
    • If an option character is followed by a single colon (":"), + * then the option requires an argument.
    • + * + *
    • If an option character is followed by two colons ("::"), then + * the option accepts an optional argument.
    • + * + *
    • Otherwise, the option character accepts no argument.
    • + * + *
    • If the option specification string begins with a plus sign ("+"), + * the parser will behave "POSIX-ly correct".
    • + * + *
    • If the option specification string contains the sequence "W;" + * (capital W followed by a semicolon), the parser will recognize the alternative + * form of long options.
    • + *
    + *
  4. + *
+ * + *

Each of the options in a list of options given to {@link #acceptsAll(Collection) + * acceptsAll} is treated as a synonym of the others. For example: + *

+ *     
+ *     OptionParser parser = new OptionParser();
+ *     parser.acceptsAll( asList( "w", "interactive", "confirmation" ) );
+ *     OptionSet options = parser.parse( "-w" );
+ *     
+ *   
+ * In this case, options.{@link OptionSet#has(String) has} would answer + * {@code true} when given arguments "w", "interactive", and + * "confirmation". The {@link OptionSet} would give the same responses to + * these arguments for its other methods as well.

+ * + *

By default, as with GNU {@code getopt()}, the parser allows intermixing of options + * and non-options. If, however, the parser has been created to be "POSIX-ly correct", + * then the first argument that does not look lexically like an option, and is not a + * required argument of a preceding option, signals the end of options. You can still + * bind optional arguments to their options using the abutting (for short options) or + * = syntax.

+ * + *

Unlike GNU {@code getopt()}, this parser does not honor the environment variable + * {@code POSIXLY_CORRECT}. "POSIX-ly correct" parsers are configured by either:

+ * + *
    + *
  1. using the method {@link #posixlyCorrect(boolean)}, or
  2. + * + *
  3. using the {@linkplain #OptionParser(String) constructor} with an argument whose + * first character is a plus sign ("+")
  4. + *
+ * + * @author Paul Holser + * @version $Id: OptionParser.java,v 1.38 2009/10/25 18:37:06 pholser Exp $ + * @see The GNU C Library + */ +public class OptionParser { + private final AbbreviationMap> recognizedOptions; + private OptionParserState state; + private boolean posixlyCorrect; + + /** + *

Creates an option parser that initially recognizes no options, and does not + * exhibit "POSIX-ly correct" behavior.

+ */ + public OptionParser() { + recognizedOptions = new AbbreviationMap>(); + state = moreOptions( false ); + } + + /** + *

Creates an option parser and configures it to recognize the short options + * specified in the given string.

+ * + *

Arguments of options specified this way will be of type {@link String}.

+ * + * @param optionSpecification an option specification + * @throws NullPointerException if optionSpecification is + * {@code null} + * @throws OptionException if the option specification contains illegal characters + * or otherwise cannot be recognized + */ + public OptionParser( String optionSpecification ) { + this(); + + new OptionSpecTokenizer( optionSpecification ).configure( this ); + } + + /** + *

Tells the parser to recognize the given option.

+ * + *

This method returns an instance of {@link OptionSpecBuilder} to allow the + * formation of parser directives as sentences in a fluent interface language. + * For example:

+ * + *

+     *   OptionParser parser = new OptionParser();
+     *   parser.accepts( "c" ).withRequiredArg().ofType( Integer.class );
+     * 
+ * + *

If no methods are invoked on the returned {@link OptionSpecBuilder}, then the + * parser treats the option as accepting no argument.

+ * + * @param option the option to recognize + * @return an object that can be used to flesh out more detail about the option + * @throws OptionException if the option contains illegal characters + * @throws NullPointerException if the option is {@code null} + */ + public OptionSpecBuilder accepts( String option ) { + return acceptsAll( singletonList( option ) ); + } + + /** + *

Tells the parser to recognize the given option.

+ * + * @see #accepts(String) + * @param option the option to recognize + * @param description a string that describes the purpose of the option. This is + * used when generating help information about the parser. + * @return an object that can be used to flesh out more detail about the option + * @throws OptionException if the option contains illegal characters + * @throws NullPointerException if the option is {@code null} + */ + public OptionSpecBuilder accepts( String option, String description ) { + return acceptsAll( singletonList( option ), description ); + } + + /** + *

Tells the parser to recognize the given options, and treat them as + * synonymous.

+ * + * @see #accepts(String) + * @param options the options to recognize and treat as synonymous + * @return an object that can be used to flesh out more detail about the options + * @throws OptionException if any of the options contain illegal characters + * @throws NullPointerException if the option list or any of its elements are + * {@code null} + */ + public OptionSpecBuilder acceptsAll( Collection options ) { + return acceptsAll( options, "" ); + } + + /** + *

Tells the parser to recognize the given options, and treat them as + * synonymous.

+ * + * @see #acceptsAll(Collection) + * @param options the options to recognize and treat as synonymous + * @param description a string that describes the purpose of the option. This is + * used when generating help information about the parser. + * @return an object that can be used to flesh out more detail about the options + * @throws OptionException if any of the options contain illegal characters + * @throws NullPointerException if the option list or any of its elements are + * {@code null} + * @throws IllegalArgumentException if the option list is empty + */ + public OptionSpecBuilder acceptsAll( Collection options, + String description ) { + + if ( options.isEmpty() ) + throw new IllegalArgumentException( "need at least one option" ); + + ensureLegalOptions( options ); + + return new OptionSpecBuilder( this, options, description ); + } + + /** + *

Tells the parser whether or not to behave "POSIX-ly correct"-ly.

+ * + * @param setting {@code true} if the parser should behave "POSIX-ly correct"-ly + */ + public void posixlyCorrect( boolean setting ) { + posixlyCorrect = setting; + state = moreOptions( setting ); + } + + boolean posixlyCorrect() { + return posixlyCorrect; + } + + /** + *

Tells the parser either to recognize or ignore "-W"-style long + * options.

+ * + * @param recognize {@code true} if the parser is to recognize the special style + * of long options + */ + public void recognizeAlternativeLongOptions( boolean recognize ) { + if ( recognize ) + recognize( new AlternativeLongOptionSpec() ); + else + recognizedOptions.remove( String.valueOf( RESERVED_FOR_EXTENSIONS ) ); + } + + void recognize( AbstractOptionSpec spec ) { + recognizedOptions.putAll( spec.options(), spec ); + } + + /** + *

Writes information about the options this parser recognizes to the given output + * sink.

+ * + *

The output sink is flushed, but not closed.

+ * + * @param sink the sink to write information to + * @throws IOException if there is a problem writing to the sink + * @throws NullPointerException if sink is {@code null} + * @see #printHelpOn(Writer) + */ + public void printHelpOn( OutputStream sink ) throws IOException { + printHelpOn( new OutputStreamWriter( sink ) ); + } + + /** + *

Writes information about the options this parser recognizes to the given output + * sink.

+ * + *

The output sink is flushed, but not closed.

+ * + * @param sink the sink to write information to + * @throws IOException if there is a problem writing to the sink + * @throws NullPointerException if sink is {@code null} + * @see #printHelpOn(OutputStream) + */ + public void printHelpOn( Writer sink ) throws IOException { + sink.write( new HelpFormatter().format( recognizedOptions.toJavaUtilMap() ) ); + sink.flush(); + } + + /** + *

Parses the given command line arguments according to the option specifications + * given to the parser.

+ * + * @param arguments arguments to parse + * @return an {@link OptionSet} describing the parsed options, their arguments, and + * any non-option arguments found + * @throws OptionException if problems are detected while parsing + * @throws NullPointerException if the argument list is {@code null} + */ + public OptionSet parse( String... arguments ) { + ArgumentList argumentList = new ArgumentList( arguments ); + OptionSet detected = new OptionSet( defaultValues() ); + + while ( argumentList.hasMore() ) + state.handleArgument( this, argumentList, detected ); + + reset(); + return detected; + } + + void handleLongOptionToken( String candidate, ArgumentList arguments, OptionSet detected ) { + KeyValuePair optionAndArgument = parseLongOptionWithArgument( candidate ); + + if ( !isRecognized( optionAndArgument.key ) ) + throw unrecognizedOption( optionAndArgument.key ); + + AbstractOptionSpec optionSpec = specFor( optionAndArgument.key ); + optionSpec.handleOption( this, arguments, detected, optionAndArgument.value ); + } + + void handleShortOptionToken( String candidate, ArgumentList arguments, OptionSet detected ) { + KeyValuePair optionAndArgument = parseShortOptionWithArgument( candidate ); + + if ( isRecognized( optionAndArgument.key ) ) { + specFor( optionAndArgument.key ).handleOption( this, arguments, detected, optionAndArgument.value ); + } + else + handleShortOptionCluster( candidate, arguments, detected ); + } + + private void handleShortOptionCluster( String candidate, ArgumentList arguments, OptionSet detected ) { + char[] options = extractShortOptionsFrom( candidate ); + validateOptionCharacters( options ); + + AbstractOptionSpec optionSpec = specFor( options[ 0 ] ); + + if ( optionSpec.acceptsArguments() && options.length > 1 ) { + String detectedArgument = String.valueOf( options, 1, options.length - 1 ); + optionSpec.handleOption( this, arguments, detected, detectedArgument ); + } + else { + for ( char each : options ) + specFor( each ).handleOption( this, arguments, detected, null ); + } + } + + void noMoreOptions() { + state = OptionParserState.noMoreOptions(); + } + + boolean looksLikeAnOption( String argument ) { + return isShortOptionToken( argument ) || isLongOptionToken( argument ); + } + + private boolean isRecognized( String option ) { + return recognizedOptions.contains( option ); + } + + private AbstractOptionSpec specFor( char option ) { + return specFor( String.valueOf( option ) ); + } + + private AbstractOptionSpec specFor( String option ) { + return recognizedOptions.get( option ); + } + + private void reset() { + state = moreOptions( posixlyCorrect ); + } + + private static char[] extractShortOptionsFrom( String argument ) { + char[] options = new char[ argument.length() - 1 ]; + argument.getChars( 1, argument.length(), options, 0 ); + + return options; + } + + private void validateOptionCharacters( char[] options ) { + for ( int i = 0; i < options.length; ++i ) { + String option = String.valueOf( options[ i ] ); + + if ( !isRecognized( option ) ) + throw unrecognizedOption( option ); + + if ( specFor( option ).acceptsArguments() ) { + if ( i > 0 ) + throw illegalOptionCluster( option ); + + // remainder of chars are the argument to the option at char 0 + return; + } + } + } + + private static KeyValuePair parseLongOptionWithArgument( String argument ) { + return KeyValuePair.valueOf( argument.substring( 2 ) ); + } + + private static KeyValuePair parseShortOptionWithArgument( String argument ) { + return KeyValuePair.valueOf( argument.substring( 1 ) ); + } + + private Map> defaultValues() { + Map> defaults = new HashMap>(); + for ( Map.Entry> each : recognizedOptions.toJavaUtilMap().entrySet() ) + defaults.put( each.getKey(), each.getValue().defaultValues() ); + return defaults; + } +} diff --git a/src/joptsimple/OptionParserState.java b/src/joptsimple/OptionParserState.java new file mode 100644 index 000000000..d011a25a8 --- /dev/null +++ b/src/joptsimple/OptionParserState.java @@ -0,0 +1,69 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +import static joptsimple.ParserRules.*; + +/** + *

Abstraction of parser state; mostly serves to model how a parser behaves depending + * on whether end-of-options has been detected.

+ * + * @author Paul Holser + * @version $Id: OptionParserState.java,v 1.8 2009/09/28 01:12:48 pholser Exp $ + */ +abstract class OptionParserState { + static OptionParserState noMoreOptions() { + return new OptionParserState() { + @Override + protected void handleArgument( OptionParser parser, ArgumentList arguments, OptionSet detectedOptions ) { + detectedOptions.addNonOptionArgument( arguments.next() ); + } + }; + } + + static OptionParserState moreOptions( final boolean posixlyCorrect ) { + return new OptionParserState() { + @Override + protected void handleArgument( OptionParser parser, ArgumentList arguments, OptionSet detectedOptions ) { + String candidate = arguments.next(); + if ( isOptionTerminator( candidate ) ) + parser.noMoreOptions(); + else if ( isLongOptionToken( candidate ) ) + parser.handleLongOptionToken( candidate, arguments, detectedOptions ); + else if ( isShortOptionToken( candidate ) ) + parser.handleShortOptionToken( candidate, arguments, detectedOptions ); + else { + if ( posixlyCorrect ) + parser.noMoreOptions(); + + detectedOptions.addNonOptionArgument( candidate ); + } + } + }; + } + + protected abstract void handleArgument( OptionParser parser, ArgumentList arguments, OptionSet detectedOptions ); +} diff --git a/src/joptsimple/OptionSet.java b/src/joptsimple/OptionSet.java new file mode 100644 index 000000000..e043893b4 --- /dev/null +++ b/src/joptsimple/OptionSet.java @@ -0,0 +1,301 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import static java.util.Collections.*; + +import static joptsimple.internal.Objects.*; + +/** + *

Representation of a group of detected command line options, their arguments, and + * non-option arguments.

+ * + * @author Paul Holser + * @version $Id: OptionSet.java,v 1.26 2009/10/25 18:37:05 pholser Exp $ + */ +public class OptionSet { + private final Map> detectedOptions; + private final Map, List> optionsToArguments; + private final List nonOptionArguments; + private final Map> defaultValues; + + /** + * Package-private because clients don't create these. + */ + OptionSet( Map> defaults ) { + detectedOptions = new HashMap>(); + optionsToArguments = new IdentityHashMap, List>(); + nonOptionArguments = new ArrayList(); + defaultValues = new HashMap>( defaults ); + } + + /** + *

Tells whether the given option was detected.

+ * + * @param option the option to search for + * @return {@code true} if the option was detected + * @see #has(OptionSpec) + */ + public boolean has( String option ) { + return detectedOptions.containsKey( option ); + } + + /** + *

Tells whether the given option was detected.

+ * + *

This method recognizes only instances of options returned from the fluent + * interface methods.

+ * + *

Specifying a {@linkplain ArgumentAcceptingOptionSpec#defaultsTo(Object, Object[])} default argument value} + * for an option does not cause this method to return {@code true} if the option was not detected on the command + * line.

+ * + * @param option the option to search for + * @return {@code true} if the option was detected + * @see #has(String) + */ + public boolean has( OptionSpec option ) { + return optionsToArguments.containsKey( option ); + } + + /** + *

Tells whether there are any arguments associated with the given option.

+ * + * @param option the option to search for + * @return {@code true} if the option was detected and at least one argument was + * detected for the option + * @see #hasArgument(OptionSpec) + */ + public boolean hasArgument( String option ) { + AbstractOptionSpec spec = detectedOptions.get( option ); + return spec != null && hasArgument( spec ); + } + + /** + *

Tells whether there are any arguments associated with the given option.

+ * + *

This method recognizes only instances of options returned from the fluent + * interface methods.

+ * + *

Specifying a {@linkplain ArgumentAcceptingOptionSpec#defaultsTo(Object, Object[]) default argument value} + * for an option does not cause this method to return {@code true} if the option was not detected on the command + * line, or if the option can take an optional argument but did not have one on the command line.

+ * + * @param option the option to search for + * @return {@code true} if the option was detected and at least one argument was + * detected for the option + * @throws NullPointerException if {@code option} is {@code null} + * @see #hasArgument(String) + */ + public boolean hasArgument( OptionSpec option ) { + ensureNotNull( option ); + + List values = optionsToArguments.get( option ); + return values != null && !values.isEmpty(); + } + + /** + *

Gives the argument associated with the given option. If the option was given + * an argument type, the argument will take on that type; otherwise, it will be a + * {@link String}.

+ * + *

Specifying a {@linkplain ArgumentAcceptingOptionSpec#defaultsTo(Object, Object[]) default argument value} + * for an option will cause this method to return that default value even if the option was not detected on the + * command line, or if the option can take an optional argument but did not have one on the command line.

+ * + * @param option the option to search for + * @return the argument of the given option; {@code null} if no argument is + * present, or that option was not detected + * @throws NullPointerException if {@code option} is {@code null} + * @throws OptionException if more than one argument was detected for the option + */ + public Object valueOf( String option ) { + ensureNotNull( option ); + + AbstractOptionSpec spec = detectedOptions.get( option ); + if ( spec == null ) { + List defaults = defaultValuesFor( option ); + return defaults.isEmpty() ? null : defaults.get( 0 ); + } + + return valueOf( spec ); + } + + /** + *

Gives the argument associated with the given option.

+ * + *

This method recognizes only instances of options returned from the fluent + * interface methods.

+ * + * @param represents the type of the arguments the given option accepts + * @param option the option to search for + * @return the argument of the given option; {@code null} if no argument is + * present, or that option was not detected + * @throws OptionException if more than one argument was detected for the option + * @throws NullPointerException if {@code option} is {@code null} + * @throws ClassCastException if the arguments of this option are not of the + * expected type + */ + public V valueOf( OptionSpec option ) { + ensureNotNull( option ); + + List values = valuesOf( option ); + switch ( values.size() ) { + case 0: + return null; + case 1: + return values.get( 0 ); + default: + throw new MultipleArgumentsForOptionException( option.options() ); + } + } + + /** + *

Gives any arguments associated with the given option. If the option was given + * an argument type, the arguments will take on that type; otherwise, they will be + * {@link String}s.

+ * + * @param option the option to search for + * @return the arguments associated with the option, as a list of objects of the + * type given to the arguments; an empty list if no such arguments are present, or if + * the option was not detected + * @throws NullPointerException if {@code option} is {@code null} + */ + public List valuesOf( String option ) { + ensureNotNull( option ); + + AbstractOptionSpec spec = detectedOptions.get( option ); + return spec == null ? defaultValuesFor( option ) : valuesOf( spec ); + } + + /** + *

Gives any arguments associated with the given option. If the option was given + * an argument type, the arguments will take on that type; otherwise, they will be + * {@link String}s.

+ * + *

This method recognizes only instances of options returned from the fluent + * interface methods.

+ * + * @param represents the type of the arguments the given option accepts + * @param option the option to search for + * @return the arguments associated with the option; an empty list if no such + * arguments are present, or if the option was not detected + * @throws NullPointerException if {@code option} is {@code null} + * @throws OptionException if there is a problem converting the option's arguments to + * the desired type; for example, if the type does not implement a correct conversion + * constructor or method + */ + public List valuesOf( OptionSpec option ) { + ensureNotNull( option ); + + List values = optionsToArguments.get( option ); + if ( values == null || values.isEmpty() ) + return defaultValueFor( option ); + + AbstractOptionSpec spec = (AbstractOptionSpec) option; + List convertedValues = new ArrayList(); + for ( String each : values ) + convertedValues.add( spec.convert( each ) ); + + return unmodifiableList( convertedValues ); + } + + /** + * @return the detected non-option arguments + */ + public List nonOptionArguments() { + return unmodifiableList( nonOptionArguments ); + } + + void add( AbstractOptionSpec option ) { + addWithArgument( option, null ); + } + + void addWithArgument( AbstractOptionSpec option, String argument ) { + for ( String each : option.options() ) + detectedOptions.put( each, option ); + + List optionArguments = optionsToArguments.get( option ); + + if ( optionArguments == null ) { + optionArguments = new ArrayList(); + optionsToArguments.put( option, optionArguments ); + } + + if ( argument != null ) + optionArguments.add( argument ); + } + + void addNonOptionArgument( String argument ) { + nonOptionArguments.add( argument ); + } + + @Override + public boolean equals( Object that ) { + if ( this == that ) + return true; + + if ( that == null || !getClass().equals( that.getClass() ) ) + return false; + + OptionSet other = (OptionSet) that; + Map, List> thisOptionsToArguments = + new HashMap, List>( optionsToArguments ); + Map, List> otherOptionsToArguments = + new HashMap, List>( other.optionsToArguments ); + return detectedOptions.equals( other.detectedOptions ) + && thisOptionsToArguments.equals( otherOptionsToArguments ) + && nonOptionArguments.equals( other.nonOptionArguments() ); + } + + @Override + public int hashCode() { + Map, List> thisOptionsToArguments = + new HashMap, List>( optionsToArguments ); + return detectedOptions.hashCode() + ^ thisOptionsToArguments.hashCode() + ^ nonOptionArguments.hashCode(); + } + + private List defaultValuesFor( String option ) { + if ( defaultValues.containsKey( option ) ) { + @SuppressWarnings( "unchecked" ) + List defaults = (List) defaultValues.get( option ); + return defaults; + } + + return emptyList(); + } + + private List defaultValueFor( OptionSpec option ) { + return defaultValuesFor( option.options().iterator().next() ); + } +} diff --git a/src/joptsimple/OptionSpec.java b/src/joptsimple/OptionSpec.java new file mode 100644 index 000000000..c7032f80c --- /dev/null +++ b/src/joptsimple/OptionSpec.java @@ -0,0 +1,95 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +import java.util.Collection; +import java.util.List; + +/** + *

Describes options that an option parser recognizes.

+ * + *

Instances of this interface are returned by the "fluent interface" methods to allow + * retrieval of option arguments in a type-safe manner. Here's an example:

+ *

+ *     OptionParser parser = new OptionParser();
+ *     OptionSpec<Integer> count =
+ *         parser.accepts( "count" ).withRequiredArg().ofType( Integer.class );
+ *     OptionSet options = parser.parse( "--count", "2" );
+ *     assert options.has( count );
+ *     int countValue = options.valueOf( count );
+ *     assert countValue == count.value( options );
+ *     List<Integer> countValues = options.valuesOf( count );
+ *     assert countValues.equals( count.values( options ) );
+ * 
+ * + * @param represents the type of the arguments this option accepts + * @author Paul Holser + * @version $Id: OptionSpec.java,v 1.25 2009/10/25 18:37:06 pholser Exp $ + */ +public interface OptionSpec { + /** + *

Gives any arguments associated with the given option in the given set of + * detected options.

+ * + *

Specifying a {@linkplain ArgumentAcceptingOptionSpec#defaultsTo(Object, Object[]) default argument value} + * for this option will cause this method to return that default value even if this option was not detected on the + * command line, or if this option can take an optional argument but did not have one on the command line.

+ * + * @param detectedOptions the detected options to search in + * @return the arguments associated with this option; an empty list if no such + * arguments are present, or if this option was not detected + * @throws OptionException if there is a problem converting this option's arguments + * to the desired type; for example, if the type does not implement a correct + * conversion constructor or method + * @throws NullPointerException if {@code detectedOptions} is {@code null} + * @see OptionSet#valuesOf(OptionSpec) + */ + List values( OptionSet detectedOptions ); + + /** + *

Gives the argument associated with the given option in the given set of + * detected options.

+ * + *

Specifying a {@linkplain ArgumentAcceptingOptionSpec#defaultsTo(Object, Object[]) default argument value} + * for this option will cause this method to return that default value even if this option was not detected on the + * command line, or if this option can take an optional argument but did not have one on the command line.

+ * + * @param detectedOptions the detected options to search in + * @return the argument of the this option; {@code null} if no argument is present, + * or that option was not detected + * @throws OptionException if more than one argument was detected for the option + * @throws NullPointerException if {@code detectedOptions} is {@code null} + * @throws ClassCastException if the arguments of this option are not of the + * expected type + * @see OptionSet#valueOf(OptionSpec) + */ + V value( OptionSet detectedOptions ); + + /** + * @return the string representations of this option + */ + Collection options(); +} diff --git a/src/joptsimple/OptionSpecBuilder.java b/src/joptsimple/OptionSpecBuilder.java new file mode 100644 index 000000000..c1095ef57 --- /dev/null +++ b/src/joptsimple/OptionSpecBuilder.java @@ -0,0 +1,101 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +import java.util.Collection; + +/** + *

Allows callers to specify whether a given option accepts arguments (required or + * optional).

+ * + *

Instances are returned from {@link OptionParser#accepts(String)} to allow the + * formation of parser directives as sentences in a "fluent interface" language. For + * example:

+ * + *

+ *   OptionParser parser = new OptionParser();
+ *   parser.accepts( "c" ).withRequiredArg().ofType( Integer.class );
+ * 
+ * + *

If no methods are invoked on an instance of this class, then that instance's option + * will accept no argument.

+ * + *

Note that you should not use the fluent interface clauses in a way that would + * defeat the typing of option arguments:

+ * + *

+ *   OptionParser parser = new OptionParser();
+ *   ArgumentAcceptingOptionSpec<String> optionC =
+ *       parser.accepts( "c" ).withRequiredArg();
+ *   optionC.ofType( Integer.class );  // DON'T THROW AWAY THE TYPE!
+ *
+ *   String value = parser.parse( "-c", "2" ).valueOf( optionC );  // ClassCastException
+ * 
+ * + * @author Paul Holser + * @version $Id: OptionSpecBuilder.java,v 1.19 2009/10/25 18:37:06 pholser Exp $ + */ +public class OptionSpecBuilder extends NoArgumentOptionSpec { + private final OptionParser parser; + + OptionSpecBuilder( OptionParser parser, Collection options, String description ) { + super( options, description ); + + this.parser = parser; + attachToParser(); + } + + private void attachToParser() { + parser.recognize( this ); + } + + /** + *

Informs an option parser that this builder's option requires an argument.

+ * + * @return a specification for the option + */ + public ArgumentAcceptingOptionSpec withRequiredArg() { + ArgumentAcceptingOptionSpec newSpec = + new RequiredArgumentOptionSpec( options(), description() ); + parser.recognize( newSpec ); + + return newSpec; + } + + /** + *

Informs an option parser that this builder's option accepts an optional + * argument.

+ * + * @return a specification for the option + */ + public ArgumentAcceptingOptionSpec withOptionalArg() { + ArgumentAcceptingOptionSpec newSpec = + new OptionalArgumentOptionSpec( options(), description() ); + parser.recognize( newSpec ); + + return newSpec; + } +} diff --git a/src/joptsimple/OptionSpecTokenizer.java b/src/joptsimple/OptionSpecTokenizer.java new file mode 100644 index 000000000..c83535b22 --- /dev/null +++ b/src/joptsimple/OptionSpecTokenizer.java @@ -0,0 +1,119 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +import java.util.NoSuchElementException; + +import static joptsimple.ParserRules.*; + +/** + *

Tokenizes a short option specification string.

+ * + * @author Paul Holser + * @version $Id: OptionSpecTokenizer.java,v 1.14 2009/10/25 18:37:06 pholser Exp $ + */ +class OptionSpecTokenizer { + private static final char POSIXLY_CORRECT_MARKER = '+'; + + private String specification; + private int index; + + OptionSpecTokenizer( String specification ) { + if ( specification == null ) + throw new NullPointerException( "null option specification" ); + + this.specification = specification; + } + + boolean hasMore() { + return index < specification.length(); + } + + AbstractOptionSpec next() { + if ( !hasMore() ) + throw new NoSuchElementException(); + + + String optionCandidate = String.valueOf( specification.charAt( index ) ); + index++; + + AbstractOptionSpec spec; + if ( RESERVED_FOR_EXTENSIONS.equals( optionCandidate ) ) { + spec = handleReservedForExtensionsToken(); + + if ( spec != null ) + return spec; + } + + ensureLegalOption( optionCandidate ); + + if ( hasMore() ) + spec = specification.charAt( index ) == ':' + ? handleArgumentAcceptingOption( optionCandidate ) + : new NoArgumentOptionSpec( optionCandidate ); + else + spec = new NoArgumentOptionSpec( optionCandidate ); + + return spec; + } + + void configure( OptionParser parser ) { + adjustForPosixlyCorrect( parser ); + + while ( hasMore() ) + parser.recognize( next() ); + } + + private void adjustForPosixlyCorrect( OptionParser parser ) { + if ( POSIXLY_CORRECT_MARKER == specification.charAt( 0 ) ) { + parser.posixlyCorrect( true ); + specification = specification.substring( 1 ); + } + } + + private AbstractOptionSpec handleReservedForExtensionsToken() { + if ( !hasMore() ) + return new NoArgumentOptionSpec( RESERVED_FOR_EXTENSIONS ); + + if ( specification.charAt( index ) == ';' ) { + ++index; + return new AlternativeLongOptionSpec(); + } + + return null; + } + + private AbstractOptionSpec handleArgumentAcceptingOption( String candidate ) { + index++; + + if ( hasMore() && specification.charAt( index ) == ':' ) { + index++; + return new OptionalArgumentOptionSpec( candidate ); + } + + return new RequiredArgumentOptionSpec( candidate ); + } +} diff --git a/src/joptsimple/OptionSpecVisitor.java b/src/joptsimple/OptionSpecVisitor.java new file mode 100644 index 000000000..ff88bad7a --- /dev/null +++ b/src/joptsimple/OptionSpecVisitor.java @@ -0,0 +1,42 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +/** + *

Visitor interface for option specifications.

+ * + * @author Paul Holser + * @version $Id: OptionSpecVisitor.java,v 1.6 2009/08/13 00:34:35 pholser Exp $ + */ +interface OptionSpecVisitor { + void visit( NoArgumentOptionSpec spec ); + + void visit( RequiredArgumentOptionSpec spec ); + + void visit( OptionalArgumentOptionSpec spec ); + + void visit( AlternativeLongOptionSpec spec ); +} diff --git a/src/joptsimple/OptionalArgumentOptionSpec.java b/src/joptsimple/OptionalArgumentOptionSpec.java new file mode 100644 index 000000000..bf572ce45 --- /dev/null +++ b/src/joptsimple/OptionalArgumentOptionSpec.java @@ -0,0 +1,75 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +import java.util.Collection; + +/** + *

Specification of an option that accepts an optional argument.

+ * + * @param represents the type of the arguments this option accepts + * @author Paul Holser + * @version $Id: OptionalArgumentOptionSpec.java,v 1.17 2009/09/28 01:12:48 pholser Exp $ + */ +class OptionalArgumentOptionSpec extends ArgumentAcceptingOptionSpec { + OptionalArgumentOptionSpec( String option ) { + super( option, false ); + } + + OptionalArgumentOptionSpec( Collection options, String description ) { + super( options, false, description ); + } + + @Override + protected void detectOptionArgument( OptionParser parser, ArgumentList arguments, OptionSet detectedOptions ) { + if ( arguments.hasMore() ) { + String nextArgument = arguments.peek(); + + if ( !parser.looksLikeAnOption( nextArgument ) ) + handleOptionArgument( parser, detectedOptions, arguments ); + else if ( isArgumentOfNumberType() && canConvertArgument( nextArgument ) ) + addArguments( detectedOptions, arguments.next() ); + else + detectedOptions.add( this ); + } + else + detectedOptions.add( this ); + } + + private void handleOptionArgument( OptionParser parser, OptionSet detectedOptions, ArgumentList arguments ) { + if ( parser.posixlyCorrect() ) { + detectedOptions.add( this ); + parser.noMoreOptions(); + } + else + addArguments( detectedOptions, arguments.next() ); + } + + @Override + void accept( OptionSpecVisitor visitor ) { + visitor.visit( this ); + } +} diff --git a/src/joptsimple/ParserRules.java b/src/joptsimple/ParserRules.java new file mode 100644 index 000000000..83799787b --- /dev/null +++ b/src/joptsimple/ParserRules.java @@ -0,0 +1,88 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +import java.util.Collection; +import static java.lang.Character.*; + +/** + *

Can tell whether or not options are well-formed.

+ * + * @author Paul Holser + * @version $Id: ParserRules.java,v 1.14 2009/09/23 00:39:14 pholser Exp $ + */ +final class ParserRules { + static final char HYPHEN_CHAR = '-'; + static final String HYPHEN = String.valueOf( HYPHEN_CHAR ); + static final String DOUBLE_HYPHEN = "--"; + static final String OPTION_TERMINATOR = DOUBLE_HYPHEN; + static final String RESERVED_FOR_EXTENSIONS = "W"; + + static { + new ParserRules(); + } + + private ParserRules() { + // nothing to do here + } + + static boolean isShortOptionToken( String argument ) { + return argument.startsWith( HYPHEN ) + && !HYPHEN.equals( argument ) + && !isLongOptionToken( argument ); + } + + static boolean isLongOptionToken( String argument ) { + return argument.startsWith( DOUBLE_HYPHEN ) && !isOptionTerminator( argument ); + } + + static boolean isOptionTerminator( String argument ) { + return OPTION_TERMINATOR.equals( argument ); + } + + static void ensureLegalOption( String option ) { + if ( option.startsWith( HYPHEN ) ) + throw new IllegalOptionSpecificationException( String.valueOf( option ) ); + + for ( int i = 0; i < option.length(); ++i ) + ensureLegalOptionCharacter( option.charAt( i ) ); + } + + static void ensureLegalOptions( Collection options ) { + for ( String each : options ) + ensureLegalOption( each ); + } + + private static void ensureLegalOptionCharacter( char option ) { + if ( !( isLetterOrDigit( option ) || isAllowedPunctuation( option ) ) ) + throw new IllegalOptionSpecificationException( String.valueOf( option ) ); + } + + private static boolean isAllowedPunctuation( char option ) { + String allowedPunctuation = "?." + HYPHEN_CHAR; + return allowedPunctuation.indexOf( option ) != -1; + } +} diff --git a/src/joptsimple/RequiredArgumentOptionSpec.java b/src/joptsimple/RequiredArgumentOptionSpec.java new file mode 100644 index 000000000..ba5d296a6 --- /dev/null +++ b/src/joptsimple/RequiredArgumentOptionSpec.java @@ -0,0 +1,58 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +import java.util.Collection; + +/** + *

Specification of an option that accepts a required argument.

+ * + * @param represents the type of the arguments this option accepts + * @author Paul Holser + * @version $Id: RequiredArgumentOptionSpec.java,v 1.16 2009/09/28 01:12:48 pholser Exp $ + */ +class RequiredArgumentOptionSpec extends ArgumentAcceptingOptionSpec { + RequiredArgumentOptionSpec( String option ) { + super( option, true ); + } + + RequiredArgumentOptionSpec( Collection options, String description ) { + super( options, true, description ); + } + + @Override + protected void detectOptionArgument( OptionParser parser, ArgumentList arguments, OptionSet detectedOptions ) { + if ( !arguments.hasMore() ) + throw new OptionMissingRequiredArgumentException( options() ); + + addArguments( detectedOptions, arguments.next() ); + } + + @Override + void accept( OptionSpecVisitor visitor ) { + visitor.visit( this ); + } +} diff --git a/src/joptsimple/UnrecognizedOptionException.java b/src/joptsimple/UnrecognizedOptionException.java new file mode 100644 index 000000000..d1862d1c8 --- /dev/null +++ b/src/joptsimple/UnrecognizedOptionException.java @@ -0,0 +1,47 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +import static java.util.Collections.*; + +/** + *

Thrown when the option parser encounters an unrecognized option.

+ * + * @author Paul Holser + * @version $Id: UnrecognizedOptionException.java,v 1.10 2009/10/25 18:37:06 pholser Exp $ + */ +class UnrecognizedOptionException extends OptionException { + private static final long serialVersionUID = -1L; + + UnrecognizedOptionException( String option ) { + super( singletonList( option ) ); + } + + @Override + public String getMessage() { + return singleOptionMessage() + " is not a recognized option"; + } +} diff --git a/src/joptsimple/ValueConversionException.java b/src/joptsimple/ValueConversionException.java new file mode 100644 index 000000000..d07f7ffc7 --- /dev/null +++ b/src/joptsimple/ValueConversionException.java @@ -0,0 +1,56 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +/** + * Thrown by {@link ValueConverter}s when problems occur in converting string values to + * other Java types. + * + * @author Paul Holser + * @version $Id: ValueConversionException.java,v 1.5 2009/08/13 00:34:35 pholser Exp $ + */ +public class ValueConversionException extends RuntimeException { + private static final long serialVersionUID = -1L; + + /** + * Creates a new exception with the specified detail message. + * + * @param message the detail message + */ + public ValueConversionException( String message ) { + this( message, null ); + } + + /** + * Creates a new exception with the specified detail message and cause. + * + * @param message the detail message + * @param cause the original exception + */ + public ValueConversionException( String message, Throwable cause ) { + super( message, cause ); + } +} diff --git a/src/joptsimple/ValueConverter.java b/src/joptsimple/ValueConverter.java new file mode 100644 index 000000000..b167de2e9 --- /dev/null +++ b/src/joptsimple/ValueConverter.java @@ -0,0 +1,61 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple; + +/** + * Instances of this interface are used to convert arguments of options into specific + * Java types. + * + * @param constraint on the type of values being converted to + * @author Paul Holser + * @version $Id: ValueConverter.java,v 1.9 2009/08/13 00:34:35 pholser Exp $ + */ +public interface ValueConverter { + /** + * Converts the given string value into a Java type. + * + * @param value the string to convert + * @return the converted value + * @throws ValueConversionException if a problem occurs while converting the value + */ + V convert( String value ); + + /** + * Gives the class of the type of values this converter converts to. + * + * @return the target class for conversion + */ + Class valueType(); + + /** + * Gives a string that describes the pattern of the values this converter expects, + * if any. For example, a date converter can respond with a + * {@link java.text.SimpleDateFormat date format string}. + * + * @return a value pattern, or {@code null} if there's nothing interesting here + */ + String valuePattern(); +} diff --git a/src/joptsimple/internal/AbbreviationMap.java b/src/joptsimple/internal/AbbreviationMap.java new file mode 100644 index 000000000..b239943f8 --- /dev/null +++ b/src/joptsimple/internal/AbbreviationMap.java @@ -0,0 +1,239 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple.internal; + +import java.util.Map; +import java.util.TreeMap; + +/** + *

A map whose keys are strings; when a key/value pair is added to the map, + * the longest unique abbreviations of that key are added as well, and associated with + * the value. Thus:

+ * + *
+ *   
+ *   abbreviations.put( "good", "bye" );
+ *   
+ * 
+ * + *

would make it such that you could retrieve the value {@code "bye"} from the map + * using the keys {@code "good"}, {@code "goo"}, {@code "go"}, and {@code "g"}. + * A subsequent invocation of:

+ *
+ *   
+ *   abbreviations.put( "go", "fish" );
+ *   
+ * 
+ * + *

would make it such that you could retrieve the value {@code "bye"} using the keys + * {@code "good"} and {@code "goo"}, and the value {@code "fish"} using the key + * {@code "go"}. The key {@code "g"} would yield {@code null}, since it would no longer + * be a unique abbreviation.

+ * + *

The data structure is much like a "trie".

+ * + * @param a constraint on the types of the values in the map + * @author Paul Holser + * @version $Id: AbbreviationMap.java,v 1.14 2009/10/25 18:37:08 pholser Exp $ + * @see Perl's + * Text::Abbrev module + */ +public class AbbreviationMap { + private String key; + private V value; + private final Map> children = new TreeMap>(); + private int keysBeyond; + + /** + *

Tells whether the given key is in the map, or whether the given key is a unique + * abbreviation of a key that is in the map.

+ * + * @param aKey key to look up + * @return {@code true} if {@code key} is present in the map + * @throws NullPointerException if {@code key} is {@code null} + */ + public boolean contains( String aKey ) { + return get( aKey ) != null; + } + + /** + *

Answers the value associated with the given key. The key can be a unique + * abbreviation of a key that is in the map.

+ * + * @param aKey key to look up + * @return the value associated with {@code aKey}; or {@code null} if there is no + * such value or {@code aKey} is not a unique abbreviation of a key in the map + * @throws NullPointerException if {@code aKey} is {@code null} + */ + public V get( String aKey ) { + char[] chars = charsOf( aKey ); + + AbbreviationMap child = this; + for ( char each : chars ) { + child = child.children.get( each ); + if ( child == null ) + return null; + } + + return child.value; + } + + /** + *

Associates a given value with a given key. If there was a previous + * association, the old value is replaced with the new one.

+ * + * @param aKey key to create in the map + * @param newValue value to associate with the key + * @throws NullPointerException if {@code aKey} or {@code newValue} is {@code null} + * @throws IllegalArgumentException if {@code aKey} is a zero-length string + */ + public void put( String aKey, V newValue ) { + if ( newValue == null ) + throw new NullPointerException(); + if ( aKey.length() == 0 ) + throw new IllegalArgumentException(); + + char[] chars = charsOf( aKey ); + add( chars, newValue, 0, chars.length ); + } + + /** + *

Associates a given value with a given set of keys. If there was a previous + * association, the old value is replaced with the new one.

+ * + * @param keys keys to create in the map + * @param newValue value to associate with the key + * @throws NullPointerException if {@code keys} or {@code newValue} is {@code null} + * @throws IllegalArgumentException if any of {@code keys} is a zero-length string + */ + public void putAll( Iterable keys, V newValue ) { + for ( String each : keys ) + put( each, newValue ); + } + + private boolean add( char[] chars, V newValue, int offset, int length ) { + if ( offset == length ) { + value = newValue; + boolean wasAlreadyAKey = key != null; + key = new String( chars ); + return !wasAlreadyAKey; + } + + char nextChar = chars[ offset ]; + AbbreviationMap child = children.get( nextChar ); + if ( child == null ) { + child = new AbbreviationMap(); + children.put( nextChar, child ); + } + + boolean newKeyAdded = child.add( chars, newValue, offset + 1, length ); + + if ( newKeyAdded ) + ++keysBeyond; + + if ( key == null ) + value = keysBeyond > 1 ? null : newValue; + + return newKeyAdded; + } + + /** + *

If the map contains the given key, dissociates the key from its value.

+ * + * @param aKey key to remove + * @throws NullPointerException if {@code aKey} is {@code null} + * @throws IllegalArgumentException if {@code aKey} is a zero-length string + */ + public void remove( String aKey ) { + if ( aKey.length() == 0 ) + throw new IllegalArgumentException(); + + char[] keyChars = charsOf( aKey ); + remove( keyChars, 0, keyChars.length ); + } + + private boolean remove( char[] aKey, int offset, int length ) { + if ( offset == length ) + return removeAtEndOfKey(); + + char nextChar = aKey[ offset ]; + AbbreviationMap child = children.get( nextChar ); + if ( child == null || !child.remove( aKey, offset + 1, length ) ) + return false; + + --keysBeyond; + if ( child.keysBeyond == 0 ) + children.remove( nextChar ); + if ( keysBeyond == 1 && key == null ) + setValueToThatOfOnlyChild(); + + return true; + } + + private void setValueToThatOfOnlyChild() { + Map.Entry> entry = children.entrySet().iterator().next(); + AbbreviationMap onlyChild = entry.getValue(); + value = onlyChild.value; + } + + private boolean removeAtEndOfKey() { + if ( key == null ) + return false; + + key = null; + if ( keysBeyond == 1 ) + setValueToThatOfOnlyChild(); + else + value = null; + + return true; + } + + /** + * Gives a Java map representation of this abbreviation map. + * + * @return a Java map corresponding to this abbreviation map + */ + public Map toJavaUtilMap() { + Map mappings = new TreeMap(); + addToMappings( mappings ); + return mappings; + } + + private void addToMappings( Map mappings ) { + if ( key != null ) + mappings.put( key, value ); + + for ( AbbreviationMap each : children.values() ) + each.addToMappings( mappings ); + } + + private static char[] charsOf( String aKey ) { + char[] chars = new char[ aKey.length() ]; + aKey.getChars( 0, aKey.length(), chars, 0 ); + return chars; + } +} diff --git a/src/joptsimple/internal/Classes.java b/src/joptsimple/internal/Classes.java new file mode 100644 index 000000000..097409fc0 --- /dev/null +++ b/src/joptsimple/internal/Classes.java @@ -0,0 +1,51 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple.internal; + +/** + * @author Paul Holser + * @version $Id: Classes.java,v 1.10 2009/08/13 01:05:35 pholser Exp $ + */ +public final class Classes { + static { + new Classes(); + } + + private Classes() { + // nothing to do here + } + + /** + * Gives the "short version" of the given class name. Somewhat naive to inner + * classes. + * + * @param className class name to chew on + * @return the short name of the class + */ + public static String shortNameOf( String className ) { + return className.substring( className.lastIndexOf( '.' ) + 1 ); + } +} diff --git a/src/joptsimple/internal/Column.java b/src/joptsimple/internal/Column.java new file mode 100644 index 000000000..b53269f0f --- /dev/null +++ b/src/joptsimple/internal/Column.java @@ -0,0 +1,132 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple.internal; + +import java.text.BreakIterator; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import static java.lang.System.*; +import static java.text.BreakIterator.*; + +import static joptsimple.internal.Strings.*; + +/** + * @author Paul Holser + * @version $Id: Column.java,v 1.16 2009/10/25 18:37:08 pholser Exp $ + */ +public class Column { + static final Comparator BY_HEIGHT = new Comparator() { + public int compare( Column first, Column second ) { + if ( first.height() < second.height() ) + return -1; + return first.height() == second.height() ? 0 : 1; + } + }; + + private final String header; + private final List data; + private final int width; + private int height; + + Column( String header, int width ) { + this.header = header; + this.width = Math.max( width, header.length() ); + data = new LinkedList(); + height = 0; + } + + int addCells( Object cellCandidate ) { + int originalHeight = height; + + String source = String.valueOf( cellCandidate ).trim(); + for ( String eachPiece : source.split( getProperty( "line.separator" ) ) ) + processNextEmbeddedLine( eachPiece ); + + return height - originalHeight; + } + + private void processNextEmbeddedLine( String line ) { + BreakIterator words = BreakIterator.getLineInstance( Locale.US ); + words.setText( line ); + + StringBuilder nextCell = new StringBuilder(); + + int start = words.first(); + for ( int end = words.next(); end != DONE; start = end, end = words.next() ) + nextCell = processNextWord( line, nextCell, start, end ); + + if ( nextCell.length() > 0 ) + addCell( nextCell.toString() ); + } + + private StringBuilder processNextWord( String source, StringBuilder nextCell, int start, int end ) { + StringBuilder augmented = nextCell; + + String word = source.substring( start, end ); + if ( augmented.length() + word.length() > width ) { + addCell( augmented.toString() ); + augmented = new StringBuilder( " " ).append( word ); + } + else + augmented.append( word ); + + return augmented; + } + + void addCell( String newCell ) { + data.add( newCell ); + ++height; + } + + void writeHeaderOn( StringBuilder buffer, boolean appendSpace ) { + buffer.append( header ).append( repeat( ' ', width - header.length() ) ); + + if ( appendSpace ) + buffer.append( ' ' ); + } + + void writeSeparatorOn( StringBuilder buffer, boolean appendSpace ) { + buffer.append( repeat( '-', header.length() ) ).append( repeat( ' ', width - header.length() ) ); + if ( appendSpace ) + buffer.append( ' ' ); + } + + void writeCellOn( int index, StringBuilder buffer, boolean appendSpace ) { + if ( index < data.size() ) { + String item = data.get( index ); + + buffer.append( item ).append( repeat( ' ', width - item.length() ) ); + if ( appendSpace ) + buffer.append( ' ' ); + } + } + + int height() { + return height; + } +} diff --git a/src/joptsimple/internal/ColumnWidthCalculator.java b/src/joptsimple/internal/ColumnWidthCalculator.java new file mode 100644 index 000000000..0a3d28fd7 --- /dev/null +++ b/src/joptsimple/internal/ColumnWidthCalculator.java @@ -0,0 +1,42 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple.internal; + +/** + * @author Paul Holser + * @version $Id: ColumnWidthCalculator.java,v 1.4 2009/08/13 00:34:36 pholser Exp $ + */ +class ColumnWidthCalculator { + int calculate( int totalWidth, int numberOfColumns ) { + if ( numberOfColumns == 1 ) + return totalWidth; + + int remainder = totalWidth % numberOfColumns; + if ( remainder == numberOfColumns - 1 ) + return totalWidth / numberOfColumns; + return totalWidth / numberOfColumns - 1; + } +} diff --git a/src/joptsimple/internal/ColumnarData.java b/src/joptsimple/internal/ColumnarData.java new file mode 100644 index 000000000..fdc1c8021 --- /dev/null +++ b/src/joptsimple/internal/ColumnarData.java @@ -0,0 +1,162 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple.internal; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import static java.lang.Integer.*; +import static java.lang.System.*; +import static java.util.Collections.*; + +import static joptsimple.internal.Column.*; +import static joptsimple.internal.Strings.*; + +/** + *

A means to display data in a text grid.

+ * + * @author Paul Holser + * @version $Id: ColumnarData.java,v 1.17 2009/10/25 18:37:08 pholser Exp $ + */ +public class ColumnarData { + private static final String LINE_SEPARATOR = getProperty( "line.separator" ); + private static final int TOTAL_WIDTH = 80; + + private final ColumnWidthCalculator widthCalculator; + private final List columns; + private final String[] headers; + + /** + * Creates a new grid with the given column headers. + * + * @param headers column headers + */ + public ColumnarData( String... headers ) { + this.headers = headers.clone(); + widthCalculator = new ColumnWidthCalculator(); + columns = new LinkedList(); + + clear(); + } + + /** + * Adds a row to the grid. The data will fall under the corresponding headers. + * There can be fewer elements in the row than headers. Any data in columns outside + * of the number of headers will not be added to the grid. + * + * @param rowData row data to add + */ + public void addRow( Object... rowData ) { + int[] numberOfCellsAddedAt = addRowCells( rowData ); + addPaddingCells( numberOfCellsAddedAt ); + } + + /** + * Gives a string that represents the data formatted in columns. + * + * @return the formatted grid + */ + public String format() { + StringBuilder buffer = new StringBuilder(); + + writeHeadersOn( buffer ); + writeSeparatorsOn( buffer ); + writeRowsOn( buffer ); + + return buffer.toString(); + } + + /** + * Removes all data from the grid, but preserves the headers. + */ + public final void clear() { + columns.clear(); + + int desiredColumnWidth = widthCalculator.calculate( TOTAL_WIDTH, headers.length ); + for ( String each : headers ) + columns.add( new Column( each, desiredColumnWidth ) ); + } + + private void writeHeadersOn( StringBuilder buffer ) { + for ( Iterator iter = columns.iterator(); iter.hasNext(); ) + iter.next().writeHeaderOn( buffer, iter.hasNext() ); + + buffer.append( LINE_SEPARATOR ); + } + + private void writeSeparatorsOn( StringBuilder buffer ) { + for ( Iterator iter = columns.iterator(); iter.hasNext(); ) + iter.next().writeSeparatorOn( buffer, iter.hasNext() ); + + buffer.append( LINE_SEPARATOR ); + } + + private void writeRowsOn( StringBuilder buffer ) { + int maxHeight = max( columns, BY_HEIGHT ).height(); + + for ( int i = 0; i < maxHeight; ++i ) + writeRowOn( buffer, i ); + } + + private void writeRowOn( StringBuilder buffer, int rowIndex ) { + for ( Iterator iter = columns.iterator(); iter.hasNext(); ) + iter.next().writeCellOn( rowIndex, buffer, iter.hasNext() ); + + buffer.append( LINE_SEPARATOR ); + } + + private int arrayMax( int[] numbers ) { + int maximum = MIN_VALUE; + + for ( int each : numbers ) + maximum = Math.max( maximum, each ); + + return maximum; + } + + private int[] addRowCells( Object... rowData ) { + int[] cellsAddedAt = new int[ rowData.length ]; + + Iterator iter = columns.iterator(); + for ( int i = 0; iter.hasNext() && i < rowData.length; ++i ) + cellsAddedAt[ i ] = iter.next().addCells( rowData[ i ] ); + + return cellsAddedAt; + } + + private void addPaddingCells( int... numberOfCellsAddedAt ) { + int maxHeight = arrayMax( numberOfCellsAddedAt ); + + Iterator iter = columns.iterator(); + for ( int i = 0; iter.hasNext() && i < numberOfCellsAddedAt.length; ++i ) + addPaddingCellsForColumn( iter.next(), maxHeight, numberOfCellsAddedAt[ i ] ); + } + + private void addPaddingCellsForColumn( Column column, int maxHeight, int numberOfCellsAdded ) { + for ( int i = 0; i < maxHeight - numberOfCellsAdded; ++i ) + column.addCell( EMPTY ); + } +} diff --git a/src/joptsimple/internal/ConstructorInvokingValueConverter.java b/src/joptsimple/internal/ConstructorInvokingValueConverter.java new file mode 100644 index 000000000..f3b53f5e3 --- /dev/null +++ b/src/joptsimple/internal/ConstructorInvokingValueConverter.java @@ -0,0 +1,56 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple.internal; + +import java.lang.reflect.Constructor; + +import joptsimple.ValueConverter; +import static joptsimple.internal.Reflection.*; + +/** + * @param constraint on the type of values being converted to + * @author Paul Holser + * @version $Id: ConstructorInvokingValueConverter.java,v 1.4 2009/10/25 18:37:08 pholser Exp $ + */ +class ConstructorInvokingValueConverter implements ValueConverter { + private final Constructor ctor; + + ConstructorInvokingValueConverter( Constructor ctor ) { + this.ctor = ctor; + } + + public V convert( String value ) { + return instantiate( ctor, value ); + } + + public Class valueType() { + return ctor.getDeclaringClass(); + } + + public String valuePattern() { + return null; + } +} diff --git a/src/joptsimple/internal/MethodInvokingValueConverter.java b/src/joptsimple/internal/MethodInvokingValueConverter.java new file mode 100644 index 000000000..ffb2834ee --- /dev/null +++ b/src/joptsimple/internal/MethodInvokingValueConverter.java @@ -0,0 +1,58 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple.internal; + +import java.lang.reflect.Method; + +import joptsimple.ValueConverter; +import static joptsimple.internal.Reflection.*; + +/** + * @param constraint on the type of values being converted to + * @author Paul Holser + * @version $Id: MethodInvokingValueConverter.java,v 1.4 2009/10/25 18:37:08 pholser Exp $ + */ +class MethodInvokingValueConverter implements ValueConverter { + private final Method method; + private final Class clazz; + + MethodInvokingValueConverter( Method method, Class clazz ) { + this.method = method; + this.clazz = clazz; + } + + public V convert( String value ) { + return clazz.cast( invoke( method, value ) ); + } + + public Class valueType() { + return clazz; + } + + public String valuePattern() { + return null; + } +} diff --git a/src/joptsimple/internal/Objects.java b/src/joptsimple/internal/Objects.java new file mode 100644 index 000000000..683ce4f6c --- /dev/null +++ b/src/joptsimple/internal/Objects.java @@ -0,0 +1,51 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple.internal; + +/** + * @author Paul Holser + * @version $Id: Objects.java,v 1.2 2009/10/25 18:37:08 pholser Exp $ + */ +public final class Objects { + static { + new Objects(); + } + + private Objects() { + // nothing to do here + } + + /** + * Rejects {@code null} references. + * + * @param target reference to check + * @throws NullPointerException if {@code target} is {@code null} + */ + public static void ensureNotNull( Object target ) { + if ( target == null ) + throw new NullPointerException(); + } +} diff --git a/src/joptsimple/internal/Reflection.java b/src/joptsimple/internal/Reflection.java new file mode 100644 index 000000000..8d124d2b5 --- /dev/null +++ b/src/joptsimple/internal/Reflection.java @@ -0,0 +1,142 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple.internal; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import static java.lang.reflect.Modifier.*; + +import joptsimple.ValueConverter; + +/** + *

Helper methods for reflection.

+ * + * @author Paul Holser + * @version $Id: Reflection.java,v 1.20 2009/09/28 01:12:48 pholser Exp $ + */ +public final class Reflection { + static { + new Reflection(); + } + + private Reflection() { + // nothing to do here + } + + /** + * Finds an appropriate value converter for the given class. + * + * @param a constraint on the class object to introspect + * @param clazz class to introspect on + * @return a converter method or constructor + */ + public static ValueConverter findConverter( Class clazz ) { + ValueConverter valueOf = valueOfConverter( clazz ); + if ( valueOf != null ) + return valueOf; + + ValueConverter constructor = constructorConverter( clazz ); + if ( constructor != null ) + return constructor; + + throw new IllegalArgumentException( clazz + " is not a value type" ); + } + + private static ValueConverter valueOfConverter( Class clazz ) { + try { + Method valueOf = clazz.getDeclaredMethod( "valueOf", String.class ); + if ( !meetsConverterRequirements( valueOf, clazz ) ) + return null; + + return new MethodInvokingValueConverter( valueOf, clazz ); + } + catch ( NoSuchMethodException ignored ) { + return null; + } + } + + private static ValueConverter constructorConverter( Class clazz ) { + try { + return new ConstructorInvokingValueConverter( + clazz.getConstructor( String.class ) ); + } + catch ( NoSuchMethodException ignored ) { + return null; + } + } + + /** + * Invokes the given constructor with the given arguments. + * + * @param constraint on the type of the objects yielded by the constructor + * @param constructor constructor to invoke + * @param args arguments to hand to the constructor + * @return the result of invoking the constructor + * @throws ReflectionException in lieu of the gaggle of reflection-related exceptions + */ + public static T instantiate( Constructor constructor, Object... args ) { + try { + return constructor.newInstance( args ); + } + catch ( Exception ex ) { + throw reflectionException( ex ); + } + } + + /** + * Invokes the given static method with the given arguments. + * + * @param method method to invoke + * @param args arguments to hand to the method + * @return the result of invoking the method + * @throws ReflectionException in lieu of the gaggle of reflection-related exceptions + */ + public static Object invoke( Method method, Object... args ) { + try { + return method.invoke( null, args ); + } + catch ( Exception ex ) { + throw reflectionException( ex ); + } + } + + private static boolean meetsConverterRequirements( Method method, Class expectedReturnType ) { + int modifiers = method.getModifiers(); + return isPublic( modifiers ) && isStatic( modifiers ) && expectedReturnType.equals( method.getReturnType() ); + } + + private static RuntimeException reflectionException( Exception ex ) { + if ( ex instanceof IllegalArgumentException ) + return new ReflectionException( ex ); + if ( ex instanceof InvocationTargetException ) + return new ReflectionException( ex.getCause() ); + if ( ex instanceof RuntimeException ) + return (RuntimeException) ex; + + return new ReflectionException( ex ); + } +} diff --git a/src/joptsimple/internal/ReflectionException.java b/src/joptsimple/internal/ReflectionException.java new file mode 100644 index 000000000..e6e842426 --- /dev/null +++ b/src/joptsimple/internal/ReflectionException.java @@ -0,0 +1,40 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple.internal; + +/** + *

This unchecked exception wraps reflection-oriented exceptions.

+ * + * @author Paul Holser + * @version $Id: ReflectionException.java,v 1.5 2009/08/13 00:34:36 pholser Exp $ + */ +public class ReflectionException extends RuntimeException { + private static final long serialVersionUID = -2L; + + ReflectionException( Throwable cause ) { + super( cause.toString() ); + } +} diff --git a/src/joptsimple/internal/Strings.java b/src/joptsimple/internal/Strings.java new file mode 100644 index 000000000..55d5a9764 --- /dev/null +++ b/src/joptsimple/internal/Strings.java @@ -0,0 +1,124 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple.internal; + +import java.util.Iterator; +import java.util.List; +import static java.lang.System.*; +import static java.util.Arrays.*; + +/** + * @author Paul Holser + * @version $Id: Strings.java,v 1.16 2009/08/13 01:05:35 pholser Exp $ + */ +public final class Strings { + public static final String EMPTY = ""; + public static final String SINGLE_QUOTE = "'"; + public static final String LINE_SEPARATOR = getProperty( "line.separator" ); + + static { + new Strings(); + } + + private Strings() { + // nothing to do here + } + + /** + *

Gives a string consisting of the given character repeated the given number of + * times.

+ * + * @param ch the character to repeat + * @param count how many times to repeat the character + * @return the resultant string + */ + public static String repeat( char ch, int count ) { + StringBuilder buffer = new StringBuilder(); + + for ( int i = 0; i < count; ++i ) + buffer.append( ch ); + + return buffer.toString(); + } + + /** + *

Tells whether the given string is either {@code} or consists solely of + * whitespace characters.

+ * + * @param target string to check + * @return {@code true} if the target string is null or empty + */ + public static boolean isNullOrEmpty( String target ) { + return target == null || EMPTY.equals( target ); + } + + + /** + *

Gives a string consisting of a given string prepended and appended with + * surrounding characters.

+ * + * @param target a string + * @param begin character to prepend + * @param end character to append + * @return the surrounded string + */ + public static String surround( String target, char begin, char end ) { + return begin + target + end; + } + + /** + * Gives a string consisting of the elements of a given array of strings, each + * separated by a given separator string. + * + * @param pieces the strings to join + * @param separator the separator + * @return the joined string + */ + public static String join( String[] pieces, String separator ) { + return join( asList( pieces ), separator ); + } + + /** + * Gives a string consisting of the string representations of the elements of a + * given array of objects, each separated by a given separator string. + * + * @param pieces the elements whose string representations are to be joined + * @param separator the separator + * @return the joined string + */ + public static String join( List pieces, String separator ) { + StringBuilder buffer = new StringBuilder(); + + for ( Iterator iter = pieces.iterator(); iter.hasNext(); ) { + buffer.append( iter.next() ); + + if ( iter.hasNext() ) + buffer.append( separator ); + } + + return buffer.toString(); + } +} diff --git a/src/joptsimple/util/DateConverter.java b/src/joptsimple/util/DateConverter.java new file mode 100644 index 000000000..021afff37 --- /dev/null +++ b/src/joptsimple/util/DateConverter.java @@ -0,0 +1,101 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple.util; + +import java.text.DateFormat; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.Date; + +import joptsimple.ValueConversionException; +import joptsimple.ValueConverter; + +/** + * Converts values to {@link Date}s using a {@link DateFormat} object. + * + * @author Paul Holser + * @version $Id: DateConverter.java,v 1.6 2009/10/25 18:37:09 pholser Exp $ + */ +public class DateConverter implements ValueConverter { + private final DateFormat formatter; + + /** + * Creates a converter that uses the given date formatter/parser. + * + * @param formatter the formatter/parser to use + * @throws NullPointerException if {@code formatter} is {@code null} + */ + public DateConverter( DateFormat formatter ) { + if ( formatter == null ) + throw new NullPointerException( "illegal null formatter" ); + + this.formatter = formatter; + } + + /** + * Creates a converter that uses a {@link SimpleDateFormat} with the given date/time + * pattern. The date formatter created is not + * {@link SimpleDateFormat#setLenient(boolean) lenient}. + * + * @param pattern expected date/time pattern + * @return the new converter + * @throws NullPointerException if {@code pattern} is {@code null} + * @throws IllegalArgumentException if {@code pattern} is invalid + */ + public static DateConverter datePattern( String pattern ) { + SimpleDateFormat formatter = new SimpleDateFormat( pattern ); + formatter.setLenient( false ); + + return new DateConverter( formatter ); + } + + public Date convert( String value ) { + ParsePosition position = new ParsePosition( 0 ); + + Date date = formatter.parse( value, position ); + if ( position.getIndex() != value.length() ) + throw new ValueConversionException( message( value ) ); + + return date; + } + + public Class valueType() { + return Date.class; + } + + public String valuePattern() { + return formatter instanceof SimpleDateFormat + ? ( (SimpleDateFormat) formatter ).toLocalizedPattern() + : ""; + } + + private String message( String value ) { + String message = "Value [" + value + "] does not match date/time pattern"; + if ( formatter instanceof SimpleDateFormat ) + message += " [" + ( (SimpleDateFormat) formatter ).toLocalizedPattern() + ']'; + + return message; + } +} diff --git a/src/joptsimple/util/KeyValuePair.java b/src/joptsimple/util/KeyValuePair.java new file mode 100644 index 000000000..7a4a9c0b5 --- /dev/null +++ b/src/joptsimple/util/KeyValuePair.java @@ -0,0 +1,84 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple.util; + +import static joptsimple.internal.Strings.*; + +/** + *

A simple string key/string value pair.

+ * + *

This is useful as an argument type for options whose values take on the form + * key=value, such as JVM command line system properties.

+ * + * @author Paul Holser + * @version $Id: KeyValuePair.java,v 1.10 2009/10/25 18:37:09 pholser Exp $ + */ +public final class KeyValuePair { + public final String key; + public final String value; + + private KeyValuePair( String key, String value ) { + this.key = key; + this.value = value; + } + + /** + * Parses a string assumed to be of the form key=value into its parts. + * + * @param asString key-value string + * @return a key-value pair + * @throws NullPointerException if {@code stringRepresentation} is {@code null} + */ + public static KeyValuePair valueOf( String asString ) { + int equalsIndex = asString.indexOf( '=' ); + if ( equalsIndex == -1 ) + return new KeyValuePair( asString, EMPTY ); + + String aKey = asString.substring( 0, equalsIndex ); + String aValue = equalsIndex == asString.length() - 1 ? EMPTY : asString.substring( equalsIndex + 1 ); + + return new KeyValuePair( aKey, aValue ); + } + + @Override + public boolean equals( Object that ) { + if ( !( that instanceof KeyValuePair ) ) + return false; + + KeyValuePair other = (KeyValuePair) that; + return key.equals( other.key ) && value.equals( other.value ); + } + + @Override + public int hashCode() { + return key.hashCode() ^ value.hashCode(); + } + + @Override + public String toString() { + return key + '=' + value; + } +} diff --git a/src/joptsimple/util/RegexMatcher.java b/src/joptsimple/util/RegexMatcher.java new file mode 100644 index 000000000..6d1ad33a3 --- /dev/null +++ b/src/joptsimple/util/RegexMatcher.java @@ -0,0 +1,86 @@ +/* + The MIT License + + Copyright (c) 2009 Paul R. Holser, Jr. + + 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 joptsimple.util; + +import java.util.regex.Pattern; +import static java.util.regex.Pattern.*; + +import joptsimple.ValueConversionException; +import joptsimple.ValueConverter; + +/** + * Ensures that values entirely match a regular expression. + * + * @author Paul Holser + * @version $Id: RegexMatcher.java,v 1.6 2009/10/25 18:37:09 pholser Exp $ + */ +public class RegexMatcher implements ValueConverter { + private final Pattern pattern; + + /** + * Creates a matcher that uses the given regular expression, modified by the given + * flags. + * + * @param pattern the regular expression pattern + * @param flags modifying regex flags + * @throws IllegalArgumentException if bit values other than those corresponding to + * the defined match flags are set in {@code flags} + * @throws java.util.regex.PatternSyntaxException if the expression's syntax is + * invalid + */ + public RegexMatcher( String pattern, int flags ) { + this.pattern = compile( pattern, flags ); + } + + /** + * Gives a matcher that uses the given regular expression. + * + * @param pattern the regular expression pattern + * @return the new converter + * @throws java.util.regex.PatternSyntaxException if the expression's syntax is + * invalid + */ + public static ValueConverter regex( String pattern ) { + return new RegexMatcher( pattern, 0 ); + } + + public String convert( String value ) { + if ( !pattern.matcher( value ).matches() ) { + throw new ValueConversionException( + "Value [" + value + "] did not match regex [" + pattern.pattern() + ']' ); + } + + return value; + } + + public Class valueType() { + return String.class; + } + + public String valuePattern() { + return pattern.pattern(); + } +} diff --git a/src/org/jnbt/ByteArrayTag.java b/src/org/jnbt/ByteArrayTag.java new file mode 100644 index 000000000..3e84b294b --- /dev/null +++ b/src/org/jnbt/ByteArrayTag.java @@ -0,0 +1,81 @@ +package org.jnbt; + +/* + * JNBT License + * + * Copyright (c) 2010 Graham Edgecombe + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the JNBT team nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * The TAG_Byte_Array tag. + * @author Graham Edgecombe + * + */ +public final class ByteArrayTag extends Tag { + + /** + * The value. + */ + private final byte[] value; + + /** + * Creates the tag. + * @param name The name. + * @param value The value. + */ + public ByteArrayTag(String name, byte[] value) { + super(name); + this.value = value; + } + + @Override + public byte[] getValue() { + return value; + } + + @Override + public String toString() { + StringBuilder hex = new StringBuilder(); + for(byte b : value) { + String hexDigits = Integer.toHexString(b).toUpperCase(); + if(hexDigits.length() == 1) { + hex.append("0"); + } + hex.append(hexDigits).append(" "); + } + String name = getName(); + String append = ""; + if(name != null && !name.equals("")) { + append = "(\"" + this.getName() + "\")"; + } + return "TAG_Byte_Array" + append + ": " + hex.toString(); + } + +} diff --git a/src/org/jnbt/ByteTag.java b/src/org/jnbt/ByteTag.java new file mode 100644 index 000000000..b255cee70 --- /dev/null +++ b/src/org/jnbt/ByteTag.java @@ -0,0 +1,73 @@ +package org.jnbt; + +/* + * JNBT License + * + * Copyright (c) 2010 Graham Edgecombe + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the JNBT team nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * The TAG_Byte tag. + * @author Graham Edgecombe + * + */ +public final class ByteTag extends Tag { + + /** + * The value. + */ + private final byte value; + + /** + * Creates the tag. + * @param name The name. + * @param value The value. + */ + public ByteTag(String name, byte value) { + super(name); + this.value = value; + } + + @Override + public Byte getValue() { + return value; + } + + @Override + public String toString() { + String name = getName(); + String append = ""; + if(name != null && !name.equals("")) { + append = "(\"" + this.getName() + "\")"; + } + return "TAG_Byte" + append + ": " + value; + } + +} diff --git a/src/org/jnbt/CompoundTag.java b/src/org/jnbt/CompoundTag.java new file mode 100644 index 000000000..ce9c07927 --- /dev/null +++ b/src/org/jnbt/CompoundTag.java @@ -0,0 +1,82 @@ +package org.jnbt; + +/* + * JNBT License + * + * Copyright (c) 2010 Graham Edgecombe + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the JNBT team nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +import java.util.Collections; +import java.util.Map; + +/** + * The TAG_Compound tag. + * @author Graham Edgecombe + * + */ +public final class CompoundTag extends Tag { + + /** + * The value. + */ + private final Map value; + + /** + * Creates the tag. + * @param name The name. + * @param value The value. + */ + public CompoundTag(String name, Map value) { + super(name); + this.value = Collections.unmodifiableMap(value); + } + + @Override + public Map getValue() { + return value; + } + + @Override + public String toString() { + String name = getName(); + String append = ""; + if(name != null && !name.equals("")) { + append = "(\"" + this.getName() + "\")"; + } + StringBuilder bldr = new StringBuilder(); + bldr.append("TAG_Compound" + append + ": " + value.size() + " entries\r\n{\r\n"); + for(Map.Entry entry : value.entrySet()) { + bldr.append(" " + entry.getValue().toString().replaceAll("\r\n", "\r\n ") + "\r\n"); + } + bldr.append("}"); + return bldr.toString(); + } + +} diff --git a/src/org/jnbt/DoubleTag.java b/src/org/jnbt/DoubleTag.java new file mode 100644 index 000000000..668e0fd02 --- /dev/null +++ b/src/org/jnbt/DoubleTag.java @@ -0,0 +1,73 @@ +package org.jnbt; + +/* + * JNBT License + * + * Copyright (c) 2010 Graham Edgecombe + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the JNBT team nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * The TAG_Double tag. + * @author Graham Edgecombe + * + */ +public final class DoubleTag extends Tag { + + /** + * The value. + */ + private final double value; + + /** + * Creates the tag. + * @param name The name. + * @param value The value. + */ + public DoubleTag(String name, double value) { + super(name); + this.value = value; + } + + @Override + public Double getValue() { + return value; + } + + @Override + public String toString() { + String name = getName(); + String append = ""; + if(name != null && !name.equals("")) { + append = "(\"" + this.getName() + "\")"; + } + return "TAG_Double" + append + ": " + value; + } + +} diff --git a/src/org/jnbt/EndTag.java b/src/org/jnbt/EndTag.java new file mode 100644 index 000000000..1f6b7f313 --- /dev/null +++ b/src/org/jnbt/EndTag.java @@ -0,0 +1,60 @@ +package org.jnbt; + +/* + * JNBT License + * + * Copyright (c) 2010 Graham Edgecombe + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the JNBT team nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * The TAG_End tag. + * @author Graham Edgecombe + * + */ +public final class EndTag extends Tag { + + /** + * Creates the tag. + */ + public EndTag() { + super(""); + } + + @Override + public Object getValue() { + return null; + } + + @Override + public String toString() { + return "TAG_End"; + } + +} diff --git a/src/org/jnbt/FloatTag.java b/src/org/jnbt/FloatTag.java new file mode 100644 index 000000000..3921a6478 --- /dev/null +++ b/src/org/jnbt/FloatTag.java @@ -0,0 +1,73 @@ +package org.jnbt; + +/* + * JNBT License + * + * Copyright (c) 2010 Graham Edgecombe + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the JNBT team nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * The TAG_Float tag. + * @author Graham Edgecombe + * + */ +public final class FloatTag extends Tag { + + /** + * The value. + */ + private final float value; + + /** + * Creates the tag. + * @param name The name. + * @param value The value. + */ + public FloatTag(String name, float value) { + super(name); + this.value = value; + } + + @Override + public Float getValue() { + return value; + } + + @Override + public String toString() { + String name = getName(); + String append = ""; + if(name != null && !name.equals("")) { + append = "(\"" + this.getName() + "\")"; + } + return "TAG_Float" + append + ": " + value; + } + +} diff --git a/src/org/jnbt/IntTag.java b/src/org/jnbt/IntTag.java new file mode 100644 index 000000000..ad2e43361 --- /dev/null +++ b/src/org/jnbt/IntTag.java @@ -0,0 +1,73 @@ +package org.jnbt; + +/* + * JNBT License + * + * Copyright (c) 2010 Graham Edgecombe + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the JNBT team nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * The TAG_Int tag. + * @author Graham Edgecombe + * + */ +public final class IntTag extends Tag { + + /** + * The value. + */ + private final int value; + + /** + * Creates the tag. + * @param name The name. + * @param value The value. + */ + public IntTag(String name, int value) { + super(name); + this.value = value; + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public String toString() { + String name = getName(); + String append = ""; + if(name != null && !name.equals("")) { + append = "(\"" + this.getName() + "\")"; + } + return "TAG_Int" + append + ": " + value; + } + +} diff --git a/src/org/jnbt/ListTag.java b/src/org/jnbt/ListTag.java new file mode 100644 index 000000000..4d267033f --- /dev/null +++ b/src/org/jnbt/ListTag.java @@ -0,0 +1,97 @@ +package org.jnbt; + +/* + * JNBT License + * + * Copyright (c) 2010 Graham Edgecombe + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the JNBT team nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +import java.util.Collections; +import java.util.List; + +/** + * The TAG_List tag. + * @author Graham Edgecombe + * + */ +public final class ListTag extends Tag { + + /** + * The type. + */ + private final Class type; + + /** + * The value. + */ + private final List value; + + /** + * Creates the tag. + * @param name The name. + * @param type The type of item in the list. + * @param value The value. + */ + public ListTag(String name, Class type, List value) { + super(name); + this.type = type; + this.value = Collections.unmodifiableList(value); + } + + /** + * Gets the type of item in this list. + * @return The type of item in this list. + */ + public Class getType() { + return type; + } + + @Override + public List getValue() { + return value; + } + + @Override + public String toString() { + String name = getName(); + String append = ""; + if(name != null && !name.equals("")) { + append = "(\"" + this.getName() + "\")"; + } + StringBuilder bldr = new StringBuilder(); + bldr.append("TAG_List" + append + ": " + value.size() + " entries of type " + NBTUtils.getTypeName(type) + "\r\n{\r\n"); + for(Tag t : value) { + bldr.append(" " + t.toString().replaceAll("\r\n", "\r\n ") + "\r\n"); + } + bldr.append("}"); + return bldr.toString(); + } + +} diff --git a/src/org/jnbt/LongTag.java b/src/org/jnbt/LongTag.java new file mode 100644 index 000000000..c337de889 --- /dev/null +++ b/src/org/jnbt/LongTag.java @@ -0,0 +1,73 @@ +package org.jnbt; + +/* + * JNBT License + * + * Copyright (c) 2010 Graham Edgecombe + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the JNBT team nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * The TAG_Long tag. + * @author Graham Edgecombe + * + */ +public final class LongTag extends Tag { + + /** + * The value. + */ + private final long value; + + /** + * Creates the tag. + * @param name The name. + * @param value The value. + */ + public LongTag(String name, long value) { + super(name); + this.value = value; + } + + @Override + public Long getValue() { + return value; + } + + @Override + public String toString() { + String name = getName(); + String append = ""; + if(name != null && !name.equals("")) { + append = "(\"" + this.getName() + "\")"; + } + return "TAG_Long" + append + ": " + value; + } + +} diff --git a/src/org/jnbt/NBTConstants.java b/src/org/jnbt/NBTConstants.java new file mode 100644 index 000000000..5dced4fcb --- /dev/null +++ b/src/org/jnbt/NBTConstants.java @@ -0,0 +1,72 @@ +package org.jnbt; + +import java.nio.charset.Charset; + +/* + * JNBT License + * + * Copyright (c) 2010 Graham Edgecombe + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the JNBT team nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * A class which holds constant values. + * @author Graham Edgecombe + * + */ +public final class NBTConstants { + + /** + * The character set used by NBT (UTF-8). + */ + public static final Charset CHARSET = Charset.forName("UTF-8"); + + /** + * Tag type constants. + */ + public static final int TYPE_END = 0, + TYPE_BYTE = 1, + TYPE_SHORT = 2, + TYPE_INT = 3, + TYPE_LONG = 4, + TYPE_FLOAT = 5, + TYPE_DOUBLE = 6, + TYPE_BYTE_ARRAY = 7, + TYPE_STRING = 8, + TYPE_LIST = 9, + TYPE_COMPOUND = 10; + + /** + * Default private constructor. + */ + private NBTConstants() { + + } + +} diff --git a/src/org/jnbt/NBTInputStream.java b/src/org/jnbt/NBTInputStream.java new file mode 100644 index 000000000..4ba8c3c0a --- /dev/null +++ b/src/org/jnbt/NBTInputStream.java @@ -0,0 +1,179 @@ +package org.jnbt; + +/* + * JNBT License + * + * Copyright (c) 2010 Graham Edgecombe + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the JNBT team nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +import java.io.Closeable; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.GZIPInputStream; + +/** + *

This class reads NBT, or + * Named Binary Tag streams, and produces an object graph of + * subclasses of the Tag object.

+ * + *

The NBT format was created by Markus Persson, and the specification may + * be found at + * http://www.minecraft.net/docs/NBT.txt.

+ * @author Graham Edgecombe + * + */ +public final class NBTInputStream implements Closeable { + + /** + * The data input stream. + */ + private final DataInputStream is; + + /** + * Creates a new NBTInputStream, which will source its data + * from the specified input stream. + * @param is The input stream. + * @throws IOException if an I/O error occurs. + */ + public NBTInputStream(InputStream is) throws IOException { + this.is = new DataInputStream(new GZIPInputStream(is)); + } + + /** + * Reads an NBT tag from the stream. + * @return The tag that was read. + * @throws IOException if an I/O error occurs. + */ + public Tag readTag() throws IOException { + return readTag(0); + } + + /** + * Reads an NBT from the stream. + * @param depth The depth of this tag. + * @return The tag that was read. + * @throws IOException if an I/O error occurs. + */ + private Tag readTag(int depth) throws IOException { + int type = is.readByte() & 0xFF; + + String name; + if(type != NBTConstants.TYPE_END) { + int nameLength = is.readShort() & 0xFFFF; + byte[] nameBytes = new byte[nameLength]; + is.readFully(nameBytes); + name = new String(nameBytes, NBTConstants.CHARSET); + } else { + name = ""; + } + + return readTagPayload(type, name, depth); + } + + /** + * Reads the payload of a tag, given the name and type. + * @param type The type. + * @param name The name. + * @param depth The depth. + * @return The tag. + * @throws IOException if an I/O error occurs. + */ + private Tag readTagPayload(int type, String name, int depth) throws IOException { + switch(type) { + case NBTConstants.TYPE_END: + if(depth == 0) { + throw new IOException("TAG_End found without a TAG_Compound/TAG_List tag preceding it."); + } else { + return new EndTag(); + } + case NBTConstants.TYPE_BYTE: + return new ByteTag(name, is.readByte()); + case NBTConstants.TYPE_SHORT: + return new ShortTag(name, is.readShort()); + case NBTConstants.TYPE_INT: + return new IntTag(name, is.readInt()); + case NBTConstants.TYPE_LONG: + return new LongTag(name, is.readLong()); + case NBTConstants.TYPE_FLOAT: + return new FloatTag(name, is.readFloat()); + case NBTConstants.TYPE_DOUBLE: + return new DoubleTag(name, is.readDouble()); + case NBTConstants.TYPE_BYTE_ARRAY: + int length = is.readInt(); + byte[] bytes = new byte[length]; + is.readFully(bytes); + return new ByteArrayTag(name, bytes); + case NBTConstants.TYPE_STRING: + length = is.readShort(); + bytes = new byte[length]; + is.readFully(bytes); + return new StringTag(name, new String(bytes, NBTConstants.CHARSET)); + case NBTConstants.TYPE_LIST: + int childType = is.readByte(); + length = is.readInt(); + + List tagList = new ArrayList(); + for(int i = 0; i < length; i++) { + Tag tag = readTagPayload(childType, "", depth + 1); + if(tag instanceof EndTag) { + throw new IOException("TAG_End not permitted in a list."); + } + tagList.add(tag); + } + + return new ListTag(name, NBTUtils.getTypeClass(childType), tagList); + case NBTConstants.TYPE_COMPOUND: + Map tagMap = new HashMap(); + while(true) { + Tag tag = readTag(depth + 1); + if(tag instanceof EndTag) { + break; + } else { + tagMap.put(tag.getName(), tag); + } + } + + return new CompoundTag(name, tagMap); + default: + throw new IOException("Invalid tag type: " + type + "."); + } + } + + @Override + public void close() throws IOException { + is.close(); + } + +} diff --git a/src/org/jnbt/NBTOutputStream.java b/src/org/jnbt/NBTOutputStream.java new file mode 100644 index 000000000..a9a3c8956 --- /dev/null +++ b/src/org/jnbt/NBTOutputStream.java @@ -0,0 +1,257 @@ +package org.jnbt; + +import java.io.Closeable; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; +import java.util.zip.GZIPOutputStream; + +/* + * JNBT License + * + * Copyright (c) 2010 Graham Edgecombe + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the JNBT team nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + *

This class writes NBT, or + * Named Binary Tag Tag objects to an underlying + * OutputStream.

+ * + *

The NBT format was created by Markus Persson, and the specification may + * be found at + * http://www.minecraft.net/docs/NBT.txt.

+ * @author Graham Edgecombe + * + */ +public final class NBTOutputStream implements Closeable { + + /** + * The output stream. + */ + private final DataOutputStream os; + + /** + * Creates a new NBTOutputStream, which will write data to the + * specified underlying output stream. + * @param os The output stream. + * @throws IOException if an I/O error occurs. + */ + public NBTOutputStream(OutputStream os) throws IOException { + this.os = new DataOutputStream(new GZIPOutputStream(os)); + } + + /** + * Writes a tag. + * @param tag The tag to write. + * @throws IOException if an I/O error occurs. + */ + public void writeTag(Tag tag) throws IOException { + int type = NBTUtils.getTypeCode(tag.getClass()); + String name = tag.getName(); + byte[] nameBytes = name.getBytes(NBTConstants.CHARSET); + + os.writeByte(type); + os.writeShort(nameBytes.length); + os.write(nameBytes); + + if(type == NBTConstants.TYPE_END) { + throw new IOException("Named TAG_End not permitted."); + } + + writeTagPayload(tag); + } + + /** + * Writes tag payload. + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeTagPayload(Tag tag) throws IOException { + int type = NBTUtils.getTypeCode(tag.getClass()); + switch(type) { + case NBTConstants.TYPE_END: + writeEndTagPayload((EndTag) tag); + break; + case NBTConstants.TYPE_BYTE: + writeByteTagPayload((ByteTag) tag); + break; + case NBTConstants.TYPE_SHORT: + writeShortTagPayload((ShortTag) tag); + break; + case NBTConstants.TYPE_INT: + writeIntTagPayload((IntTag) tag); + break; + case NBTConstants.TYPE_LONG: + writeLongTagPayload((LongTag) tag); + break; + case NBTConstants.TYPE_FLOAT: + writeFloatTagPayload((FloatTag) tag); + break; + case NBTConstants.TYPE_DOUBLE: + writeDoubleTagPayload((DoubleTag) tag); + break; + case NBTConstants.TYPE_BYTE_ARRAY: + writeByteArrayTagPayload((ByteArrayTag) tag); + break; + case NBTConstants.TYPE_STRING: + writeStringTagPayload((StringTag) tag); + break; + case NBTConstants.TYPE_LIST: + writeListTagPayload((ListTag) tag); + break; + case NBTConstants.TYPE_COMPOUND: + writeCompoundTagPayload((CompoundTag) tag); + break; + default: + throw new IOException("Invalid tag type: " + type + "."); + } + } + + /** + * Writes a TAG_Byte tag. + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeByteTagPayload(ByteTag tag) throws IOException { + os.writeByte(tag.getValue()); + } + + /** + * Writes a TAG_Byte_Array tag. + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeByteArrayTagPayload(ByteArrayTag tag) throws IOException { + byte[] bytes = tag.getValue(); + os.writeInt(bytes.length); + os.write(bytes); + } + + /** + * Writes a TAG_Compound tag. + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeCompoundTagPayload(CompoundTag tag) throws IOException { + for(Tag childTag : tag.getValue().values()) { + writeTag(childTag); + } + os.writeByte((byte) 0); // end tag - better way? + } + + /** + * Writes a TAG_List tag. + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeListTagPayload(ListTag tag) throws IOException { + Class clazz = tag.getType(); + List tags = tag.getValue(); + int size = tags.size(); + + os.writeByte(NBTUtils.getTypeCode(clazz)); + os.writeInt(size); + for(int i = 0; i < size; i++) { + writeTagPayload(tags.get(i)); + } + } + + /** + * Writes a TAG_String tag. + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeStringTagPayload(StringTag tag) throws IOException { + byte[] bytes = tag.getValue().getBytes(NBTConstants.CHARSET); + os.writeShort(bytes.length); + os.write(bytes); + } + + /** + * Writes a TAG_Double tag. + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeDoubleTagPayload(DoubleTag tag) throws IOException { + os.writeDouble(tag.getValue()); + } + + /** + * Writes a TAG_Float tag. + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeFloatTagPayload(FloatTag tag) throws IOException { + os.writeFloat(tag.getValue()); + } + + /** + * Writes a TAG_Long tag. + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeLongTagPayload(LongTag tag) throws IOException { + os.writeLong(tag.getValue()); + } + + /** + * Writes a TAG_Int tag. + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeIntTagPayload(IntTag tag) throws IOException { + os.writeInt(tag.getValue()); + } + + /** + * Writes a TAG_Short tag. + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeShortTagPayload(ShortTag tag) throws IOException { + os.writeShort(tag.getValue()); + } + + /** + * Writes a TAG_Empty tag. + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeEndTagPayload(EndTag tag) { + /* empty */ + } + + @Override + public void close() throws IOException { + os.close(); + } + +} diff --git a/src/org/jnbt/NBTUtils.java b/src/org/jnbt/NBTUtils.java new file mode 100644 index 000000000..823b04ae6 --- /dev/null +++ b/src/org/jnbt/NBTUtils.java @@ -0,0 +1,152 @@ +package org.jnbt; + +/* + * JNBT License + * + * Copyright (c) 2010 Graham Edgecombe + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the JNBT team nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * A class which contains NBT-related utility methods. + * @author Graham Edgecombe + * + */ +public final class NBTUtils { + + /** + * Gets the type name of a tag. + * @param clazz The tag class. + * @return The type name. + */ + public static String getTypeName(Class clazz) { + if(clazz.equals(ByteArrayTag.class)) { + return "TAG_Byte_Array"; + } else if(clazz.equals(ByteTag.class)) { + return "TAG_Byte"; + } else if(clazz.equals(CompoundTag.class)) { + return "TAG_Compound"; + } else if(clazz.equals(DoubleTag.class)) { + return "TAG_Double"; + } else if(clazz.equals(EndTag.class)) { + return "TAG_End"; + } else if(clazz.equals(FloatTag.class)) { + return "TAG_Float"; + } else if(clazz.equals(IntTag.class)) { + return "TAG_Int"; + } else if(clazz.equals(ListTag.class)) { + return "TAG_List"; + } else if(clazz.equals(LongTag.class)) { + return "TAG_Long"; + } else if(clazz.equals(ShortTag.class)) { + return "TAG_Short"; + } else if(clazz.equals(StringTag.class)) { + return "TAG_String"; + } else { + throw new IllegalArgumentException("Invalid tag classs (" + clazz.getName() + ")."); + } + } + + /** + * Gets the type code of a tag class. + * @param clazz The tag class. + * @return The type code. + * @throws IllegalArgumentException if the tag class is invalid. + */ + public static int getTypeCode(Class clazz) { + if(clazz.equals(ByteArrayTag.class)) { + return NBTConstants.TYPE_BYTE_ARRAY; + } else if(clazz.equals(ByteTag.class)) { + return NBTConstants.TYPE_BYTE; + } else if(clazz.equals(CompoundTag.class)) { + return NBTConstants.TYPE_COMPOUND; + } else if(clazz.equals(DoubleTag.class)) { + return NBTConstants.TYPE_DOUBLE; + } else if(clazz.equals(EndTag.class)) { + return NBTConstants.TYPE_END; + } else if(clazz.equals(FloatTag.class)) { + return NBTConstants.TYPE_FLOAT; + } else if(clazz.equals(IntTag.class)) { + return NBTConstants.TYPE_INT; + } else if(clazz.equals(ListTag.class)) { + return NBTConstants.TYPE_LIST; + } else if(clazz.equals(LongTag.class)) { + return NBTConstants.TYPE_LONG; + } else if(clazz.equals(ShortTag.class)) { + return NBTConstants.TYPE_SHORT; + } else if(clazz.equals(StringTag.class)) { + return NBTConstants.TYPE_STRING; + } else { + throw new IllegalArgumentException("Invalid tag classs (" + clazz.getName() + ")."); + } + } + + /** + * Gets the class of a type of tag. + * @param type The type. + * @return The class. + * @throws IllegalArgumentException if the tag type is invalid. + */ + public static Class getTypeClass(int type) { + switch(type) { + case NBTConstants.TYPE_END: + return EndTag.class; + case NBTConstants.TYPE_BYTE: + return ByteTag.class; + case NBTConstants.TYPE_SHORT: + return ShortTag.class; + case NBTConstants.TYPE_INT: + return IntTag.class; + case NBTConstants.TYPE_LONG: + return LongTag.class; + case NBTConstants.TYPE_FLOAT: + return FloatTag.class; + case NBTConstants.TYPE_DOUBLE: + return DoubleTag.class; + case NBTConstants.TYPE_BYTE_ARRAY: + return ByteArrayTag.class; + case NBTConstants.TYPE_STRING: + return StringTag.class; + case NBTConstants.TYPE_LIST: + return ListTag.class; + case NBTConstants.TYPE_COMPOUND: + return CompoundTag.class; + default: + throw new IllegalArgumentException("Invalid tag type : " + type + "."); + } + } + + /** + * Default private constructor. + */ + private NBTUtils() { + + } + +} diff --git a/src/org/jnbt/ShortTag.java b/src/org/jnbt/ShortTag.java new file mode 100644 index 000000000..11e2f3afd --- /dev/null +++ b/src/org/jnbt/ShortTag.java @@ -0,0 +1,73 @@ +package org.jnbt; + +/* + * JNBT License + * + * Copyright (c) 2010 Graham Edgecombe + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the JNBT team nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * The TAG_Short tag. + * @author Graham Edgecombe + * + */ +public final class ShortTag extends Tag { + + /** + * The value. + */ + private final short value; + + /** + * Creates the tag. + * @param name The name. + * @param value The value. + */ + public ShortTag(String name, short value) { + super(name); + this.value = value; + } + + @Override + public Short getValue() { + return value; + } + + @Override + public String toString() { + String name = getName(); + String append = ""; + if(name != null && !name.equals("")) { + append = "(\"" + this.getName() + "\")"; + } + return "TAG_Short" + append + ": " + value; + } + +} diff --git a/src/org/jnbt/StringTag.java b/src/org/jnbt/StringTag.java new file mode 100644 index 000000000..3bcb09265 --- /dev/null +++ b/src/org/jnbt/StringTag.java @@ -0,0 +1,73 @@ +package org.jnbt; + +/* + * JNBT License + * + * Copyright (c) 2010 Graham Edgecombe + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the JNBT team nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * The TAG_String tag. + * @author Graham Edgecombe + * + */ +public final class StringTag extends Tag { + + /** + * The value. + */ + private final String value; + + /** + * Creates the tag. + * @param name The name. + * @param value The value. + */ + public StringTag(String name, String value) { + super(name); + this.value = value; + } + + @Override + public String getValue() { + return value; + } + + @Override + public String toString() { + String name = getName(); + String append = ""; + if(name != null && !name.equals("")) { + append = "(\"" + this.getName() + "\")"; + } + return "TAG_String" + append + ": " + value; + } + +} diff --git a/src/org/jnbt/Tag.java b/src/org/jnbt/Tag.java new file mode 100644 index 000000000..384784697 --- /dev/null +++ b/src/org/jnbt/Tag.java @@ -0,0 +1,70 @@ +package org.jnbt; + +/* + * JNBT License + * + * Copyright (c) 2010 Graham Edgecombe + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the JNBT team nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Represents a single NBT tag. + * @author Graham Edgecombe + * + */ +public abstract class Tag { + + /** + * The name of this tag. + */ + private final String name; + + /** + * Creates the tag with the specified name. + * @param name The name. + */ + public Tag(String name) { + this.name = name; + } + + /** + * Gets the name of this tag. + * @return The name of this tag. + */ + public final String getName() { + return name; + } + + /** + * Gets the value of this tag. + * @return The value of this tag. + */ + public abstract Object getValue(); + +}