Switch to Gradle. Use git log --follow for history.

This converts the project into a multi-module Gradle build.

By default, Git does not show history past a rename, so use git log
--follow to see further history.
This commit is contained in:
sk89q
2014-11-14 11:27:39 -08:00
parent 44559cde68
commit 7192780251
714 changed files with 333 additions and 834 deletions

View File

@ -0,0 +1,78 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util;
public class Countable<T> implements Comparable<Countable<T>> {
private T id;
private int amount;
/**
* Construct the object.
*
* @param id the ID
* @param amount the count of
*/
public Countable(T id, int amount) {
this.id = id;
this.amount = amount;
}
public T getID() {
return id;
}
public void setID(T id) {
this.id = id;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
/**
* Decrement the amount.
*/
public void decrement() {
--this.amount;
}
/**
* Increment the amount.
*/
public void increment() {
++this.amount;
}
@Override
public int compareTo(Countable<T> other) {
if (amount > other.amount) {
return 1;
} else if (amount == other.amount) {
return 0;
} else {
return -1;
}
}
}

View File

@ -0,0 +1,159 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util;
import com.sk89q.worldedit.Vector;
import javax.annotation.Nullable;
/**
* A collection of cardinal, ordinal, and secondary-ordinal directions.
*/
public enum Direction {
NORTH(new Vector(0, 0, -1), Flag.CARDINAL),
EAST(new Vector(1, 0, 0), Flag.CARDINAL),
SOUTH(new Vector(0, 0, 1), Flag.CARDINAL),
WEST(new Vector(-1, 0, 0), Flag.CARDINAL),
UP(new Vector(0, 1, 0), Flag.UPRIGHT),
DOWN(new Vector(0, -1, 0), Flag.UPRIGHT),
NORTHEAST(new Vector(1, 0, -1), Flag.ORDINAL),
NORTHWEST(new Vector(-1, 0, -1), Flag.ORDINAL),
SOUTHEAST(new Vector(1, 0, 1), Flag.ORDINAL),
SOUTHWEST(new Vector(-1, 0, 1), Flag.ORDINAL),
WEST_NORTHWEST(new Vector(-Math.cos(Math.PI / 8), 0, Math.sin(Math.PI / 8)), Flag.SECONDARY_ORDINAL),
WEST_SOUTHWEST(new Vector(-Math.cos(Math.PI / 8), 0, Math.sin(Math.PI / 8)), Flag.SECONDARY_ORDINAL),
NORTH_NORTHWEST(new Vector(Math.sin(Math.PI / 8), 0, -Math.cos(Math.PI / 8)), Flag.SECONDARY_ORDINAL),
NORTH_NORTHEAST(new Vector(Math.sin(Math.PI / 8), 0, -Math.cos(Math.PI / 8)), Flag.SECONDARY_ORDINAL),
EAST_NORTHEAST(new Vector(Math.cos(Math.PI / 8), 0, Math.sin(Math.PI / 8)), Flag.SECONDARY_ORDINAL),
EAST_SOUTHEAST(new Vector(Math.cos(Math.PI / 8), 0, Math.sin(Math.PI / 8)), Flag.SECONDARY_ORDINAL),
SOUTH_SOUTHEAST(new Vector(Math.sin(Math.PI / 8), 0, Math.cos(Math.PI / 8)), Flag.SECONDARY_ORDINAL),
SOUTH_SOUTHWEST(new Vector(Math.sin(Math.PI / 8), 0, Math.cos(Math.PI / 8)), Flag.SECONDARY_ORDINAL);
private final Vector direction;
private final int flags;
private Direction(Vector vector, int flags) {
this.direction = vector.normalize();
this.flags = flags;
}
/**
* Return true if the direction is of a cardinal direction (north, west
* east, and south).
*
* <p>This evaluates as false for directions that have a non-zero
* Y-component.</p>
*
* @return true if cardinal
*/
public boolean isCardinal() {
return (flags & Flag.CARDINAL) > 0;
}
/**
* Return true if the direction is of an ordinal direction (northwest,
* southwest, southeast, northeaast).
*
* @return true if ordinal
*/
public boolean isOrdinal() {
return (flags & Flag.ORDINAL) > 0;
}
/**
* Return true if the direction is of a secondary ordinal direction
* (north-northwest, north-northeast, south-southwest, etc.).
*
* @return true if secondary ordinal
*/
public boolean isSecondaryOrdinal() {
return (flags & Flag.SECONDARY_ORDINAL) > 0;
}
/**
* Return whether Y component is non-zero.
*
* @return true if the Y component is non-zero
*/
public boolean isUpright() {
return (flags & Flag.UPRIGHT) > 0;
}
/**
* Get the vector.
*
* @return the vector
*/
public Vector toVector() {
return direction;
}
/**
* Find the closest direction to the given direction vector.
*
* @param vector the vector
* @param flags the only flags that are permitted (use bitwise math)
* @return the closest direction, or null if no direction can be returned
*/
@Nullable
public static Direction findClosest(Vector vector, int flags) {
if ((flags & Flag.UPRIGHT) == 0) {
vector = vector.setY(0);
}
vector = vector.normalize();
Direction closest = null;
double closestDot = -2;
for (Direction direction : values()) {
if ((~flags & direction.flags) > 0) {
continue;
}
double dot = direction.toVector().dot(vector);
if (dot >= closestDot) {
closest = direction;
closestDot = dot;
}
}
return closest;
}
/**
* Flags to use with {@link #findClosest(Vector, int)}.
*/
public static final class Flag {
public static int CARDINAL = 0x1;
public static int ORDINAL = 0x2;
public static int SECONDARY_ORDINAL = 0x4;
public static int UPRIGHT = 0x8;
public static int ALL = CARDINAL | ORDINAL | SECONDARY_ORDINAL | UPRIGHT;
private Flag() {
}
}
}

View File

@ -0,0 +1,54 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Helper methods for enums.
*/
public final class Enums {
private Enums() {
}
/**
* Search the given enum for a value that is equal to the one of the
* given values, searching in an ascending manner.
*
* @param enumType the enum type
* @param values the list of values
* @param <T> the type of enum
* @return the found value or null
*/
@Nullable
public static <T extends Enum<T>> T findByValue(Class<T> enumType, String... values) {
checkNotNull(enumType);
checkNotNull(values);
for (String val : values) {
try {
return Enum.valueOf(enumType, val);
} catch (IllegalArgumentException ignored) {}
}
return null;
}
}

View File

@ -0,0 +1,52 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util;
import com.sk89q.worldedit.entity.Entity;
import javax.annotation.Nullable;
/**
* Indicates that an object can provide various "facets," which are
* specific distinct interfaces that can represent a portion of the object.
*
* <p>For example, an instance of an {@link Entity} may have a facet
* for accessing its inventory (if it contains an inventory) or a facet
* for accessing its health (if it has health).</p>
*
* <p>Facets are referred to by their interface or abstract class and
* it is dependent on the implementation of the object specifying this
* interface to return the most applicable implementation. However, in
* many cases, such an implementation may not apply or it has not been
* implemented so a request for a facet may return {@code null}.</p>
*/
public interface Faceted {
/**
* Get the facet corresponding to the given class or interface.
*
* @param cls the class or interface
* @param <T> the type
* @return an implementation of the facet or {@code null} if one is unavailable
*/
@Nullable
<T> T getFacet(Class<? extends T> cls);
}

View File

@ -0,0 +1,96 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util;
import com.sk89q.util.StringUtil;
import javax.swing.*;
import javax.swing.filechooser.FileFilter;
import java.io.File;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public final class FileDialogUtil {
private FileDialogUtil() {
}
public static File showSaveDialog(String[] exts) {
JFileChooser dialog = new JFileChooser();
if (exts != null) {
dialog.setFileFilter(new ExtensionFilter(exts));
}
int returnVal = dialog.showSaveDialog(null);
if (returnVal == JFileChooser.APPROVE_OPTION) {
return dialog.getSelectedFile();
}
return null;
}
public static File showOpenDialog(String[] exts) {
JFileChooser dialog = new JFileChooser();
if (exts != null) {
dialog.setFileFilter(new ExtensionFilter(exts));
}
int returnVal = dialog.showOpenDialog(null);
if (returnVal == JFileChooser.APPROVE_OPTION) {
return dialog.getSelectedFile();
}
return null;
}
private static class ExtensionFilter extends FileFilter {
private Set<String> exts;
private String desc;
private ExtensionFilter(String[] exts) {
this.exts = new HashSet<String>(Arrays.asList(exts));
desc = StringUtil.joinString(exts, ",");
}
@Override
public boolean accept(File f) {
if (f.isDirectory()) {
return true;
}
String path = f.getPath();
int index = path.lastIndexOf('.');
if (index == -1 || index == path.length() - 1) {
return false;
} else {
return exts.contains(path.indexOf(index + 1));
}
}
@Override
public String getDescription() {
return desc;
}
}
}

View File

@ -0,0 +1,36 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util;
import java.util.UUID;
/**
* Represents an object that can be identified by a UUID.
*/
public interface Identifiable {
/**
* Get the UUID for this object.
*
* @return the UUID
*/
UUID getUniqueId();
}

View File

@ -0,0 +1,383 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.extent.Extent;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Represents a location in a world with has a direction.
*
* <p>Like {@code Vectors}, {@code Locations} are immutable and mutator methods
* will create a new copy.</p>
*
* <p>At the moment, but this may change in the future, {@link #hashCode()} and
* {@link #equals(Object)} are subject to minor differences caused by
* floating point errors.</p>
*/
public class Location {
private final Extent extent;
private final Vector position;
private final float pitch;
private final float yaw;
/**
* Create a new instance in the given extent at 0, 0, 0 with a
* direction vector of 0, 0, 0.
*
* @param extent the extent
*/
public Location(Extent extent) {
this(extent, new Vector(), new Vector());
}
/**
* Create a new instance in the given extent with the given coordinates
* with a direction vector of 0, 0, 0.
*
* @param extent the extent
* @param x the X coordinate
* @param y the Y coordinate
* @param z the Z coordinate
*/
public Location(Extent extent, double x, double y, double z) {
this(extent, new Vector(x, y, z), new Vector());
}
/**
* Create a new instance in the given extent with the given position
* vector and a direction vector of 0, 0, 0.
*
* @param extent the extent
* @param position the position vector
*/
public Location(Extent extent, Vector position) {
this(extent, position, new Vector());
}
/**
* Create a new instance in the given extent with the given coordinates
* and the given direction vector.
*
* @param extent the extent
* @param x the X coordinate
* @param y the Y coordinate
* @param z the Z coordinate
* @param direction the direction vector
*/
public Location(Extent extent, double x, double y, double z, Vector direction) {
this(extent, new Vector(x, y, z), direction);
}
/**
* Create a new instance in the given extent with the given coordinates
* and the given direction vector.
*
* @param extent the extent
* @param x the X coordinate
* @param y the Y coordinate
* @param z the Z coordinate
* @param yaw the yaw, in degrees
* @param pitch the pitch, in degrees
*/
public Location(Extent extent, double x, double y, double z, float yaw, float pitch) {
this(extent, new Vector(x, y, z), yaw, pitch);
}
/**
* Create a new instance in the given extent with the given position vector
* and the given direction vector.
*
* @param extent the extent
* @param position the position vector
* @param direction the direction vector
*/
public Location(Extent extent, Vector position, Vector direction) {
this(extent, position, direction.toYaw(), direction.toPitch());
}
/**
* Create a new instance in the given extent with the given position vector
* and the given direction vector.
*
* @param extent the extent
* @param position the position vector
* @param yaw the yaw, in degrees
* @param pitch the pitch, in degrees
*/
public Location(Extent extent, Vector position, float yaw, float pitch) {
checkNotNull(extent);
checkNotNull(position);
this.extent = extent;
this.position = position;
this.pitch = pitch;
this.yaw = yaw;
}
/**
* Get the extent.
*
* @return the extent
*/
public Extent getExtent() {
return extent;
}
/**
* Create a clone of this object with the given extent.
*
* @param extent the new extent
* @return the new instance
*/
public Location setExtent(Extent extent) {
return new Location(extent, position, getDirection());
}
/**
* Get the yaw in degrees.
*
* @return the yaw in degrees
*/
public float getYaw() {
return yaw;
}
/**
* Create a clone of this object with the given yaw.
*
* @param yaw the new yaw
* @return the new instance
*/
public Location setYaw(float yaw) {
return new Location(extent, position, yaw, pitch);
}
/**
* Get the pitch in degrees.
*
* @return the pitch in degrees
*/
public float getPitch() {
return pitch;
}
/**
* Create a clone of this object with the given pitch.
*
* @param pitch the new yaw
* @return the new instance
*/
public Location setPitch(float pitch) {
return new Location(extent, position, yaw, pitch);
}
/**
* Create a clone of this object with the given yaw and pitch.
*
* @param yaw the new yaw
* @param pitch the new pitch
* @return the new instance
*/
public Location setDirection(float yaw, float pitch) {
return new Location(extent, position, yaw, pitch);
}
/**
* Get the direction vector.
*
* @return the direction vector
*/
public Vector getDirection() {
double yaw = Math.toRadians(this.getYaw());
double pitch = Math.toRadians(this.getPitch());
double xz = Math.cos(pitch);
return new Vector(
-xz * Math.sin(yaw),
-Math.sin(pitch),
xz * Math.cos(yaw));
}
/**
* Create a clone of this object with the given direction.
*
* @param direction the new direction
* @return the new instance
*/
public Location setDirection(Vector direction) {
return new Location(extent, position, direction.toYaw(), direction.toPitch());
}
/**
* Get a {@link Vector} form of this location's position.
*
* @return a vector
*/
public Vector toVector() {
return position;
}
/**
* Get the X component of the position vector.
*
* @return the X component
*/
public double getX() {
return position.getX();
}
/**
* Get the rounded X component of the position vector.
*
* @return the rounded X component
*/
public int getBlockX() {
return position.getBlockX();
}
/**
* Return a copy of this object with the X component of the new object
* set to the given value.
*
* @param x the new value for the X component
* @return a new immutable instance
*/
public Location setX(double x) {
return new Location(extent, position.setX(x), yaw, pitch);
}
/**
* Return a copy of this object with the X component of the new object
* set to the given value.
*
* @param x the new value for the X component
* @return a new immutable instance
*/
public Location setX(int x) {
return new Location(extent, position.setX(x), yaw, pitch);
}
/**
* Get the Y component of the position vector.
*
* @return the Y component
*/
public double getY() {
return position.getY();
}
/**
* Get the rounded Y component of the position vector.
*
* @return the rounded Y component
*/
public int getBlockY() {
return position.getBlockY();
}
/**
* Return a copy of this object with the Y component of the new object
* set to the given value.
*
* @param y the new value for the Y component
* @return a new immutable instance
*/
public Location setY(double y) {
return new Location(extent, position.setY(y), yaw, pitch);
}
/**
* Return a copy of this object with the Y component of the new object
* set to the given value.
*
* @param y the new value for the Y component
* @return a new immutable instance
*/
public Location setY(int y) {
return new Location(extent, position.setY(y), yaw, pitch);
}
/**
* Get the Z component of the position vector.
*
* @return the Z component
*/
public double getZ() {
return position.getZ();
}
/**
* Get the rounded Z component of the position vector.
*
* @return the rounded Z component
*/
public int getBlockZ() {
return position.getBlockZ();
}
/**
* Return a copy of this object with the Z component of the new object
* set to the given value.
*
* @param z the new value for the Y component
* @return a new immutable instance
*/
public Location setZ(double z) {
return new Location(extent, position.setZ(z), yaw, pitch);
}
/**
* Return a copy of this object with the Z component of the new object
* set to the given value.
*
* @param z the new value for the Y component
* @return a new immutable instance
*/
public Location setZ(int z) {
return new Location(extent, position.setZ(z), yaw, pitch);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Location location = (Location) o;
if (Double.doubleToLongBits(pitch) != Double.doubleToLongBits(location.pitch)) return false;
if (Double.doubleToLongBits(yaw) != Double.doubleToLongBits(location.yaw)) return false;
if (!position.equals(location.position)) return false;
if (!extent.equals(location.extent)) return false;
return true;
}
@Override
public int hashCode() {
int result = extent.hashCode();
result = 31 * result + position.hashCode();
result = 31 * result + Float.floatToIntBits(this.pitch);
result = 31 * result + Float.floatToIntBits(this.yaw);
return result;
}
}

View File

@ -0,0 +1,252 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// $Id$
package com.sk89q.worldedit.util;
import com.sk89q.util.StringUtil;
import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.world.snapshot.SnapshotRepository;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Simple LocalConfiguration that loads settings using
* {@code java.util.Properties}.
*/
public class PropertiesConfiguration extends LocalConfiguration {
private static final Logger log = Logger.getLogger(PropertiesConfiguration.class.getCanonicalName());
protected Properties properties;
protected File path;
/**
* Construct the object. The configuration isn't loaded yet.
*
* @param path the path tot he configuration
*/
public PropertiesConfiguration(File path) {
this.path = path;
properties = new Properties();
}
@Override
public void load() {
InputStream stream = null;
try {
stream = new FileInputStream(path);
properties.load(stream);
} catch (FileNotFoundException ignored) {
} catch (IOException e) {
log.log(Level.WARNING, "Failed to read configuration", e);
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException ignored) {
}
}
}
profile = getBool("profile", profile);
disallowedBlocks = getIntSet("disallowed-blocks", defaultDisallowedBlocks);
defaultChangeLimit = getInt("default-max-changed-blocks", defaultChangeLimit);
maxChangeLimit = getInt("max-changed-blocks", maxChangeLimit);
defaultMaxPolygonalPoints = getInt("default-max-polygon-points", defaultMaxPolygonalPoints);
maxPolygonalPoints = getInt("max-polygon-points", maxPolygonalPoints);
defaultMaxPolyhedronPoints = getInt("default-max-polyhedron-points", defaultMaxPolyhedronPoints);
maxPolyhedronPoints = getInt("max-polyhedron-points", maxPolyhedronPoints);
shellSaveType = getString("shell-save-type", shellSaveType);
maxRadius = getInt("max-radius", maxRadius);
maxSuperPickaxeSize = getInt("max-super-pickaxe-size", maxSuperPickaxeSize);
maxBrushRadius = getInt("max-brush-radius", maxBrushRadius);
logCommands = getBool("log-commands", logCommands);
logFile = getString("log-file", logFile);
registerHelp = getBool("register-help", registerHelp);
wandItem = getInt("wand-item", wandItem);
superPickaxeDrop = getBool("super-pickaxe-drop-items", superPickaxeDrop);
superPickaxeManyDrop = getBool("super-pickaxe-many-drop-items", superPickaxeManyDrop);
noDoubleSlash = getBool("no-double-slash", noDoubleSlash);
useInventory = getBool("use-inventory", useInventory);
useInventoryOverride = getBool("use-inventory-override", useInventoryOverride);
useInventoryCreativeOverride = getBool("use-inventory-creative-override", useInventoryCreativeOverride);
navigationWand = getInt("nav-wand-item", navigationWand);
navigationWandMaxDistance = getInt("nav-wand-distance", navigationWandMaxDistance);
navigationUseGlass = getBool("nav-use-glass", navigationUseGlass);
scriptTimeout = getInt("scripting-timeout", scriptTimeout);
saveDir = getString("schematic-save-dir", saveDir);
scriptsDir = getString("craftscript-dir", scriptsDir);
butcherDefaultRadius = getInt("butcher-default-radius", butcherDefaultRadius);
butcherMaxRadius = getInt("butcher-max-radius", butcherMaxRadius);
allowSymlinks = getBool("allow-symbolic-links", allowSymlinks);
LocalSession.MAX_HISTORY_SIZE = Math.max(15, getInt("history-size", 15));
String snapshotsDir = getString("snapshots-dir", "");
if (!snapshotsDir.isEmpty()) {
snapshotRepo = new SnapshotRepository(snapshotsDir);
}
OutputStream output = null;
path.getParentFile().mkdirs();
try {
output = new FileOutputStream(path);
properties.store(output, "Don't put comments; they get removed");
} catch (FileNotFoundException e) {
log.log(Level.WARNING, "Failed to write configuration", e);
} catch (IOException e) {
log.log(Level.WARNING, "Failed to write configuration", e);
} finally {
if (output != null) {
try {
output.close();
} catch (IOException ignored) {
}
}
}
}
/**
* Get a string value.
*
* @param key the key
* @param def the default value
* @return the value
*/
protected String getString(String key, String def) {
if (def == null) {
def = "";
}
String val = properties.getProperty(key);
if (val == null) {
properties.setProperty(key, def);
return def;
} else {
return val;
}
}
/**
* Get a boolean value.
*
* @param key the key
* @param def the default value
* @return the value
*/
protected boolean getBool(String key, boolean def) {
String val = properties.getProperty(key);
if (val == null) {
properties.setProperty(key, def ? "true" : "false");
return def;
} else {
return val.equalsIgnoreCase("true")
|| val.equals("1");
}
}
/**
* Get an integer value.
*
* @param key the key
* @param def the default value
* @return the value
*/
protected int getInt(String key, int def) {
String val = properties.getProperty(key);
if (val == null) {
properties.setProperty(key, String.valueOf(def));
return def;
} else {
try {
return Integer.parseInt(val);
} catch (NumberFormatException e) {
properties.setProperty(key, String.valueOf(def));
return def;
}
}
}
/**
* Get a double value.
*
* @param key the key
* @param def the default value
* @return the value
*/
protected double getDouble(String key, double def) {
String val = properties.getProperty(key);
if (val == null) {
properties.setProperty(key, String.valueOf(def));
return def;
} else {
try {
return Double.parseDouble(val);
} catch (NumberFormatException e) {
properties.setProperty(key, String.valueOf(def));
return def;
}
}
}
/**
* Get a double value.
*
* @param key the key
* @param def the default value
* @return the value
*/
protected Set<Integer> getIntSet(String key, int[] def) {
String val = properties.getProperty(key);
if (val == null) {
properties.setProperty(key, StringUtil.joinString(def, ",", 0));
Set<Integer> set = new HashSet<Integer>();
for (int i : def) {
set.add(i);
}
return set;
} else {
Set<Integer> set = new HashSet<Integer>();
String[] parts = val.split(",");
for (String part : parts) {
try {
int v = Integer.parseInt(part.trim());
set.add(v);
} catch (NumberFormatException ignored) {
}
}
return set;
}
}
}

View File

@ -0,0 +1,213 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util;
import com.sk89q.worldedit.*;
import com.sk89q.worldedit.blocks.BlockID;
import com.sk89q.worldedit.blocks.BlockType;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.internal.LocalWorldAdapter;
/**
* This class uses an inefficient method to figure out what block a player
* is looking towards.
*
* <p>Originally written by toi. It was ported to WorldEdit and trimmed down by
* sk89q. Thanks to Raphfrk for optimization of toi's original class.</p>
*/
public class TargetBlock {
private LocalWorld world;
private int maxDistance;
private double checkDistance, curDistance;
private Vector targetPos = new Vector();
private Vector targetPosDouble = new Vector();
private Vector prevPos = new Vector();
private Vector offset = new Vector();
/**
* Constructor requiring a player, uses default values
*
* @param player player to work with
*/
public TargetBlock(LocalPlayer player) {
this.world = LocalWorldAdapter.adapt(player.getWorld());
this.setValues(player.getPosition(), player.getYaw(), player.getPitch(),
300, 1.65, 0.2);
}
/**
* Constructor requiring a player, max distance and a checking distance
*
* @param player LocalPlayer to work with
* @param maxDistance how far it checks for blocks
* @param checkDistance how often to check for blocks, the smaller the more precise
*/
public TargetBlock(LocalPlayer player, int maxDistance, double checkDistance) {
this((Player) player, maxDistance, checkDistance);
}
/**
* Constructor requiring a player, max distance and a checking distance
*
* @param player LocalPlayer to work with
* @param maxDistance how far it checks for blocks
* @param checkDistance how often to check for blocks, the smaller the more precise
*/
public TargetBlock(Player player, int maxDistance, double checkDistance) {
this.world = LocalWorldAdapter.adapt(player.getWorld());
this.setValues(player.getPosition(), player.getYaw(), player.getPitch(), maxDistance, 1.65, checkDistance);
}
/**
* Set the values, all constructors uses this function
*
* @param loc location of the view
* @param xRotation the X rotation
* @param yRotation the Y rotation
* @param maxDistance how far it checks for blocks
* @param viewHeight where the view is positioned in y-axis
* @param checkDistance how often to check for blocks, the smaller the more precise
*/
private void setValues(Vector loc, double xRotation, double yRotation,
int maxDistance, double viewHeight, double checkDistance) {
this.maxDistance = maxDistance;
this.checkDistance = checkDistance;
this.curDistance = 0;
xRotation = (xRotation + 90) % 360;
yRotation = yRotation * -1;
double h = (checkDistance * Math.cos(Math.toRadians(yRotation)));
offset = new Vector((h * Math.cos(Math.toRadians(xRotation))),
(checkDistance * Math.sin(Math.toRadians(yRotation))),
(h * Math.sin(Math.toRadians(xRotation))));
targetPosDouble = loc.add(0, viewHeight, 0);
targetPos = targetPosDouble.toBlockPoint();
prevPos = targetPos;
}
/**
* Returns any block at the sight. Returns null if out of range or if no
* viable target was found. Will try to return the last valid air block it finds.
*
* @return Block
*/
public BlockWorldVector getAnyTargetBlock() {
boolean searchForLastBlock = true;
BlockWorldVector lastBlock = null;
while (getNextBlock() != null) {
if (world.getBlockType(getCurrentBlock()) == BlockID.AIR) {
if (searchForLastBlock) {
lastBlock = getCurrentBlock();
if (lastBlock.getBlockY() <= 0 || lastBlock.getBlockY() >= world.getMaxY()) {
searchForLastBlock = false;
}
}
} else {
break;
}
}
BlockWorldVector currentBlock = getCurrentBlock();
return (currentBlock != null ? currentBlock : lastBlock);
}
/**
* Returns the block at the sight. Returns null if out of range or if no
* viable target was found
*
* @return Block
*/
public BlockWorldVector getTargetBlock() {
while (getNextBlock() != null && world.getBlockType(getCurrentBlock()) == 0) ;
return getCurrentBlock();
}
/**
* Returns the block at the sight. Returns null if out of range or if no
* viable target was found
*
* @return Block
*/
public BlockWorldVector getSolidTargetBlock() {
while (getNextBlock() != null && BlockType.canPassThrough(world.getBlock(getCurrentBlock()))) ;
return getCurrentBlock();
}
/**
* Get next block
*
* @return next block position
*/
public BlockWorldVector getNextBlock() {
prevPos = targetPos;
do {
curDistance += checkDistance;
targetPosDouble = offset.add(targetPosDouble.getX(),
targetPosDouble.getY(),
targetPosDouble.getZ());
targetPos = targetPosDouble.toBlockPoint();
} while (curDistance <= maxDistance
&& targetPos.getBlockX() == prevPos.getBlockX()
&& targetPos.getBlockY() == prevPos.getBlockY()
&& targetPos.getBlockZ() == prevPos.getBlockZ());
if (curDistance > maxDistance) {
return null;
}
return new BlockWorldVector(world, targetPos);
}
/**
* Returns the current block along the line of vision
*
* @return block position
*/
public BlockWorldVector getCurrentBlock() {
if (curDistance > maxDistance) {
return null;
} else {
return new BlockWorldVector(world, targetPos);
}
}
/**
* Returns the previous block in the aimed path
*
* @return block position
*/
public BlockWorldVector getPreviousBlock() {
return new BlockWorldVector(world, prevPos);
}
public WorldVectorFace getAnyTargetBlockFace() {
getAnyTargetBlock();
return WorldVectorFace.getWorldVectorFace(world, getCurrentBlock(), getPreviousBlock());
}
public WorldVectorFace getTargetBlockFace() {
getAnyTargetBlock();
return WorldVectorFace.getWorldVectorFace(world, getCurrentBlock(), getPreviousBlock());
}
}

View File

@ -0,0 +1,231 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.blocks.BlockID;
import javax.annotation.Nullable;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/**
* Tree generator.
*/
public class TreeGenerator {
public enum TreeType {
TREE("Regular tree", "tree", "regular"),
BIG_TREE("Big tree", "big", "bigtree"),
REDWOOD("Redwood", "redwood", "sequoia", "sequoioideae"),
TALL_REDWOOD("Tall redwood", "tallredwood", "tallsequoia", "tallsequoioideae"),
BIRCH("Birch", "birch", "white", "whitebark"),
PINE("Pine", "pine") {
@Override
public boolean generate(EditSession editSession, Vector pos) throws MaxChangedBlocksException {
makePineTree(editSession, pos);
return true;
}
},
RANDOM_REDWOOD("Random redwood", "randredwood", "randomredwood", "anyredwood") {
@Override
public boolean generate(EditSession editSession, Vector pos) throws MaxChangedBlocksException {
TreeType[] choices = new TreeType[] {
TreeType.REDWOOD, TreeType.TALL_REDWOOD
};
return choices[TreeGenerator.RANDOM.nextInt(choices.length)].generate(editSession, pos);
}
},
JUNGLE("Jungle", "jungle"),
SMALL_JUNGLE("Small jungle", "shortjungle", "smalljungle"),
SHORT_JUNGLE("Short jungle") {
@Override
public boolean generate(EditSession editSession, Vector pos) throws MaxChangedBlocksException {
return SMALL_JUNGLE.generate(editSession, pos);
}
},
JUNGLE_BUSH("Jungle bush", "junglebush", "jungleshrub"),
RED_MUSHROOM("Red Mushroom", "redmushroom", "redgiantmushroom"),
BROWN_MUSHROOM("Brown Mushroom", "brownmushroom", "browngiantmushroom"),
SWAMP("Swamp", "swamp", "swamptree"),
ACACIA("Acacia", "acacia"),
DARK_OAK("Dark Oak", "darkoak"),
MEGA_REDWOOD("Mega Redwood", "megaredwood"),
TALL_BIRCH("Tall Birch", "tallbirch"),
RANDOM("Random", "rand", "random") {
@Override
public boolean generate(EditSession editSession, Vector pos) throws MaxChangedBlocksException {
TreeType[] choices = new TreeType[] {
TreeType.TREE, TreeType.BIG_TREE, TreeType.BIRCH,
TreeType.REDWOOD, TreeType.TALL_REDWOOD, TreeType.PINE
};
return choices[TreeGenerator.RANDOM.nextInt(choices.length)].generate(editSession, pos);
}
};
/**
* Stores a map of the names for fast access.
*/
private static final Map<String, TreeType> lookup = new HashMap<String, TreeType>();
private final String name;
private final String[] lookupKeys;
static {
for (TreeType type : EnumSet.allOf(TreeType.class)) {
for (String key : type.lookupKeys) {
lookup.put(key, type);
}
}
}
TreeType(String name, String... lookupKeys) {
this.name = name;
this.lookupKeys = lookupKeys;
}
public boolean generate(EditSession editSession, Vector pos) throws MaxChangedBlocksException {
return editSession.getWorld().generateTree(this, editSession, pos);
}
/**
* Get user-friendly tree type name.
*
* @return a name
*/
public String getName() {
return name;
}
/**
* Return type from name. May return null.
*
* @param name name to search
* @return a tree type or null
*/
@Nullable
public static TreeType lookup(String name) {
return lookup.get(name.toLowerCase());
}
}
private static final Random RANDOM = new Random();
private TreeType type;
/**
* Construct the tree generator with a tree type.
*
* @param type the tree type
*/
@Deprecated
public TreeGenerator(TreeType type) {
this.type = type;
}
/**
* Generate a tree.
*
* @param editSession the edit session
* @param position the position to generate the tree at
* @return true if generation was successful
* @throws MaxChangedBlocksException
*/
public boolean generate(EditSession editSession, Vector position) throws MaxChangedBlocksException {
return type.generate(editSession, position);
}
/**
* Makes a terrible looking pine tree.
*
* @param basePosition the base position
*/
private static void makePineTree(EditSession editSession, Vector basePosition)
throws MaxChangedBlocksException {
int trunkHeight = (int) Math.floor(Math.random() * 2) + 3;
int height = (int) Math.floor(Math.random() * 5) + 8;
BaseBlock logBlock = new BaseBlock(BlockID.LOG);
BaseBlock leavesBlock = new BaseBlock(BlockID.LEAVES);
// Create trunk
for (int i = 0; i < trunkHeight; ++i) {
if (!editSession.setBlockIfAir(basePosition.add(0, i, 0), logBlock)) {
return;
}
}
// Move up
basePosition = basePosition.add(0, trunkHeight, 0);
// Create tree + leaves
for (int i = 0; i < height; ++i) {
editSession.setBlockIfAir(basePosition.add(0, i, 0), logBlock);
// Less leaves at these levels
double chance = ((i == 0 || i == height - 1) ? 0.6 : 1);
// Inner leaves
editSession.setChanceBlockIfAir(basePosition.add(-1, i, 0), leavesBlock, chance);
editSession.setChanceBlockIfAir(basePosition.add(1, i, 0), leavesBlock, chance);
editSession.setChanceBlockIfAir(basePosition.add(0, i, -1), leavesBlock, chance);
editSession.setChanceBlockIfAir(basePosition.add(0, i, 1), leavesBlock, chance);
editSession.setChanceBlockIfAir(basePosition.add(1, i, 1), leavesBlock, chance);
editSession.setChanceBlockIfAir(basePosition.add(-1, i, 1), leavesBlock, chance);
editSession.setChanceBlockIfAir(basePosition.add(1, i, -1), leavesBlock, chance);
editSession.setChanceBlockIfAir(basePosition.add(-1, i, -1), leavesBlock, chance);
if (!(i == 0 || i == height - 1)) {
for (int j = -2; j <= 2; ++j) {
editSession.setChanceBlockIfAir(basePosition.add(-2, i, j), leavesBlock, 0.6);
}
for (int j = -2; j <= 2; ++j) {
editSession.setChanceBlockIfAir(basePosition.add(2, i, j), leavesBlock, 0.6);
}
for (int j = -2; j <= 2; ++j) {
editSession.setChanceBlockIfAir(basePosition.add(j, i, -2), leavesBlock, 0.6);
}
for (int j = -2; j <= 2; ++j) {
editSession.setChanceBlockIfAir(basePosition.add(j, i, 2), leavesBlock, 0.6);
}
}
}
editSession.setBlockIfAir(basePosition.add(0, height, 0), leavesBlock);
}
/**
* Looks up a tree type. May return null if a tree type by that
* name is not found.
*
* @param type the tree type
* @return a tree type or null
*/
@Nullable
public static TreeType lookup(String type) {
return TreeType.lookup(type);
}
}

View File

@ -0,0 +1,118 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Returns the best choice given a weighting function and a target weight.
*
* <p>A function must be supplied that returns a numeric score for each
* choice. The function can return null to mean that the choice should
* not be considered.</p>
*
* @param <T> the type of choice
*/
public class WeightedChoice<T> {
private final Function<T, ? extends Number> function;
private double target;
private double best;
private T current;
/**
* Create a new instance.
*
* @param function a function that assigns a score for each choice
* @param target the target score that the best choice should be closest to
*/
public WeightedChoice(Function<T, ? extends Number> function, double target) {
checkNotNull(function);
this.function = function;
this.target = target;
}
/**
* Consider the given object.
*
* @param object the choice
*/
public void consider(T object) {
checkNotNull(object);
Number value = checkNotNull(function.apply(object));
if (value != null) {
double distance = Math.abs(target - value.doubleValue());
if (current == null || distance <= best) {
best = distance;
current = object;
}
}
}
/**
* Get the best choice.
*
* @return the best choice
*/
public Optional<Choice<T>> getChoice() {
if (current != null) {
return Optional.of(new Choice<T>(current, best));
} else {
return Optional.absent();
}
}
/**
* A tuple of choice and score.
*
* @param <T> the choice type
*/
public static class Choice<T> {
private final T object;
private final double value;
private Choice(T object, double value) {
this.object = object;
this.value = value;
}
/**
* Get the chosen value.
*
* @return the value
*/
public T getValue() {
return object;
}
/**
* Get the score.
*
* @return the score
*/
public double getScore() {
return value;
}
}
}

View File

@ -0,0 +1,127 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util;
import com.sk89q.util.yaml.YAMLProcessor;
import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.session.SessionManager;
import com.sk89q.worldedit.world.snapshot.SnapshotRepository;
import java.io.IOException;
import java.util.HashSet;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A less simple implementation of {@link LocalConfiguration}
* using YAML configuration files.
*/
public class YAMLConfiguration extends LocalConfiguration {
protected final YAMLProcessor config;
protected final Logger logger;
public YAMLConfiguration(YAMLProcessor config, Logger logger) {
this.config = config;
this.logger = logger;
}
@Override
public void load() {
try {
config.load();
} catch (IOException e) {
logger.log(Level.WARNING, "Error loading WorldEdit configuration", e);
}
profile = config.getBoolean("debug", profile);
wandItem = config.getInt("wand-item", wandItem);
defaultChangeLimit = Math.max(-1, config.getInt(
"limits.max-blocks-changed.default", defaultChangeLimit));
maxChangeLimit = Math.max(-1,
config.getInt("limits.max-blocks-changed.maximum", maxChangeLimit));
defaultMaxPolygonalPoints = Math.max(-1,
config.getInt("limits.max-polygonal-points.default", defaultMaxPolygonalPoints));
maxPolygonalPoints = Math.max(-1,
config.getInt("limits.max-polygonal-points.maximum", maxPolygonalPoints));
defaultMaxPolyhedronPoints = Math.max(-1, config.getInt("limits.max-polyhedron-points.default", defaultMaxPolyhedronPoints));
maxPolyhedronPoints = Math.max(-1, config.getInt("limits.max-polyhedron-points.maximum", maxPolyhedronPoints));
maxRadius = Math.max(-1, config.getInt("limits.max-radius", maxRadius));
maxBrushRadius = config.getInt("limits.max-brush-radius", maxBrushRadius);
maxSuperPickaxeSize = Math.max(1, config.getInt(
"limits.max-super-pickaxe-size", maxSuperPickaxeSize));
butcherDefaultRadius = Math.max(-1, config.getInt("limits.butcher-radius.default", butcherDefaultRadius));
butcherMaxRadius = Math.max(-1, config.getInt("limits.butcher-radius.maximum", butcherMaxRadius));
disallowedBlocks = new HashSet<Integer>(config.getIntList("limits.disallowed-blocks", null));
allowedDataCycleBlocks = new HashSet<Integer>(config.getIntList("limits.allowed-data-cycle-blocks", null));
registerHelp = config.getBoolean("register-help", true);
logCommands = config.getBoolean("logging.log-commands", logCommands);
logFile = config.getString("logging.file", logFile);
superPickaxeDrop = config.getBoolean("super-pickaxe.drop-items",
superPickaxeDrop);
superPickaxeManyDrop = config.getBoolean(
"super-pickaxe.many-drop-items", superPickaxeManyDrop);
noDoubleSlash = config.getBoolean("no-double-slash", noDoubleSlash);
useInventory = config.getBoolean("use-inventory.enable", useInventory);
useInventoryOverride = config.getBoolean("use-inventory.allow-override",
useInventoryOverride);
useInventoryCreativeOverride = config.getBoolean("use-inventory.creative-mode-overrides",
useInventoryCreativeOverride);
navigationWand = config.getInt("navigation-wand.item", navigationWand);
navigationWandMaxDistance = config.getInt("navigation-wand.max-distance", navigationWandMaxDistance);
navigationUseGlass = config.getBoolean("navigation.use-glass", navigationUseGlass);
scriptTimeout = config.getInt("scripting.timeout", scriptTimeout);
scriptsDir = config.getString("scripting.dir", scriptsDir);
saveDir = config.getString("saving.dir", saveDir);
allowSymlinks = config.getBoolean("files.allow-symbolic-links", false);
LocalSession.MAX_HISTORY_SIZE = Math.max(0, config.getInt("history.size", 15));
SessionManager.EXPIRATION_GRACE = config.getInt("history.expiration", 10) * 60 * 1000;
showHelpInfo = config.getBoolean("show-help-on-first-use", true);
String snapshotsDir = config.getString("snapshots.directory", "");
if (!snapshotsDir.isEmpty()) {
snapshotRepo = new SnapshotRepository(snapshotsDir);
}
String type = config.getString("shell-save-type", "").trim();
shellSaveType = type.equals("") ? null : type;
}
public void unload() {
}
}

View File

@ -0,0 +1,43 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.auth;
import com.sk89q.worldedit.WorldEditException;
/**
* Raised when authorization is not granted.
*/
public class AuthorizationException extends WorldEditException {
public AuthorizationException() {
}
public AuthorizationException(String message) {
super(message);
}
public AuthorizationException(String message, Throwable cause) {
super(message, cause);
}
public AuthorizationException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,38 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.auth;
import com.sk89q.minecraft.util.commands.CommandLocals;
/**
* Tests whether permission is granted.
*/
public interface Authorizer {
/**
* Tests whether permission is granted for the given context.
*
* @param locals locals
* @param permission the permission string
* @return true if permitted
*/
boolean testPermission(CommandLocals locals, String permission);
}

View File

@ -0,0 +1,35 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.auth;
import com.sk89q.minecraft.util.commands.CommandLocals;
/**
* An implementation of {@link Authorizer} that always returns false for
* tests of permissions.
*/
public class NullAuthorizer implements Authorizer {
@Override
public boolean testPermission(CommandLocals locals, String permission) {
return false;
}
}

View File

@ -0,0 +1,51 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.auth;
/**
* A subject has authorization attached to it.
*/
public interface Subject {
/**
* Get a list of groups that this subject is a part of.
*
* @return an array containing a group name per entry
*/
String[] getGroups();
/**
* Check whether this subject has been granted the given permission
* and throw an exception on error.
*
* @param permission the permission
* @throws AuthorizationException thrown if not permitted
*/
void checkPermission(String permission) throws AuthorizationException;
/**
* Return whether this subject has the given permission.
*
* @param permission the permission
* @return true if permission is granted
*/
boolean hasPermission(String permission);
}

View File

@ -0,0 +1,187 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.collection;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.NoSuchElementException;
/**
* Double array lists to work like a Map, but not really.
*
* <p>The usefulness of this class is highly questionable.</p>
*/
public class DoubleArrayList<A, B> implements Iterable<Map.Entry<A, B>> {
private List<A> listA = new ArrayList<A>();
private List<B> listB = new ArrayList<B>();
private boolean isReversed = false;
/**
* Construct the object.
*
* @param isReversed true if the list should be reversed
*/
public DoubleArrayList(boolean isReversed) {
this.isReversed = isReversed;
}
/**
* Add an item.
*
* @param a the first item
* @param b the second item
*/
public void put(A a, B b) {
listA.add(a);
listB.add(b);
}
/**
* Get size.
*
* @return count of objects
*/
public int size() {
return listA.size();
}
/**
* Clear the list.
*/
public void clear() {
listA.clear();
listB.clear();
}
/**
* Get an entry set.
*
* @return entry set
*/
public Iterator<Map.Entry<A, B>> iterator(boolean reversed) {
if (reversed) {
return new ReverseEntryIterator<Map.Entry<A, B>>(
listA.listIterator(listA.size()),
listB.listIterator(listB.size()));
} else {
return new ForwardEntryIterator<Map.Entry<A, B>>(
listA.iterator(),
listB.iterator());
}
}
@Override
public Iterator<Map.Entry<A, B>> iterator() {
return iterator(isReversed);
}
/**
* Entry iterator.
*/
public class ForwardEntryIterator<T extends Map.Entry<A, B>>
implements Iterator<Map.Entry<A, B>> {
private Iterator<A> keyIterator;
private Iterator<B> valueIterator;
public ForwardEntryIterator(Iterator<A> keyIterator, Iterator<B> valueIterator) {
this.keyIterator = keyIterator;
this.valueIterator = valueIterator;
}
@Override
public boolean hasNext() {
return keyIterator.hasNext();
}
@Override
public Map.Entry<A, B> next() throws NoSuchElementException {
return new Entry<A, B>(keyIterator.next(), valueIterator.next());
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
/**
* Entry iterator.
*/
public class ReverseEntryIterator<T extends Map.Entry<A, B>>
implements Iterator<Map.Entry<A, B>> {
private ListIterator<A> keyIterator;
private ListIterator<B> valueIterator;
public ReverseEntryIterator(ListIterator<A> keyIterator, ListIterator<B> valueIterator) {
this.keyIterator = keyIterator;
this.valueIterator = valueIterator;
}
@Override
public boolean hasNext() {
return keyIterator.hasPrevious();
}
@Override
public Map.Entry<A, B> next() throws NoSuchElementException {
return new Entry<A, B>(keyIterator.previous(), valueIterator.previous());
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
/**
* Class to masquerade as Map.Entry.
*/
public class Entry<C, D> implements Map.Entry<A, B> {
private A key;
private B value;
private Entry(A key, B value) {
this.key = key;
this.value = value;
}
@Override
public A getKey() {
return key;
}
@Override
public B getValue() {
return value;
}
@Override
public B setValue(B value) {
throw new UnsupportedOperationException();
}
}
}

View File

@ -0,0 +1,114 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* A fast iterator for lists that uses an internal index integer
* and caches the size of the list. The size of the list cannot change
* during iteration and {@link Iterator#remove()} is not supported.
*
* <p>The iterator in Java, at least in older Java versions, is very slow,
* causing a significant amount of time in operations in WorldEdit
* being spent on {@link Iterator#hasNext()}. In contrast, the iterator
* implemented by this class is very quick, as long as
* {@link List#get(int)} is fast.</p>
*
* @param <E> the element
*/
public class FastListIterator<E> implements Iterator<E> {
private final List<E> list;
private int index;
private final int size;
private final int increment;
/**
* Create a new fast iterator.
*
* @param list the list
* @param index the index to start from
* @param size the size of the list
* @param increment the increment amount (i.e. 1 or -1)
*/
private FastListIterator(List<E> list, int index, int size, int increment) {
checkNotNull(list);
checkArgument(size >= 0, "size >= 0 required");
checkArgument(index >= 0, "index >= 0 required");
this.list = list;
this.index = index;
this.size = size;
this.increment = increment;
}
@Override
public boolean hasNext() {
return index >= 0 && index < size;
}
@Override
public E next() {
if (hasNext()) {
E entry = list.get(index);
index += increment;
return entry;
} else {
throw new NoSuchElementException();
}
}
@Override
public void remove() {
throw new UnsupportedOperationException("Not supported");
}
/**
* Create a new forward iterator for the given list.
*
* @param list the list
* @param <E> the element
* @return an iterator
*/
public static <E> Iterator<E> forwardIterator(List<E> list) {
return new FastListIterator<E>(list, 0, list.size(), 1);
}
/**
* Create a new reverse iterator for the given list.
*
* @param list the list
* @param <E> the element
* @return an iterator
*/
public static <E> Iterator<E> reverseIterator(List<E> list) {
if (!list.isEmpty()) {
return new FastListIterator<E>(list, list.size() - 1, list.size(), -1);
} else {
return new FastListIterator<E>(list, 0, 0, -1);
}
}
}

View File

@ -0,0 +1,94 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.collection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
/**
* An {@link ArrayList} that takes {@link Map.Entry}-like tuples. This class
* exists for legacy reasons.
*
* @param <A> the first type in the tuple
* @param <B> the second type in the tuple
*/
public class TupleArrayList<A, B> extends ArrayList<Map.Entry<A, B>> {
/**
* Add an item to the list.
*
* @param a the 'key'
* @param b the 'value'
*/
public void put(A a, B b) {
add(new Tuple<A, B>(a, b));
}
/**
* Return an entry iterator that traverses in the reverse direction.
*
* @param reverse true to return the reverse iterator
* @return an entry iterator
*/
public Iterator<Map.Entry<A, B>> iterator(boolean reverse) {
return reverse ? reverseIterator() : iterator();
}
@Override
public Iterator<Map.Entry<A, B>> iterator() {
return FastListIterator.forwardIterator(this);
}
/**
* Return an entry iterator that traverses in the reverse direction.
*
* @return an entry iterator
*/
public Iterator<Map.Entry<A, B>> reverseIterator() {
return FastListIterator.reverseIterator(this);
}
private static class Tuple<A, B> implements Map.Entry<A, B> {
private A key;
private B value;
private Tuple(A key, B value) {
this.key = key;
this.value = value;
}
@Override
public A getKey() {
return key;
}
@Override
public B getValue() {
return value;
}
@Override
public B setValue(B value) {
throw new UnsupportedOperationException();
}
}
}

View File

@ -0,0 +1,59 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.minecraft.util.commands.CommandLocals;
/**
* A command that can be executed.
*/
public interface CommandCallable extends CommandCompleter {
/**
* Execute the correct command based on the input.
*
* <p>The implementing class must perform the necessary permission
* checks.</p>
*
* @param arguments the arguments
* @param locals the locals
* @param parentCommands a list of parent commands, with the first most entry being the top-level command
* @return the called command, or null if there was no command found
* @throws CommandException thrown on a command error
*/
boolean call(String arguments, CommandLocals locals, String[] parentCommands) throws CommandException;
/**
* Get an object describing this command.
*
* @return the command description
*/
Description getDescription();
/**
* Test whether this command can be executed with the given context.
*
* @param locals the locals
* @return true if execution is permitted
*/
boolean testPermission(CommandLocals locals);
}

View File

@ -0,0 +1,42 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.minecraft.util.commands.CommandLocals;
import java.util.List;
/**
* Provides a method that can provide tab completion for commands.
*/
public interface CommandCompleter {
/**
* Get a list of suggestions based on input.
*
* @param arguments the arguments entered up to this point
* @param locals the locals
* @return a list of suggestions
* @throws CommandException thrown if there was a parsing error
*/
List<String> getSuggestions(String arguments, CommandLocals locals) throws CommandException;
}

View File

@ -0,0 +1,55 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command;
/**
* Provides information about a mapping between a command and its aliases.
*/
public interface CommandMapping {
/**
* Get the primary alias.
*
* @return the primary alias
*/
String getPrimaryAlias();
/**
* Get a list of all aliases.
*
* @return aliases
*/
String[] getAllAliases();
/**
* Get the callable
*
* @return the callable
*/
CommandCallable getCallable();
/**
* Get the {@link Description} form the callable.
*
* @return the description
*/
Description getDescription();
}

View File

@ -0,0 +1,70 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command;
import java.util.List;
/**
* A description of a command.
*/
public interface Description {
/**
* Get the list of parameters for this command.
*
* @return a list of parameters
*/
List<Parameter> getParameters();
/**
* Get a short one-line description of this command.
*
* @return a description, or null if no description is available
*/
String getShortDescription();
/**
* Get a longer help text about this command.
*
* @return a help text, or null if no help is available
*/
String getHelp();
/**
* Get the usage string of this command.
*
* <p>A usage string may look like
* {@code [-w &lt;world&gt;] &lt;var1&gt; &lt;var2&gt;}.</p>
*
* @return a usage string
*/
String getUsage();
/**
* Get a list of permissions that the player may have to have permission.
*
* <p>Permission data may or may not be available. This is only useful as a
* potential hint.</p>
*
* @return the list of permissions
*/
List<String> getPermissions();
}

View File

@ -0,0 +1,85 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Set;
/**
* Executes a command based on user input.
*/
public interface Dispatcher extends CommandCallable {
/**
* Register a command with this dispatcher.
*
* @param callable the command executor
* @param alias a list of aliases, where the first alias is the primary name
*/
void registerCommand(CommandCallable callable, String... alias);
/**
* Get a list of commands. Each command, regardless of how many aliases
* it may have, will only appear once in the returned set.
*
* <p>The returned collection cannot be modified.</p>
*
* @return a list of registrations
*/
Set<CommandMapping> getCommands();
/**
* Get a list of primary aliases.
*
* <p>The returned collection cannot be modified.</p>
*
* @return a list of aliases
*/
Collection<String> getPrimaryAliases();
/**
* Get a list of all the command aliases, which includes the primary alias.
*
* <p>A command may have more than one alias assigned to it. The returned
* collection cannot be modified.</p>
*
* @return a list of aliases
*/
Collection<String> getAliases();
/**
* Get the {@link CommandCallable} associated with an alias. Returns
* null if no command is named by the given alias.
*
* @param alias the alias
* @return the command mapping (null if not found)
*/
@Nullable CommandMapping get(String alias);
/**
* Returns whether the dispatcher contains a registered command for the given alias.
*
* @param alias the alias
* @return true if a registered command exists
*/
boolean contains(String alias);
}

View File

@ -0,0 +1,107 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command;
import com.sk89q.minecraft.util.commands.CommandException;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Thrown when a command is not used properly.
*
* <p>When handling this exception, print the error message if it is not null.
* Print a one line help instruction unless {@link #isFullHelpSuggested()}
* is true, which, in that case, the full help of the command should be
* shown.</p>
*
* <p>If no error message is set and full help is not to be shown, then a generic
* "you used this command incorrectly" message should be shown.</p>
*/
public class InvalidUsageException extends CommandException {
private final CommandCallable command;
private final boolean fullHelpSuggested;
/**
* Create a new instance with no error message and with no suggestion
* that full and complete help for the command should be shown. This will
* result in a generic error message.
*
* @param command the command
*/
public InvalidUsageException(CommandCallable command) {
this(null, command);
}
/**
* Create a new instance with a message and with no suggestion
* that full and complete help for the command should be shown.
*
* @param message the message
* @param command the command
*/
public InvalidUsageException(@Nullable String message, CommandCallable command) {
this(message, command, false);
}
/**
* Create a new instance with a message.
*
* @param message the message
* @param command the command
* @param fullHelpSuggested true if the full help for the command should be shown
*/
public InvalidUsageException(@Nullable String message, CommandCallable command, boolean fullHelpSuggested) {
super(message);
checkNotNull(command);
this.command = command;
this.fullHelpSuggested = fullHelpSuggested;
}
/**
* Get the command.
*
* @return the command
*/
public CommandCallable getCommand() {
return command;
}
/**
* Get a simple usage string.
*
* @param prefix the command shebang (such as "/") -- may be blank
* @return a usage string
*/
public String getSimpleUsageString(String prefix) {
return getCommandUsed(prefix, command.getDescription().getUsage());
}
/**
* Return whether the full usage of the command should be shown.
*
* @return show full usage
*/
public boolean isFullHelpSuggested() {
return fullHelpSuggested;
}
}

View File

@ -0,0 +1,29 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command;
import com.sk89q.worldedit.util.command.parametric.ParameterException;
/**
* Thrown when there is a missing parameter.
*/
public class MissingParameterException extends ParameterException {
}

View File

@ -0,0 +1,38 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.minecraft.util.commands.CommandLocals;
import java.util.Collections;
import java.util.List;
/**
* Always returns an empty list of suggestions.
*/
public class NullCompleter implements CommandCompleter {
@Override
public List<String> getSuggestions(String arguments, CommandLocals locals) throws CommandException {
return Collections.emptyList();
}
}

View File

@ -0,0 +1,66 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command;
/**
* Describes a parameter.
*
* @see Description
*/
public interface Parameter {
/**
* The name of the parameter.
*
* @return the name
*/
String getName();
/**
* Get the flag associated with this parameter.
*
* @return the flag, or null if there is no flag associated
* @see #isValueFlag()
*/
Character getFlag();
/**
* Return whether the flag is a value flag.
*
* @return true if the flag is a value flag
* @see #getFlag()
*/
boolean isValueFlag();
/**
* Get whether this parameter is optional.
*
* @return true if the parameter does not have to be specified
*/
boolean isOptional();
/**
* Get the default value as a string to be parsed by the binding.
*
* @return a default value, or null if none is set
*/
public String[] getDefaultValue();
}

View File

@ -0,0 +1,59 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command;
import javax.annotation.Nullable;
import java.util.Comparator;
import java.util.regex.Pattern;
/**
* Compares the primary aliases of two {@link CommandMapping} using
* {@link String#compareTo(String)}.
*/
public final class PrimaryAliasComparator implements Comparator<CommandMapping> {
/**
* An instance of this class.
*/
public static final PrimaryAliasComparator INSTANCE = new PrimaryAliasComparator(null);
private final @Nullable Pattern removalPattern;
/**
* Create a new instance.
*
* @param removalPattern a regex to remove unwanted characters from the compared aliases
*/
public PrimaryAliasComparator(@Nullable Pattern removalPattern) {
this.removalPattern = removalPattern;
}
private String clean(String alias) {
if (removalPattern != null) {
return removalPattern.matcher(alias).replaceAll("");
}
return alias;
}
@Override
public int compare(CommandMapping o1, CommandMapping o2) {
return clean(o1.getPrimaryAlias()).compareTo(clean(o2.getPrimaryAlias()));
}
}

View File

@ -0,0 +1,72 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command;
import java.util.Arrays;
/**
* Tracks a command registration.
*/
public class SimpleCommandMapping implements CommandMapping {
private final String[] aliases;
private final CommandCallable callable;
/**
* Create a new instance.
*
* @param callable the command callable
* @param alias a list of all aliases, where the first one is the primary one
*/
public SimpleCommandMapping(CommandCallable callable, String... alias) {
super();
this.aliases = alias;
this.callable = callable;
}
@Override
public String getPrimaryAlias() {
return aliases[0];
}
@Override
public String[] getAllAliases() {
return aliases;
}
@Override
public CommandCallable getCallable() {
return callable;
}
@Override
public Description getDescription() {
return getCallable().getDescription();
}
@Override
public String toString() {
return "CommandMapping{" +
"aliases=" + Arrays.toString(aliases) +
", callable=" + callable +
'}';
}
}

View File

@ -0,0 +1,130 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A simple implementation of {@link Description} which has setters.
*/
public class SimpleDescription implements Description {
private List<Parameter> parameters = new ArrayList<Parameter>();
private List<String> permissions = new ArrayList<String>();
private String description;
private String help;
private String overrideUsage;
@Override
public List<Parameter> getParameters() {
return parameters;
}
/**
* Set the list of parameters.
*
* @param parameters the list of parameters
* @see #getParameters()
*/
public void setParameters(List<Parameter> parameters) {
this.parameters = Collections.unmodifiableList(parameters);
}
@Override
public String getShortDescription() {
return description;
}
/**
* Set the description of the command.
*
* @param description the description
* @see #getShortDescription()
*/
public void setDescription(String description) {
this.description = description;
}
@Override
public String getHelp() {
return help;
}
/**
* Set the help text of the command.
*
* @param help the help text
* @see #getHelp()
*/
public void setHelp(String help) {
this.help = help;
}
@Override
public List<String> getPermissions() {
return permissions;
}
/**
* Set the permissions of this command.
*
* @param permissions the permissions
*/
public void setPermissions(List<String> permissions) {
this.permissions = Collections.unmodifiableList(permissions);
}
/**
* Override the usage string returned with a given one.
*
* @param usage usage string, or null
*/
public void overrideUsage(String usage) {
this.overrideUsage = usage;
}
@Override
public String getUsage() {
if (overrideUsage != null) {
return overrideUsage;
}
StringBuilder builder = new StringBuilder();
boolean first = true;
for (Parameter parameter : parameters) {
if (!first) {
builder.append(" ");
}
builder.append(parameter);
first = false;
}
return builder.toString();
}
@Override
public String toString() {
return getUsage();
}
}

View File

@ -0,0 +1,191 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command;
import com.google.common.base.Joiner;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.minecraft.util.commands.CommandLocals;
import com.sk89q.minecraft.util.commands.CommandPermissionsException;
import com.sk89q.minecraft.util.commands.WrappedCommandException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* A simple implementation of {@link Dispatcher}.
*/
public class SimpleDispatcher implements Dispatcher {
private final Map<String, CommandMapping> commands = new HashMap<String, CommandMapping>();
private final SimpleDescription description = new SimpleDescription();
/**
* Create a new instance.
*/
public SimpleDispatcher() {
description.getParameters().add(new SimpleParameter("subcommand"));
SimpleParameter extraArgs = new SimpleParameter("...");
extraArgs.setOptional(true);
description.getParameters().add(extraArgs);
}
@Override
public void registerCommand(CommandCallable callable, String... alias) {
CommandMapping mapping = new SimpleCommandMapping(callable, alias);
// Check for replacements
for (String a : alias) {
String lower = a.toLowerCase();
if (commands.containsKey(lower)) {
throw new IllegalArgumentException(
"Replacing commands is currently undefined behavior");
}
}
for (String a : alias) {
String lower = a.toLowerCase();
commands.put(lower, mapping);
}
}
@Override
public Set<CommandMapping> getCommands() {
return Collections.unmodifiableSet(new HashSet<CommandMapping>(commands.values()));
}
@Override
public Set<String> getAliases() {
return Collections.unmodifiableSet(commands.keySet());
}
@Override
public Set<String> getPrimaryAliases() {
Set<String> aliases = new HashSet<String>();
for (CommandMapping mapping : getCommands()) {
aliases.add(mapping.getPrimaryAlias());
}
return Collections.unmodifiableSet(aliases);
}
@Override
public boolean contains(String alias) {
return commands.containsKey(alias.toLowerCase());
}
@Override
public CommandMapping get(String alias) {
return commands.get(alias.toLowerCase());
}
@Override
public boolean call(String arguments, CommandLocals locals, String[] parentCommands) throws CommandException {
// We have permission for this command if we have permissions for subcommands
if (!testPermission(locals)) {
throw new CommandPermissionsException();
}
String[] split = CommandContext.split(arguments);
Set<String> aliases = getPrimaryAliases();
if (aliases.isEmpty()) {
throw new InvalidUsageException("This command has no sub-commands.", this);
} else if (split.length > 0) {
String subCommand = split[0];
String subArguments = Joiner.on(" ").join(Arrays.copyOfRange(split, 1, split.length));
String[] subParents = Arrays.copyOf(parentCommands, parentCommands.length + 1);
subParents[parentCommands.length] = subCommand;
CommandMapping mapping = get(subCommand);
if (mapping != null) {
try {
mapping.getCallable().call(subArguments, locals, subParents);
} catch (CommandException e) {
e.prependStack(subCommand);
throw e;
} catch (Throwable t) {
throw new WrappedCommandException(t);
}
return true;
}
}
throw new InvalidUsageException("Please choose a sub-command.", this, true);
}
@Override
public List<String> getSuggestions(String arguments, CommandLocals locals) throws CommandException {
String[] split = CommandContext.split(arguments);
if (split.length <= 1) {
String prefix = split.length > 0 ? split[0] : "";
List<String> suggestions = new ArrayList<String>();
for (CommandMapping mapping : getCommands()) {
if (mapping.getCallable().testPermission(locals)) {
for (String alias : mapping.getAllAliases()) {
if (prefix.isEmpty() || alias.startsWith(arguments)) {
suggestions.add(mapping.getPrimaryAlias());
break;
}
}
}
}
return suggestions;
} else {
String subCommand = split[0];
CommandMapping mapping = get(subCommand);
String passedArguments = Joiner.on(" ").join(Arrays.copyOfRange(split, 1, split.length));
if (mapping != null) {
return mapping.getCallable().getSuggestions(passedArguments, locals);
} else {
return Collections.emptyList();
}
}
}
@Override
public SimpleDescription getDescription() {
return description;
}
@Override
public boolean testPermission(CommandLocals locals) {
for (CommandMapping mapping : getCommands()) {
if (mapping.getCallable().testPermission(locals)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,131 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command;
/**
* A simple implementation of {@link Parameter} that has setters.
*/
public class SimpleParameter implements Parameter {
private String name;
private Character flag;
private boolean isValue;
private boolean isOptional;
private String[] defaultValue;
/**
* Create a new parameter with no name defined yet.
*/
public SimpleParameter() {
}
/**
* Create a new parameter of the given name.
*
* @param name the name
*/
public SimpleParameter(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
/**
* Set the name of the parameter.
*
* @param name the parameter name
*/
public void setName(String name) {
this.name = name;
}
@Override
public Character getFlag() {
return flag;
}
@Override
public boolean isValueFlag() {
return flag != null && isValue;
}
/**
* Set the flag used by this parameter.
*
* @param flag the flag, or null if there is no flag
* @param isValue true if the flag is a value flag
*/
public void setFlag(Character flag, boolean isValue) {
this.flag = flag;
this.isValue = isValue;
}
@Override
public boolean isOptional() {
return isOptional || getFlag() != null;
}
/**
* Set whether this parameter is optional.
*
* @param isOptional true if this parameter is optional
*/
public void setOptional(boolean isOptional) {
this.isOptional = isOptional;
}
@Override
public String[] getDefaultValue() {
return defaultValue;
}
/**
* Set the default value.
*
* @param defaultValue a default value, or null if none
*/
public void setDefaultValue(String[] defaultValue) {
this.defaultValue = defaultValue;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (getFlag() != null) {
if (isValueFlag()) {
builder.append("[-")
.append(getFlag()).append(" <").append(getName()).append(">]");
} else {
builder.append("[-").append(getFlag()).append("]");
}
} else {
if (isOptional()) {
builder.append("[<").append(getName()).append(">]");
} else {
builder.append("<").append(getName()).append(">");
}
}
return builder.toString();
}
}

View File

@ -0,0 +1,40 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command;
import com.sk89q.worldedit.util.command.parametric.ParameterException;
/**
* Thrown when there are leftover parameters that were not consumed, particular in the
* case of the user providing too many parameters.
*/
public class UnconsumedParameterException extends ParameterException {
private String unconsumed;
public UnconsumedParameterException(String unconsumed) {
this.unconsumed = unconsumed;
}
public String getUnconsumed() {
return unconsumed;
}
}

View File

@ -0,0 +1,293 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.binding;
import com.sk89q.worldedit.internal.expression.Expression;
import com.sk89q.worldedit.internal.expression.ExpressionException;
import com.sk89q.worldedit.internal.expression.runtime.EvaluationException;
import com.sk89q.worldedit.util.command.parametric.ArgumentStack;
import com.sk89q.worldedit.util.command.parametric.BindingBehavior;
import com.sk89q.worldedit.util.command.parametric.BindingHelper;
import com.sk89q.worldedit.util.command.parametric.BindingMatch;
import com.sk89q.worldedit.util.command.parametric.ParameterException;
import javax.annotation.Nullable;
import java.lang.annotation.Annotation;
/**
* Handles basic Java types such as {@link String}s, {@link Byte}s, etc.
*
* <p>Handles both the object and primitive types.</p>
*/
public final class PrimitiveBindings extends BindingHelper {
/**
* Gets a type from a {@link ArgumentStack}.
*
* @param context the context
* @param text the text annotation
* @param modifiers a list of modifiers
* @return the requested type
* @throws ParameterException on error
*/
@BindingMatch(classifier = Text.class,
type = String.class,
behavior = BindingBehavior.CONSUMES,
consumedCount = -1,
provideModifiers = true)
public String getText(ArgumentStack context, Text text, Annotation[] modifiers)
throws ParameterException {
String v = context.remaining();
validate(v, modifiers);
return v;
}
/**
* Gets a type from a {@link ArgumentStack}.
*
* @param context the context
* @param modifiers a list of modifiers
* @return the requested type
* @throws ParameterException on error
*/
@BindingMatch(type = String.class,
behavior = BindingBehavior.CONSUMES,
consumedCount = 1,
provideModifiers = true)
public String getString(ArgumentStack context, Annotation[] modifiers)
throws ParameterException {
String v = context.next();
validate(v, modifiers);
return v;
}
/**
* Gets a type from a {@link ArgumentStack}.
*
* @param context the context
* @return the requested type
* @throws ParameterException on error
*/
@BindingMatch(type = { Boolean.class, boolean.class },
behavior = BindingBehavior.CONSUMES,
consumedCount = 1)
public Boolean getBoolean(ArgumentStack context) throws ParameterException {
return context.nextBoolean();
}
/**
* Try to parse numeric input as either a number or a mathematical expression.
*
* @param input input
* @return a number
* @throws ParameterException thrown on parse error
*/
private @Nullable Double parseNumericInput(@Nullable String input) throws ParameterException {
if (input == null) {
return null;
}
try {
return Double.parseDouble(input);
} catch (NumberFormatException e1) {
try {
Expression expression = Expression.compile(input);
return expression.evaluate();
} catch (EvaluationException e) {
throw new ParameterException(String.format(
"Expected '%s' to be a valid number (or a valid mathematical expression)", input));
} catch (ExpressionException e) {
throw new ParameterException(String.format(
"Expected '%s' to be a number or valid math expression (error: %s)", input, e.getMessage()));
}
}
}
/**
* Gets a type from a {@link ArgumentStack}.
*
* @param context the context
* @param modifiers a list of modifiers
* @return the requested type
* @throws ParameterException on error
*/
@BindingMatch(type = { Integer.class, int.class },
behavior = BindingBehavior.CONSUMES,
consumedCount = 1,
provideModifiers = true)
public Integer getInteger(ArgumentStack context, Annotation[] modifiers) throws ParameterException {
Double v = parseNumericInput(context.next());
if (v != null) {
int intValue = v.intValue();
validate(intValue, modifiers);
return intValue;
} else {
return null;
}
}
/**
* Gets a type from a {@link ArgumentStack}.
*
* @param context the context
* @param modifiers a list of modifiers
* @return the requested type
* @throws ParameterException on error
*/
@BindingMatch(type = { Short.class, short.class },
behavior = BindingBehavior.CONSUMES,
consumedCount = 1,
provideModifiers = true)
public Short getShort(ArgumentStack context, Annotation[] modifiers) throws ParameterException {
Integer v = getInteger(context, modifiers);
if (v != null) {
return v.shortValue();
}
return null;
}
/**
* Gets a type from a {@link ArgumentStack}.
*
* @param context the context
* @param modifiers a list of modifiers
* @return the requested type
* @throws ParameterException on error
*/
@BindingMatch(type = { Double.class, double.class },
behavior = BindingBehavior.CONSUMES,
consumedCount = 1,
provideModifiers = true)
public Double getDouble(ArgumentStack context, Annotation[] modifiers) throws ParameterException {
Double v = parseNumericInput(context.next());
if (v != null) {
validate(v, modifiers);
return v;
} else {
return null;
}
}
/**
* Gets a type from a {@link ArgumentStack}.
*
* @param context the context
* @param modifiers a list of modifiers
* @return the requested type
* @throws ParameterException on error
*/
@BindingMatch(type = { Float.class, float.class },
behavior = BindingBehavior.CONSUMES,
consumedCount = 1,
provideModifiers = true)
public Float getFloat(ArgumentStack context, Annotation[] modifiers) throws ParameterException {
Double v = getDouble(context, modifiers);
if (v != null) {
return v.floatValue();
}
return null;
}
/**
* Validate a number value using relevant modifiers.
*
* @param number the number
* @param modifiers the list of modifiers to scan
* @throws ParameterException on a validation error
*/
private static void validate(double number, Annotation[] modifiers)
throws ParameterException {
for (Annotation modifier : modifiers) {
if (modifier instanceof Range) {
Range range = (Range) modifier;
if (number < range.min()) {
throw new ParameterException(
String.format(
"A valid value is greater than or equal to %s " +
"(you entered %s)", range.min(), number));
} else if (number > range.max()) {
throw new ParameterException(
String.format(
"A valid value is less than or equal to %s " +
"(you entered %s)", range.max(), number));
}
}
}
}
/**
* Validate a number value using relevant modifiers.
*
* @param number the number
* @param modifiers the list of modifiers to scan
* @throws ParameterException on a validation error
*/
private static void validate(int number, Annotation[] modifiers)
throws ParameterException {
for (Annotation modifier : modifiers) {
if (modifier instanceof Range) {
Range range = (Range) modifier;
if (number < range.min()) {
throw new ParameterException(
String.format(
"A valid value is greater than or equal to %s " +
"(you entered %s)", range.min(), number));
} else if (number > range.max()) {
throw new ParameterException(
String.format(
"A valid value is less than or equal to %s " +
"(you entered %s)", range.max(), number));
}
}
}
}
/**
* Validate a string value using relevant modifiers.
*
* @param string the string
* @param modifiers the list of modifiers to scan
* @throws ParameterException on a validation error
*/
private static void validate(String string, Annotation[] modifiers)
throws ParameterException {
if (string == null) {
return;
}
for (Annotation modifier : modifiers) {
if (modifier instanceof Validate) {
Validate validate = (Validate) modifier;
if (!validate.regex().isEmpty()) {
if (!string.matches(validate.regex())) {
throw new ParameterException(
String.format(
"The given text doesn't match the right " +
"format (technically speaking, the 'format' is %s)",
validate.regex()));
}
}
}
}
}
}

View File

@ -0,0 +1,50 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.binding;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specifies a range of values for numbers.
*
* @see PrimitiveBindings a user of this annotation as a modifier
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Range {
/**
* The minimum value that the number can be at, inclusive.
*
* @return the minimum value
*/
double min() default Double.MIN_VALUE;
/**
* The maximum value that the number can be at, inclusive.
*
* @return the maximum value
*/
double max() default Double.MAX_VALUE;
}

View File

@ -0,0 +1,46 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.binding;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.worldedit.util.command.parametric.BindingBehavior;
import com.sk89q.worldedit.util.command.parametric.BindingHelper;
import com.sk89q.worldedit.util.command.parametric.BindingMatch;
import com.sk89q.worldedit.util.command.parametric.ArgumentStack;
/**
* Standard bindings that should be available to most configurations.
*/
public final class StandardBindings extends BindingHelper {
/**
* Gets a {@link CommandContext} from a {@link ArgumentStack}.
*
* @param context the context
* @return a selection
*/
@BindingMatch(type = CommandContext.class,
behavior = BindingBehavior.PROVIDES)
public CommandContext getCommandContext(ArgumentStack context) {
context.markConsumed(); // Consume entire stack
return context.getContext();
}
}

View File

@ -0,0 +1,44 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.binding;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates a command flag, such as {@code /command -f}.
*
* <p>If used on a boolean type, then the flag will be a non-value flag. If
* used on any other type, then the flag will be a value flag.</p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Switch {
/**
* The flag character.
*
* @return the flag character (A-Z a-z 0-9 is acceptable)
*/
char value();
}

View File

@ -0,0 +1,41 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.binding;
import com.sk89q.worldedit.util.command.parametric.ArgumentStack;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates a {@link String} parameter will call {@link ArgumentStack#remaining()} and
* therefore consume all remaining arguments.
*
* <p>This should only be used at the end of a list of parameters (of parameters that
* need to consume from the stack of arguments), otherwise following parameters will
* have no values left to consume.</p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Text {
}

View File

@ -0,0 +1,45 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.binding;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.regex.Pattern;
/**
* Used to validate a string.
*
* @see PrimitiveBindings where this validation is used
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Validate {
/**
* An optional regular expression that must match the string.
*
* @see Pattern regular expression class
* @return the pattern
*/
String regex() default "";
}

View File

@ -0,0 +1,84 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.fluent;
import com.sk89q.worldedit.util.command.Dispatcher;
import com.sk89q.worldedit.util.command.SimpleDispatcher;
import com.sk89q.worldedit.util.command.parametric.ParametricBuilder;
/**
* A fluent interface to creating a command graph.
*
* <p>A command graph may have multiple commands, and multiple sub-commands below that,
* and possibly below that.</p>
*/
public class CommandGraph {
private final DispatcherNode rootDispatcher;
private ParametricBuilder builder;
/**
* Create a new command graph.
*/
public CommandGraph() {
SimpleDispatcher dispatcher = new SimpleDispatcher();
rootDispatcher = new DispatcherNode(this, null, dispatcher);
}
/**
* Get the root dispatcher node.
*
* @return the root dispatcher node
*/
public DispatcherNode commands() {
return rootDispatcher;
}
/**
* Get the {@link ParametricBuilder}.
*
* @return the builder, or null.
*/
public ParametricBuilder getBuilder() {
return builder;
}
/**
* Set the {@link ParametricBuilder} used for calls to
* {@link DispatcherNode#registerMethods(Object)}.
*
* @param builder the builder, or null
* @return this object
*/
public CommandGraph builder(ParametricBuilder builder) {
this.builder = builder;
return this;
}
/**
* Get the root dispatcher.
*
* @return the root dispatcher
*/
public Dispatcher getDispatcher() {
return rootDispatcher.getDispatcher();
}
}

View File

@ -0,0 +1,138 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.fluent;
import com.sk89q.worldedit.util.command.CommandCallable;
import com.sk89q.worldedit.util.command.Dispatcher;
import com.sk89q.worldedit.util.command.SimpleDispatcher;
import com.sk89q.worldedit.util.command.parametric.ParametricBuilder;
/**
* A collection of commands.
*/
public class DispatcherNode {
private final CommandGraph graph;
private final DispatcherNode parent;
private final SimpleDispatcher dispatcher;
/**
* Create a new instance.
*
* @param graph the root fluent graph object
* @param parent the parent node, or null
* @param dispatcher the dispatcher for this node
*/
DispatcherNode(CommandGraph graph, DispatcherNode parent,
SimpleDispatcher dispatcher) {
this.graph = graph;
this.parent = parent;
this.dispatcher = dispatcher;
}
/**
* Set the description.
*
* <p>This can only be used on {@link DispatcherNode}s returned by
* {@link #group(String...)}.</p>
*
* @param description the description
* @return this object
*/
public DispatcherNode describeAs(String description) {
dispatcher.getDescription().setDescription(description);
return this;
}
/**
* Register a command with this dispatcher.
*
* @param callable the executor
* @param alias the list of aliases, where the first alias is the primary one
*/
public void register(CommandCallable callable, String... alias) {
dispatcher.registerCommand(callable, alias);
}
/**
* Build and register a command with this dispatcher using the
* {@link ParametricBuilder} assigned on the root {@link CommandGraph}.
*
* @param object the object provided to the {@link ParametricBuilder}
* @return this object
* @see ParametricBuilder#registerMethodsAsCommands(com.sk89q.worldedit.util.command.Dispatcher, Object)
*/
public DispatcherNode registerMethods(Object object) {
ParametricBuilder builder = graph.getBuilder();
if (builder == null) {
throw new RuntimeException("No ParametricBuilder set");
}
builder.registerMethodsAsCommands(getDispatcher(), object);
return this;
}
/**
* Create a new command that will contain sub-commands.
*
* <p>The object returned by this method can be used to add sub-commands. To
* return to this "parent" context, use {@link DispatcherNode#graph()}.</p>
*
* @param alias the list of aliases, where the first alias is the primary one
* @return an object to place sub-commands
*/
public DispatcherNode group(String... alias) {
SimpleDispatcher command = new SimpleDispatcher();
getDispatcher().registerCommand(command, alias);
return new DispatcherNode(graph, this, command);
}
/**
* Return the parent node.
*
* @return the parent node
* @throws RuntimeException if there is no parent node.
*/
public DispatcherNode parent() {
if (parent != null) {
return parent;
}
throw new RuntimeException("This node does not have a parent");
}
/**
* Get the root command graph.
*
* @return the root command graph
*/
public CommandGraph graph() {
return graph;
}
/**
* Get the underlying dispatcher of this object.
*
* @return the dispatcher
*/
public Dispatcher getDispatcher() {
return dispatcher;
}
}

View File

@ -0,0 +1,36 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
import com.sk89q.worldedit.util.command.SimpleDescription;
import java.lang.reflect.Method;
/**
* An abstract listener.
*/
public abstract class AbstractInvokeListener implements InvokeListener {
@Override
public void updateDescription(Object object, Method method,
ParameterData[] parameters, SimpleDescription description) {
}
}

View File

@ -0,0 +1,78 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
import com.sk89q.minecraft.util.commands.CommandContext;
public interface ArgumentStack {
/**
* Get the next string, which may come from the stack or a value flag.
*
* @return the value
* @throws ParameterException on a parameter error
*/
String next() throws ParameterException;
/**
* Get the next integer, which may come from the stack or a value flag.
*
* @return the value
* @throws ParameterException on a parameter error
*/
Integer nextInt() throws ParameterException;
/**
* Get the next double, which may come from the stack or a value flag.
*
* @return the value
* @throws ParameterException on a parameter error
*/
Double nextDouble() throws ParameterException;
/**
* Get the next boolean, which may come from the stack or a value flag.
*
* @return the value
* @throws ParameterException on a parameter error
*/
Boolean nextBoolean() throws ParameterException;
/**
* Get all remaining string values, which will consume the rest of the stack.
*
* @return the value
* @throws ParameterException on a parameter error
*/
String remaining() throws ParameterException;
/**
* Set as completely consumed.
*/
void markConsumed();
/**
* Get the underlying context.
*
* @return the context
*/
CommandContext getContext();
}

View File

@ -0,0 +1,93 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.worldedit.util.command.binding.PrimitiveBindings;
import com.sk89q.worldedit.util.command.binding.StandardBindings;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.util.List;
/**
* Used to parse user input for a command, based on available method types
* and annotations.
*
* <p>A binding can be used to handle several types at once. For a binding to be
* called, it must be registered with a {@link ParametricBuilder} with
* {@link ParametricBuilder#addBinding(Binding, java.lang.reflect.Type...)}.</p>
*
* @see PrimitiveBindings an example of primitive bindings
* @see StandardBindings standard bindings
*/
public interface Binding {
/**
* Get the types that this binding handles.
*
* @return the types
*/
Type[] getTypes();
/**
* Get how this binding consumes from a {@link ArgumentStack}.
*
* @param parameter information about the parameter
* @return the behavior
*/
BindingBehavior getBehavior(ParameterData parameter);
/**
* Get the number of arguments that this binding will consume, if this
* information is available.
*
* <p>This method must return -1 for binding behavior types that are not
* {@link BindingBehavior#CONSUMES}.</p>
*
* @param parameter information about the parameter
* @return the number of consumed arguments, or -1 if unknown or irrelevant
*/
int getConsumedCount(ParameterData parameter);
/**
* Attempt to consume values (if required) from the given {@link ArgumentStack}
* in order to instantiate an object for the given parameter.
*
* @param parameter information about the parameter
* @param scoped the arguments the user has input
* @param onlyConsume true to only consume arguments
* @return an object parsed for the given parameter
* @throws ParameterException thrown if the parameter could not be formulated
* @throws CommandException on a command exception
*/
Object bind(ParameterData parameter, ArgumentStack scoped, boolean onlyConsume)
throws ParameterException, CommandException, InvocationTargetException;
/**
* Get a list of suggestions for the given parameter and user arguments.
*
* @param parameter information about the parameter
* @param prefix what the user has typed so far (may be an empty string)
* @return a list of suggestions
*/
List<String> getSuggestions(ParameterData parameter, String prefix);
}

View File

@ -0,0 +1,52 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
import com.sk89q.minecraft.util.commands.CommandLocals;
import com.sk89q.worldedit.util.command.binding.Switch;
/**
* Determines the type of binding.
*/
public enum BindingBehavior {
/**
* Always consumes from a {@link ArgumentStack}.
*/
CONSUMES,
/**
* Sometimes consumes from a {@link ArgumentStack}.
*
* <p>Bindings that exhibit this behavior must be defined as a {@link Switch}
* by commands utilizing the given binding.</p>
*/
INDETERMINATE,
/**
* Never consumes from a {@link ArgumentStack}.
*
* <p>Bindings that exhibit this behavior generate objects from other sources,
* such as from a {@link CommandLocals}. These are "magic" bindings that inject
* variables.</p>
*/
PROVIDES
}

View File

@ -0,0 +1,224 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
import com.sk89q.minecraft.util.commands.CommandException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A binding helper that uses the {@link BindingMatch} annotation to make
* writing bindings extremely easy.
*
* <p>Methods must have the following and only the following parameters:</p>
*
* <ul>
* <li>A {@link ArgumentStack}</li>
* <li>A {@link Annotation} <strong>if there is a classifier set</strong></li>
* <li>A {@link Annotation}[]
* <strong>if there {@link BindingMatch#provideModifiers()} is true</strong></li>
* </ul>
*
* <p>Methods may throw any exception. Exceptions may be converted using a
* {@link ExceptionConverter} registered with the {@link ParametricBuilder}.</p>
*/
public class BindingHelper implements Binding {
private final List<BoundMethod> bindings;
private final Type[] types;
/**
* Create a new instance.
*/
public BindingHelper() {
List<BoundMethod> bindings = new ArrayList<BoundMethod>();
List<Type> types = new ArrayList<Type>();
for (Method method : this.getClass().getMethods()) {
BindingMatch info = method.getAnnotation(BindingMatch.class);
if (info != null) {
Class<? extends Annotation> classifier = null;
// Set classifier
if (!info.classifier().equals(Annotation.class)) {
classifier = info.classifier();
types.add(classifier);
}
for (Type t : info.type()) {
Type type = null;
// Set type
if (!t.equals(Class.class)) {
type = t;
if (classifier == null) {
types.add(type); // Only if there is no classifier set!
}
}
// Check to see if at least one is set
if (type == null && classifier == null) {
throw new RuntimeException(
"A @BindingMatch needs either a type or classifier set");
}
BoundMethod handler = new BoundMethod(info, type, classifier, method);
bindings.add(handler);
}
}
}
Collections.sort(bindings);
this.bindings = bindings;
Type[] typesArray = new Type[types.size()];
types.toArray(typesArray);
this.types = typesArray;
}
/**
* Match a {@link BindingMatch} according to the given parameter.
*
* @param parameter the parameter
* @return a binding
*/
private BoundMethod match(ParameterData parameter) {
for (BoundMethod binding : bindings) {
Annotation classifer = parameter.getClassifier();
Type type = parameter.getType();
if (binding.classifier != null) {
if (classifer != null && classifer.annotationType().equals(binding.classifier)) {
if (binding.type == null || binding.type.equals(type)) {
return binding;
}
}
} else if (binding.type.equals(type)) {
return binding;
}
}
throw new RuntimeException("Unknown type");
}
@Override
public Type[] getTypes() {
return types;
}
@Override
public int getConsumedCount(ParameterData parameter) {
return match(parameter).annotation.consumedCount();
}
@Override
public BindingBehavior getBehavior(ParameterData parameter) {
return match(parameter).annotation.behavior();
}
@Override
public Object bind(ParameterData parameter, ArgumentStack scoped,
boolean onlyConsume) throws ParameterException, CommandException, InvocationTargetException {
BoundMethod binding = match(parameter);
List<Object> args = new ArrayList<Object>();
args.add(scoped);
if (binding.classifier != null) {
args.add(parameter.getClassifier());
}
if (binding.annotation.provideModifiers()) {
args.add(parameter.getModifiers());
}
if (onlyConsume && binding.annotation.behavior() == BindingBehavior.PROVIDES) {
return null; // Nothing to consume, nothing to do
}
Object[] argsArray = new Object[args.size()];
args.toArray(argsArray);
try {
return binding.method.invoke(this, argsArray);
} catch (IllegalArgumentException e) {
throw new RuntimeException(
"Processing of classifier " + parameter.getClassifier() +
" and type " + parameter.getType() + " failed for method\n" +
binding.method + "\nbecause the parameters for that method are wrong", e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
if (e.getCause() instanceof ParameterException) {
throw (ParameterException) e.getCause();
} else if (e.getCause() instanceof CommandException) {
throw (CommandException) e.getCause();
}
throw e;
}
}
@Override
public List<String> getSuggestions(ParameterData parameter, String prefix) {
return new ArrayList<String>();
}
private static class BoundMethod implements Comparable<BoundMethod> {
private final BindingMatch annotation;
private final Type type;
private final Class<? extends Annotation> classifier;
private final Method method;
BoundMethod(BindingMatch annotation, Type type,
Class<? extends Annotation> classifier, Method method) {
this.annotation = annotation;
this.type = type;
this.classifier = classifier;
this.method = method;
}
@Override
public int compareTo(BoundMethod o) {
if (classifier != null && o.classifier == null) {
return -1;
} else if (classifier == null && o.classifier != null) {
return 1;
} else if (classifier != null && o.classifier != null) {
if (type != null && o.type == null) {
return -1;
} else if (type == null && o.type != null) {
return 1;
} else {
return 0;
}
} else {
return 0;
}
}
}
}

View File

@ -0,0 +1,71 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Denotes a match of a binding.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindingMatch {
/**
* The classifier.
*
* @return the classifier, or {@link Annotation} if not set
*/
Class<? extends Annotation> classifier() default Annotation.class;
/**
* The type.
*
* @return the type, or {@link Class} if not set
*/
Class<?>[] type() default Class.class;
/**
* The binding behavior.
*
* @return the behavior
*/
BindingBehavior behavior();
/**
* Get the number of arguments that this binding consumes.
*
* @return -1 if unknown or irrelevant
*/
int consumedCount() default -1;
/**
* Set whether an array of modifier annotations is provided in the list of
* arguments.
*
* @return true to provide modifiers
*/
boolean provideModifiers() default false;
}

View File

@ -0,0 +1,178 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.worldedit.util.command.MissingParameterException;
/**
* Makes an instance of a {@link CommandContext} into a stack of arguments
* that can be consumed.
*
* @see ParametricBuilder a user of this class
*/
public class ContextArgumentStack implements ArgumentStack {
private final CommandContext context;
private int index = 0;
private int markedIndex = 0;
/**
* Create a new instance using the given context.
*
* @param context the context
*/
public ContextArgumentStack(CommandContext context) {
this.context = context;
}
@Override
public String next() throws ParameterException {
try {
return context.getString(index++);
} catch (IndexOutOfBoundsException e) {
throw new MissingParameterException();
}
}
@Override
public Integer nextInt() throws ParameterException {
try {
return Integer.parseInt(next());
} catch (NumberFormatException e) {
throw new ParameterException(
"Expected a number, got '" + context.getString(index - 1) + "'");
}
}
@Override
public Double nextDouble() throws ParameterException {
try {
return Double.parseDouble(next());
} catch (NumberFormatException e) {
throw new ParameterException(
"Expected a number, got '" + context.getString(index - 1) + "'");
}
}
@Override
public Boolean nextBoolean() throws ParameterException {
try {
return next().equalsIgnoreCase("true");
} catch (IndexOutOfBoundsException e) {
throw new MissingParameterException();
}
}
@Override
public String remaining() throws ParameterException {
try {
String value = context.getJoinedStrings(index);
index = context.argsLength();
return value;
} catch (IndexOutOfBoundsException e) {
throw new MissingParameterException();
}
}
/**
* Get the unconsumed arguments left over, without touching the stack.
*
* @return the unconsumed arguments
*/
public String getUnconsumed() {
if (index >= context.argsLength()) {
return null;
}
return context.getJoinedStrings(index);
}
@Override
public void markConsumed() {
index = context.argsLength();
}
/**
* Return the current position.
*
* @return the position
*/
public int position() {
return index;
}
/**
* Mark the current position of the stack.
*
* <p>The marked position initially starts at 0.</p>
*/
public void mark() {
markedIndex = index;
}
/**
* Reset to the previously {@link #mark()}ed position of the stack, and return
* the arguments that were consumed between this point and that previous point.
*
* <p>The marked position initially starts at 0.</p>
*
* @return the consumed arguments
*/
public String reset() {
String value = context.getString(markedIndex, index);
index = markedIndex;
return value;
}
/**
* Return whether any arguments were consumed between the marked position
* and the current position.
*
* <p>The marked position initially starts at 0.</p>
*
* @return true if values were consumed.
*/
public boolean wasConsumed() {
return markedIndex != index;
}
/**
* Return the arguments that were consumed between this point and that marked point.
*
* <p>The marked position initially starts at 0.</p>
*
* @return the consumed arguments
*/
public String getConsumed() {
return context.getString(markedIndex, index);
}
/**
* Get the underlying context.
*
* @return the context
*/
@Override
public CommandContext getContext() {
return context;
}
}

View File

@ -0,0 +1,52 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.minecraft.util.commands.WrappedCommandException;
/**
* Used to convert a recognized {@link Throwable} into an appropriate
* {@link CommandException}.
*
* <p>Methods (when invoked by a {@link ParametricBuilder}-created command) may throw
* relevant exceptions that are not caught by the command manager, but translate
* into reasonable exceptions for an application. However, unknown exceptions are
* normally simply wrapped in a {@link WrappedCommandException} and bubbled up. Only
* normal {@link CommandException}s will be printed correctly, so a converter translates
* one of these unknown exceptions into an appropriate {@link CommandException}.</p>
*
* <p>This also allows the code calling the command to not need be aware of these
* application-specific exceptions, as they will all be converted to
* {@link CommandException}s that are handled normally.</p>
*/
public interface ExceptionConverter {
/**
* Attempt to convert the given throwable into a {@link CommandException}.
*
* <p>If the exception is not recognized, then nothing should be thrown.</p>
*
* @param t the throwable
* @throws CommandException a command exception
*/
void convert(Throwable t) throws CommandException;
}

View File

@ -0,0 +1,109 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.minecraft.util.commands.WrappedCommandException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* An implementation of an {@link ExceptionConverter} that automatically calls
* the correct method defined on this object.
*
* <p>Only public methods will be used. Methods will be called in order of decreasing
* levels of inheritance (between classes where one inherits the other). For two
* different inheritance branches, the order between them is undefined.</p>
*/
public abstract class ExceptionConverterHelper implements ExceptionConverter {
private final List<ExceptionHandler> handlers;
@SuppressWarnings("unchecked")
public ExceptionConverterHelper() {
List<ExceptionHandler> handlers = new ArrayList<ExceptionHandler>();
for (Method method : this.getClass().getMethods()) {
if (method.getAnnotation(ExceptionMatch.class) == null) {
continue;
}
Class<?>[] parameters = method.getParameterTypes();
if (parameters.length == 1) {
Class<?> cls = parameters[0];
if (Throwable.class.isAssignableFrom(cls)) {
handlers.add(new ExceptionHandler(
(Class<? extends Throwable>) cls, method));
}
}
}
Collections.sort(handlers);
this.handlers = handlers;
}
@Override
public void convert(Throwable t) throws CommandException {
Class<?> throwableClass = t.getClass();
for (ExceptionHandler handler : handlers) {
if (handler.cls.isAssignableFrom(throwableClass)) {
try {
handler.method.invoke(this, t);
} catch (InvocationTargetException e) {
if (e.getCause() instanceof CommandException) {
throw (CommandException) e.getCause();
}
throw new WrappedCommandException(e);
} catch (IllegalArgumentException e) {
throw new WrappedCommandException(e);
} catch (IllegalAccessException e) {
throw new WrappedCommandException(e);
}
}
}
}
private static class ExceptionHandler implements Comparable<ExceptionHandler> {
final Class<? extends Throwable> cls;
final Method method;
private ExceptionHandler(Class<? extends Throwable> cls, Method method) {
this.cls = cls;
this.method = method;
}
@Override
public int compareTo(ExceptionHandler o) {
if (cls.equals(o.cls)) {
return 0;
} else if (cls.isAssignableFrom(o.cls)) {
return 1;
} else {
return -1;
}
}
}
}

View File

@ -0,0 +1,34 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Denotes a match of an exception.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExceptionMatch {
}

View File

@ -0,0 +1,82 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandException;
import java.lang.reflect.Method;
/**
* Called before and after a command is invoked for commands executed by a command
* created using {@link ParametricBuilder}.
*
* <p>Invocation handlers are created by {@link InvokeListener}s. Multiple
* listeners and handlers can be registered, and all be run. However, if one handler
* throws an exception, future handlers will not execute and the command will
* not execute (if thrown in
* {@link #preInvoke(Object, Method, ParameterData[], Object[], CommandContext)}).</p>
*
* @see InvokeListener the factory
*/
public interface InvokeHandler {
/**
* Called before parameters are processed.
*
* @param object the object
* @param method the method
* @param parameters the list of parameters
* @param context the context
* @throws CommandException can be thrown for an error, which will stop invocation
* @throws ParameterException on parameter error
*/
void preProcess(Object object, Method method, ParameterData[] parameters,
CommandContext context) throws CommandException, ParameterException;
/**
* Called before the parameter is invoked.
*
* @param object the object
* @param method the method
* @param parameters the list of parameters
* @param args the arguments to be given to the method
* @param context the context
* @throws CommandException can be thrown for an error, which will stop invocation
* @throws ParameterException on parameter error
*/
void preInvoke(Object object, Method method, ParameterData[] parameters,
Object[] args, CommandContext context) throws CommandException, ParameterException;
/**
* Called after the parameter is invoked.
*
* @param object the object
* @param method the method
* @param parameters the list of parameters
* @param args the arguments to be given to the method
* @param context the context
* @throws CommandException can be thrown for an error
* @throws ParameterException on parameter error
*/
void postInvoke(Object object, Method method, ParameterData[] parameters,
Object[] args, CommandContext context) throws CommandException, ParameterException;
}

View File

@ -0,0 +1,58 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
import com.sk89q.minecraft.util.commands.CommandPermissions;
import com.sk89q.worldedit.util.command.CommandCallable;
import com.sk89q.worldedit.util.command.SimpleDescription;
import java.lang.reflect.Method;
/**
* Listens to events related to {@link ParametricBuilder}.
*/
public interface InvokeListener {
/**
* Create a new invocation handler.
*
* <p>An example use of an {@link InvokeHandler} would be to verify permissions
* added by the {@link CommandPermissions} annotation.</p>
*
* <p>For simple {@link InvokeHandler}, an object can implement both this
* interface and {@link InvokeHandler}.</p>
*
* @return a new invocation handler
*/
InvokeHandler createInvokeHandler();
/**
* During creation of a {@link CommandCallable} by a {@link ParametricBuilder},
* this will be called in case the description needs to be updated.
*
* @param object the object
* @param method the method
* @param parameters a list of parameters
* @param description the description to be updated
*/
void updateDescription(Object object, Method method, ParameterData[] parameters,
SimpleDescription description);
}

View File

@ -0,0 +1,97 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.worldedit.util.command.MissingParameterException;
import com.sk89q.worldedit.util.command.SimpleDescription;
import com.sk89q.worldedit.util.command.UnconsumedParameterException;
import java.lang.reflect.Method;
/**
* Handles legacy properties on {@link Command} such as {@link Command#min()} and
* {@link Command#max()}.
*/
public class LegacyCommandsHandler extends AbstractInvokeListener implements InvokeHandler {
@Override
public InvokeHandler createInvokeHandler() {
return this;
}
@Override
public void preProcess(Object object, Method method,
ParameterData[] parameters, CommandContext context)
throws CommandException, ParameterException {
}
@Override
public void preInvoke(Object object, Method method,
ParameterData[] parameters, Object[] args, CommandContext context)
throws ParameterException {
Command annotation = method.getAnnotation(Command.class);
if (annotation != null) {
if (context.argsLength() < annotation.min()) {
throw new MissingParameterException();
}
if (annotation.max() != -1 && context.argsLength() > annotation.max()) {
throw new UnconsumedParameterException(
context.getRemainingString(annotation.max()));
}
}
}
@Override
public void postInvoke(Object object, Method method,
ParameterData[] parameters, Object[] args, CommandContext context) {
}
@Override
public void updateDescription(Object object, Method method,
ParameterData[] parameters, SimpleDescription description) {
Command annotation = method.getAnnotation(Command.class);
// Handle the case for old commands where no usage is set and all of its
// parameters are provider bindings, so its usage information would
// be blank and would imply that there were no accepted parameters
if (annotation != null && annotation.usage().isEmpty()
&& (annotation.min() > 0 || annotation.max() > 0)) {
boolean hasUserParameters = false;
for (ParameterData parameter : parameters) {
if (parameter.getBinding().getBehavior(parameter) != BindingBehavior.PROVIDES) {
hasUserParameters = true;
break;
}
}
if (!hasUserParameters) {
description.overrideUsage("(unknown usage information)");
}
}
}
}

View File

@ -0,0 +1,41 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates an optional parameter.
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Optional {
/**
* The default value to use if no value is set.
*
* @return a string value, or an empty list
*/
String[] value() default {};
}

View File

@ -0,0 +1,194 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
import com.sk89q.worldedit.util.command.SimpleParameter;
import com.sk89q.worldedit.util.command.binding.PrimitiveBindings;
import com.sk89q.worldedit.util.command.binding.Range;
import com.sk89q.worldedit.util.command.binding.Text;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
/**
* Describes a parameter in detail.
*/
public class ParameterData extends SimpleParameter {
private Binding binding;
private Annotation classifier;
private Annotation[] modifiers;
private Type type;
/**
* Get the binding associated with this parameter.
*
* @return the binding
*/
public Binding getBinding() {
return binding;
}
/**
* Set the binding associated with this parameter.
*
* @param binding the binding
*/
void setBinding(Binding binding) {
this.binding = binding;
}
/**
* Set the main type of this parameter.
*
* <p>The type is normally that is used to determine which binding is used
* for a particular method's parameter.</p>
*
* @return the main type
* @see #getClassifier() which can override the type
*/
public Type getType() {
return type;
}
/**
* Set the main type of this parameter.
*
* @param type the main type
*/
void setType(Type type) {
this.type = type;
}
/**
* Get the classifier annotation.
*
* <p>Normally, the type determines what binding is called, but classifiers
* take precedence if one is found (and registered with
* {@link ParametricBuilder#addBinding(Binding, Type...)}).
* An example of a classifier annotation is {@link Text}.</p>
*
* @return the classifier annotation, null is possible
*/
public Annotation getClassifier() {
return classifier;
}
/**
* Set the classifier annotation.
*
* @param classifier the classifier annotation, null is possible
*/
void setClassifier(Annotation classifier) {
this.classifier = classifier;
}
/**
* Get a list of modifier annotations.
*
* <p>Modifier annotations are not considered in the process of choosing a binding
* for a method parameter, but they can be used to modify the behavior of a binding.
* An example of a modifier annotation is {@link Range}, which can restrict
* numeric values handled by {@link PrimitiveBindings} to be within a range. The list
* of annotations may contain a classifier and other unrelated annotations.</p>
*
* @return a list of annotations
*/
public Annotation[] getModifiers() {
return modifiers;
}
/**
* Set the list of modifiers.
*
* @param modifiers a list of annotations
*/
void setModifiers(Annotation[] modifiers) {
this.modifiers = modifiers;
}
/**
* Return the number of arguments this binding consumes.
*
* @return -1 if unknown or unavailable
*/
int getConsumedCount() {
return getBinding().getConsumedCount(this);
}
/**
* Get whether this parameter is entered by the user.
*
* @return true if this parameter is entered by the user.
*/
boolean isUserInput() {
return getBinding().getBehavior(this) != BindingBehavior.PROVIDES;
}
/**
* Get whether this parameter consumes non-flag arguments.
*
* @return true if this parameter consumes non-flag arguments
*/
boolean isNonFlagConsumer() {
return getBinding().getBehavior(this) != BindingBehavior.PROVIDES && !isValueFlag();
}
/**
* Validate this parameter and its binding.
*/
void validate(Method method, int parameterIndex) throws ParametricException {
// We can't have indeterminate consumers without @Switches otherwise
// it may screw up parameter processing for later bindings
BindingBehavior behavior = getBinding().getBehavior(this);
boolean indeterminate = (behavior == BindingBehavior.INDETERMINATE);
if (!isValueFlag() && indeterminate) {
throw new ParametricException(
"@Switch missing for indeterminate consumer\n\n" +
"Notably:\nFor the type " + type + ", the binding " +
getBinding().getClass().getCanonicalName() +
"\nmay or may not consume parameters (isIndeterminateConsumer(" + type + ") = true)" +
"\nand therefore @Switch(flag) is required for parameter #" + parameterIndex + " of \n" +
method.toGenericString());
}
// getConsumedCount() better return -1 if the BindingBehavior is not CONSUMES
if (behavior != BindingBehavior.CONSUMES && binding.getConsumedCount(this) != -1) {
throw new ParametricException(
"getConsumedCount() does not return -1 for binding " +
getBinding().getClass().getCanonicalName() +
"\neven though its behavior type is " + behavior.name() +
"\nfor parameter #" + parameterIndex + " of \n" +
method.toGenericString());
}
// getConsumedCount() should not return 0 if the BindingBehavior is not PROVIDES
if (behavior != BindingBehavior.PROVIDES && binding.getConsumedCount(this) == 0) {
throw new ParametricException(
"getConsumedCount() must not return 0 for binding " +
getBinding().getClass().getCanonicalName() +
"\nwhen its behavior type is " + behavior.name() + " and not PROVIDES " +
"\nfor parameter #" + parameterIndex + " of \n" +
method.toGenericString());
}
}
}

View File

@ -0,0 +1,43 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
/**
* Thrown if there is an error with a parameter.
*/
public class ParameterException extends Exception {
public ParameterException() {
super();
}
public ParameterException(String message) {
super(message);
}
public ParameterException(Throwable cause) {
super(cause);
}
public ParameterException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,253 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
import com.google.common.collect.ImmutableBiMap.Builder;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.minecraft.util.commands.CommandPermissions;
import com.sk89q.worldedit.util.auth.Authorizer;
import com.sk89q.worldedit.util.auth.NullAuthorizer;
import com.sk89q.worldedit.util.command.CommandCallable;
import com.sk89q.worldedit.util.command.CommandCompleter;
import com.sk89q.worldedit.util.command.Dispatcher;
import com.sk89q.worldedit.util.command.NullCompleter;
import com.sk89q.worldedit.util.command.binding.PrimitiveBindings;
import com.sk89q.worldedit.util.command.binding.StandardBindings;
import com.sk89q.worldedit.util.command.binding.Switch;
import com.thoughtworks.paranamer.CachingParanamer;
import com.thoughtworks.paranamer.Paranamer;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Creates commands using annotations placed on methods and individual parameters of
* such methods.
*
* @see Command defines a command
* @see Switch defines a flag
*/
public class ParametricBuilder {
private final Map<Type, Binding> bindings = new HashMap<Type, Binding>();
private final Paranamer paranamer = new CachingParanamer();
private final List<InvokeListener> invokeListeners = new ArrayList<InvokeListener>();
private final List<ExceptionConverter> exceptionConverters = new ArrayList<ExceptionConverter>();
private Authorizer authorizer = new NullAuthorizer();
private CommandCompleter defaultCompleter = new NullCompleter();
/**
* Create a new builder.
*
* <p>This method will install {@link PrimitiveBindings} and
* {@link StandardBindings} and default bindings.</p>
*/
public ParametricBuilder() {
addBinding(new PrimitiveBindings());
addBinding(new StandardBindings());
}
/**
* Add a binding for a given type or classifier (annotation).
*
* <p>Whenever a method parameter is encountered, a binding must be found for it
* so that it can be called later to consume the stack of arguments provided by
* the user and return an object that is later passed to
* {@link Method#invoke(Object, Object...)}.</p>
*
* <p>Normally, a {@link Type} is used to discern between different bindings, but
* if this is not specific enough, an annotation can be defined and used. This
* makes it a "classifier" and it will take precedence over the base type. For
* example, even if there is a binding that handles {@link String} parameters,
* a special {@code @MyArg} annotation can be assigned to a {@link String}
* parameter, which will cause the {@link Builder} to consult the {@link Binding}
* associated with {@code @MyArg} rather than with the binding for
* the {@link String} type.</p>
*
* @param binding the binding
* @param type a list of types (if specified) to override the binding's types
*/
public void addBinding(Binding binding, Type... type) {
if (type == null || type.length == 0) {
type = binding.getTypes();
}
for (Type t : type) {
bindings.put(t, binding);
}
}
/**
* Attach an invocation listener.
*
* <p>Invocation handlers are called in order that their listeners are
* registered with a {@link ParametricBuilder}. It is not guaranteed that
* a listener may be called, in the case of a {@link CommandException} being
* thrown at any time before the appropriate listener or handler is called.
* It is possible for a
* {@link InvokeHandler#preInvoke(Object, Method, ParameterData[], Object[], CommandContext)} to
* be called for a invocation handler, but not the associated
* {@link InvokeHandler#postInvoke(Object, Method, ParameterData[], Object[], CommandContext)}.</p>
*
* <p>An example of an invocation listener is one to handle
* {@link CommandPermissions}, by first checking to see if permission is available
* in a {@link InvokeHandler#preInvoke(Object, Method, ParameterData[], Object[], CommandContext)}
* call. If permission is not found, then an appropriate {@link CommandException}
* can be thrown to cease invocation.</p>
*
* @param listener the listener
* @see InvokeHandler the handler
*/
public void addInvokeListener(InvokeListener listener) {
invokeListeners.add(listener);
}
/**
* Attach an exception converter to this builder in order to wrap unknown
* {@link Throwable}s into known {@link CommandException}s.
*
* <p>Exception converters are called in order that they are registered.</p>
*
* @param converter the converter
* @see ExceptionConverter for an explanation
*/
public void addExceptionConverter(ExceptionConverter converter) {
exceptionConverters.add(converter);
}
/**
* Build a list of commands from methods specially annotated with {@link Command}
* (and other relevant annotations) and register them all with the given
* {@link Dispatcher}.
*
* @param dispatcher the dispatcher to register commands with
* @param object the object contain the methods
* @throws ParametricException thrown if the commands cannot be registered
*/
public void registerMethodsAsCommands(Dispatcher dispatcher, Object object) throws ParametricException {
for (Method method : object.getClass().getDeclaredMethods()) {
Command definition = method.getAnnotation(Command.class);
if (definition != null) {
CommandCallable callable = build(object, method, definition);
dispatcher.registerCommand(callable, definition.aliases());
}
}
}
/**
* Build a {@link CommandCallable} for the given method.
*
* @param object the object to be invoked on
* @param method the method to invoke
* @param definition the command definition annotation
* @return the command executor
* @throws ParametricException thrown on an error
*/
private CommandCallable build(Object object, Method method, Command definition)
throws ParametricException {
return new ParametricCallable(this, object, method, definition);
}
/**
* Get the object used to get method names on Java versions before 8 (assuming
* that Java 8 is given the ability to reliably reflect method names at runtime).
*
* @return the paranamer
*/
Paranamer getParanamer() {
return paranamer;
}
/**
* Get the map of bindings.
*
* @return the map of bindings
*/
Map<Type, Binding> getBindings() {
return bindings;
}
/**
* Get a list of invocation listeners.
*
* @return a list of invocation listeners
*/
List<InvokeListener> getInvokeListeners() {
return invokeListeners;
}
/**
* Get the list of exception converters.
*
* @return a list of exception converters
*/
List<ExceptionConverter> getExceptionConverters() {
return exceptionConverters;
}
/**
* Get the authorizer.
*
* @return the authorizer
*/
public Authorizer getAuthorizer() {
return authorizer;
}
/**
* Set the authorizer.
*
* @param authorizer the authorizer
*/
public void setAuthorizer(Authorizer authorizer) {
checkNotNull(authorizer);
this.authorizer = authorizer;
}
/**
* Get the default command suggestions provider that will be used if
* no suggestions are available.
*
* @return the default command completer
*/
public CommandCompleter getDefaultCompleter() {
return defaultCompleter;
}
/**
* Set the default command suggestions provider that will be used if
* no suggestions are available.
*
* @param defaultCompleter the default command completer
*/
public void setDefaultCompleter(CommandCompleter defaultCompleter) {
checkNotNull(defaultCompleter);
this.defaultCompleter = defaultCompleter;
}
}

View File

@ -0,0 +1,483 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
import com.google.common.primitives.Chars;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.minecraft.util.commands.CommandLocals;
import com.sk89q.minecraft.util.commands.CommandPermissions;
import com.sk89q.minecraft.util.commands.CommandPermissionsException;
import com.sk89q.minecraft.util.commands.WrappedCommandException;
import com.sk89q.worldedit.util.command.CommandCallable;
import com.sk89q.worldedit.util.command.InvalidUsageException;
import com.sk89q.worldedit.util.command.MissingParameterException;
import com.sk89q.worldedit.util.command.Parameter;
import com.sk89q.worldedit.util.command.SimpleDescription;
import com.sk89q.worldedit.util.command.UnconsumedParameterException;
import com.sk89q.worldedit.util.command.binding.Switch;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* The implementation of a {@link CommandCallable} for the {@link ParametricBuilder}.
*/
class ParametricCallable implements CommandCallable {
private final ParametricBuilder builder;
private final Object object;
private final Method method;
private final ParameterData[] parameters;
private final Set<Character> valueFlags = new HashSet<Character>();
private final boolean anyFlags;
private final Set<Character> legacyFlags = new HashSet<Character>();
private final SimpleDescription description = new SimpleDescription();
private final CommandPermissions commandPermissions;
/**
* Create a new instance.
*
* @param builder the parametric builder
* @param object the object to invoke on
* @param method the method to invoke
* @param definition the command definition annotation
* @throws ParametricException thrown on an error
*/
ParametricCallable(ParametricBuilder builder, Object object, Method method, Command definition) throws ParametricException {
this.builder = builder;
this.object = object;
this.method = method;
Annotation[][] annotations = method.getParameterAnnotations();
String[] names = builder.getParanamer().lookupParameterNames(method, false);
Type[] types = method.getGenericParameterTypes();
parameters = new ParameterData[types.length];
List<Parameter> userParameters = new ArrayList<Parameter>();
// This helps keep tracks of @Nullables that appear in the middle of a list
// of parameters
int numOptional = 0;
// Set permission hint
CommandPermissions permHint = method.getAnnotation(CommandPermissions.class);
if (permHint != null) {
description.setPermissions(Arrays.asList(permHint.value()));
}
// Go through each parameter
for (int i = 0; i < types.length; i++) {
Type type = types[i];
ParameterData parameter = new ParameterData();
parameter.setType(type);
parameter.setModifiers(annotations[i]);
// Search for annotations
for (Annotation annotation : annotations[i]) {
if (annotation instanceof Switch) {
parameter.setFlag(((Switch) annotation).value(), type != boolean.class);
} else if (annotation instanceof Optional) {
parameter.setOptional(true);
String[] value = ((Optional) annotation).value();
if (value.length > 0) {
parameter.setDefaultValue(value);
}
// Special annotation bindings
} else if (parameter.getBinding() == null) {
parameter.setBinding(builder.getBindings().get(annotation.annotationType()));
parameter.setClassifier(annotation);
}
}
parameter.setName(names.length > 0 ? names[i] : generateName(type, parameter.getClassifier(), i));
// Track all value flags
if (parameter.isValueFlag()) {
valueFlags.add(parameter.getFlag());
}
// No special @annotation binding... let's check for the type
if (parameter.getBinding() == null) {
parameter.setBinding(builder.getBindings().get(type));
// Don't know how to parse for this type of value
if (parameter.getBinding() == null) {
throw new ParametricException("Don't know how to handle the parameter type '" + type + "' in\n" + method.toGenericString());
}
}
// Do some validation of this parameter
parameter.validate(method, i + 1);
// Keep track of optional parameters
if (parameter.isOptional() && parameter.getFlag() == null) {
numOptional++;
} else {
if (numOptional > 0 && parameter.isNonFlagConsumer()) {
if (parameter.getConsumedCount() < 0) {
throw new ParametricException(
"Found an parameter using the binding " +
parameter.getBinding().getClass().getCanonicalName() +
"\nthat does not know how many arguments it consumes, but " +
"it follows an optional parameter\nMethod: " +
method.toGenericString());
}
}
}
parameters[i] = parameter;
// Make a list of "real" parameters
if (parameter.isUserInput()) {
userParameters.add(parameter);
}
}
// Gather legacy flags
anyFlags = definition.anyFlags();
legacyFlags.addAll(Chars.asList(definition.flags().toCharArray()));
// Finish description
description.setDescription(!definition.desc().isEmpty() ? definition.desc() : null);
description.setHelp(!definition.help().isEmpty() ? definition.help() : null);
description.overrideUsage(!definition.usage().isEmpty() ? definition.usage() : null);
for (InvokeListener listener : builder.getInvokeListeners()) {
listener.updateDescription(object, method, parameters, description);
}
// Set parameters
description.setParameters(userParameters);
// Get permissions annotation
commandPermissions = method.getAnnotation(CommandPermissions.class);
}
@Override
public boolean call(String stringArguments, CommandLocals locals, String[] parentCommands) throws CommandException {
// Test permission
if (!testPermission(locals)) {
throw new CommandPermissionsException();
}
String calledCommand = parentCommands.length > 0 ? parentCommands[parentCommands.length - 1] : "_";
String[] split = CommandContext.split(calledCommand + " " + stringArguments);
CommandContext context = new CommandContext(split, getValueFlags(), false, locals);
// Provide help if -? is specified
if (context.hasFlag('?')) {
throw new InvalidUsageException(null, this, true);
}
Object[] args = new Object[parameters.length];
ContextArgumentStack arguments = new ContextArgumentStack(context);
ParameterData parameter = null;
try {
// preProcess handlers
List<InvokeHandler> handlers = new ArrayList<InvokeHandler>();
for (InvokeListener listener : builder.getInvokeListeners()) {
InvokeHandler handler = listener.createInvokeHandler();
handlers.add(handler);
handler.preProcess(object, method, parameters, context);
}
// Collect parameters
for (int i = 0; i < parameters.length; i++) {
parameter = parameters[i];
if (mayConsumeArguments(i, arguments)) {
// Parse the user input into a method argument
ArgumentStack usedArguments = getScopedContext(parameter, arguments);
try {
args[i] = parameter.getBinding().bind(parameter, usedArguments, false);
} catch (MissingParameterException e) {
// Not optional? Then we can't execute this command
if (!parameter.isOptional()) {
throw e;
}
args[i] = getDefaultValue(i, arguments);
}
} else {
args[i] = getDefaultValue(i, arguments);
}
}
// Check for unused arguments
checkUnconsumed(arguments);
// preInvoke handlers
for (InvokeHandler handler : handlers) {
handler.preInvoke(object, method, parameters, args, context);
}
// Execute!
method.invoke(object, args);
// postInvoke handlers
for (InvokeHandler handler : handlers) {
handler.postInvoke(handler, method, parameters, args, context);
}
} catch (MissingParameterException e) {
throw new InvalidUsageException("Too few parameters!", this);
} catch (UnconsumedParameterException e) {
throw new InvalidUsageException("Too many parameters! Unused parameters: " + e.getUnconsumed(), this);
} catch (ParameterException e) {
assert parameter != null;
String name = parameter.getName();
throw new InvalidUsageException("For parameter '" + name + "': " + e.getMessage(), this);
} catch (InvocationTargetException e) {
for (ExceptionConverter converter : builder.getExceptionConverters()) {
converter.convert(e.getCause());
}
throw new WrappedCommandException(e);
} catch (IllegalArgumentException e) {
throw new WrappedCommandException(e);
} catch (CommandException e) {
throw e;
} catch (Throwable e) {
throw new WrappedCommandException(e);
}
return true;
}
@Override
public List<String> getSuggestions(String arguments, CommandLocals locals) throws CommandException {
return builder.getDefaultCompleter().getSuggestions(arguments, locals);
}
/**
* Get a list of value flags used by this command.
*
* @return a list of value flags
*/
public Set<Character> getValueFlags() {
return valueFlags;
}
@Override
public SimpleDescription getDescription() {
return description;
}
@Override
public boolean testPermission(CommandLocals locals) {
if (commandPermissions != null) {
for (String perm : commandPermissions.value()) {
if (builder.getAuthorizer().testPermission(locals, perm)) {
return true;
}
}
return false;
} else {
return true;
}
}
/**
* Get the right {@link ArgumentStack}.
*
* @param parameter the parameter
* @param existing the existing scoped context
* @return the context to use
*/
private static ArgumentStack getScopedContext(Parameter parameter, ArgumentStack existing) {
if (parameter.getFlag() != null) {
CommandContext context = existing.getContext();
if (parameter.isValueFlag()) {
return new StringArgumentStack(context, context.getFlag(parameter.getFlag()), false);
} else {
String v = context.hasFlag(parameter.getFlag()) ? "true" : "false";
return new StringArgumentStack(context, v, true);
}
}
return existing;
}
/**
* Get whether a parameter is allowed to consume arguments.
*
* @param i the index of the parameter
* @param scoped the scoped context
* @return true if arguments may be consumed
*/
private boolean mayConsumeArguments(int i, ContextArgumentStack scoped) {
CommandContext context = scoped.getContext();
ParameterData parameter = parameters[i];
// Flag parameters: Always consume
// Required non-flag parameters: Always consume
// Optional non-flag parameters:
// - Before required parameters: Consume if there are 'left over' args
// - At the end: Always consumes
if (parameter.isOptional()) {
if (parameter.getFlag() != null) {
return !parameter.isValueFlag() || context.hasFlag(parameter.getFlag());
} else {
int numberFree = context.argsLength() - scoped.position();
for (int j = i; j < parameters.length; j++) {
if (parameters[j].isNonFlagConsumer() && !parameters[j].isOptional()) {
// We already checked if the consumed count was > -1
// when we created this object
numberFree -= parameters[j].getConsumedCount();
}
}
// Skip this optional parameter
if (numberFree < 1) {
return false;
}
}
}
return true;
}
/**
* Get the default value for a parameter.
*
* @param i the index of the parameter
* @param scoped the scoped context
* @return a value
* @throws ParameterException on an error
* @throws CommandException on an error
*/
private Object getDefaultValue(int i, ContextArgumentStack scoped) throws ParameterException, CommandException, InvocationTargetException {
CommandContext context = scoped.getContext();
ParameterData parameter = parameters[i];
String[] defaultValue = parameter.getDefaultValue();
if (defaultValue != null) {
try {
return parameter.getBinding().bind(parameter, new StringArgumentStack(context, defaultValue, false), false);
} catch (MissingParameterException e) {
throw new ParametricException(
"The default value of the parameter using the binding " +
parameter.getBinding().getClass() + " in the method\n" +
method.toGenericString() + "\nis invalid");
}
}
return null;
}
/**
* Check to see if all arguments, including flag arguments, were consumed.
*
* @param scoped the argument scope
* @throws UnconsumedParameterException thrown if parameters were not consumed
*/
private void checkUnconsumed(ContextArgumentStack scoped) throws UnconsumedParameterException {
CommandContext context = scoped.getContext();
String unconsumed;
String unconsumedFlags = getUnusedFlags(context);
if ((unconsumed = scoped.getUnconsumed()) != null) {
throw new UnconsumedParameterException(unconsumed + " " + unconsumedFlags);
}
if (unconsumedFlags != null) {
throw new UnconsumedParameterException(unconsumedFlags);
}
}
/**
* Get any unused flag arguments.
*
* @param context the command context
*/
private String getUnusedFlags(CommandContext context) {
if (!anyFlags) {
Set<Character> unusedFlags = null;
for (char flag : context.getFlags()) {
boolean found = false;
if (legacyFlags.contains(flag)) {
break;
}
for (ParameterData parameter : parameters) {
Character paramFlag = parameter.getFlag();
if (paramFlag != null && flag == paramFlag) {
found = true;
break;
}
}
if (!found) {
if (unusedFlags == null) {
unusedFlags = new HashSet<Character>();
}
unusedFlags.add(flag);
}
}
if (unusedFlags != null) {
StringBuilder builder = new StringBuilder();
for (Character flag : unusedFlags) {
builder.append("-").append(flag).append(" ");
}
return builder.toString().trim();
}
}
return null;
}
/**
* Generate a name for a parameter.
*
* @param type the type
* @param classifier the classifier
* @param index the index
* @return a generated name
*/
private static String generateName(Type type, Annotation classifier, int index) {
if (classifier != null) {
return classifier.annotationType().getSimpleName().toLowerCase();
} else {
if (type instanceof Class<?>) {
return ((Class<?>) type).getSimpleName().toLowerCase();
} else {
return "unknown" + index;
}
}
}
}

View File

@ -0,0 +1,44 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
/**
* Thrown if the {@link ParametricBuilder} can't build commands from
* an object for whatever reason.
*/
public class ParametricException extends RuntimeException {
public ParametricException() {
super();
}
public ParametricException(String message, Throwable cause) {
super(message, cause);
}
public ParametricException(String message) {
super(message);
}
public ParametricException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,128 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.worldedit.util.command.MissingParameterException;
import com.sk89q.util.StringUtil;
/**
* A virtual scope that does not actually read from the underlying
* {@link CommandContext}.
*/
public class StringArgumentStack implements ArgumentStack {
private final boolean nonNullBoolean;
private final CommandContext context;
private final String[] arguments;
private int index = 0;
/**
* Create a new instance using the given context.
*
* @param context the context
* @param arguments a list of arguments
* @param nonNullBoolean true to have {@link #nextBoolean()} return false instead of null
*/
public StringArgumentStack(
CommandContext context, String[] arguments, boolean nonNullBoolean) {
this.context = context;
this.arguments = arguments;
this.nonNullBoolean = nonNullBoolean;
}
/**
* Create a new instance using the given context.
*
* @param context the context
* @param arguments an argument string to be parsed
* @param nonNullBoolean true to have {@link #nextBoolean()} return false instead of null
*/
public StringArgumentStack(
CommandContext context, String arguments, boolean nonNullBoolean) {
this.context = context;
this.arguments = CommandContext.split(arguments);
this.nonNullBoolean = nonNullBoolean;
}
@Override
public String next() throws ParameterException {
try {
return arguments[index++];
} catch (ArrayIndexOutOfBoundsException e) {
throw new MissingParameterException();
}
}
@Override
public Integer nextInt() throws ParameterException {
try {
return Integer.parseInt(next());
} catch (NumberFormatException e) {
throw new ParameterException(
"Expected a number, got '" + context.getString(index - 1) + "'");
}
}
@Override
public Double nextDouble() throws ParameterException {
try {
return Double.parseDouble(next());
} catch (NumberFormatException e) {
throw new ParameterException(
"Expected a number, got '" + context.getString(index - 1) + "'");
}
}
@Override
public Boolean nextBoolean() throws ParameterException {
try {
return next().equalsIgnoreCase("true");
} catch (IndexOutOfBoundsException e) {
if (nonNullBoolean) { // Special case
return false;
}
throw new MissingParameterException();
}
}
@Override
public String remaining() throws ParameterException {
try {
String value = StringUtil.joinString(arguments, " ", index);
markConsumed();
return value;
} catch (IndexOutOfBoundsException e) {
throw new MissingParameterException();
}
}
@Override
public void markConsumed() {
index = arguments.length;
}
@Override
public CommandContext getContext() {
return context;
}
}

View File

@ -0,0 +1,54 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.concurrency;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Even more {@code ExecutorService} factory methods.
*/
public final class EvenMoreExecutors {
private EvenMoreExecutors() {
}
/**
* Creates a thread pool that creates new threads as needed up to
* a maximum number of threads, but will reuse previously constructed
* threads when they are available.
*
* @param minThreads the minimum number of threads to have at a given time
* @param maxThreads the maximum number of threads to have at a given time
* @param queueSize the size of the queue before new submissions are rejected
* @return the newly created thread pool
*/
public static ExecutorService newBoundedCachedThreadPool(int minThreads, int maxThreads, int queueSize) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
minThreads, maxThreads,
60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(queueSize));
threadPoolExecutor.allowCoreThreadTimeOut(true);
return threadPoolExecutor;
}
}

View File

@ -0,0 +1,69 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.eventbus;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import java.lang.reflect.Method;
/**
* A {@link SubscriberFindingStrategy} for collecting all event handler methods
* that are marked with the {@link Subscribe} annotation.
*
* <p>Original for Guava, licensed under the Apache License, Version 2.0.</p>
*/
class AnnotatedSubscriberFinder implements SubscriberFindingStrategy {
/**
* {@inheritDoc}
*
* This implementation finds all methods marked with a {@link Subscribe}
* annotation.
*/
@Override
public Multimap<Class<?>, EventHandler> findAllSubscribers(Object listener) {
Multimap<Class<?>, EventHandler> methodsInListener = HashMultimap.create();
Class<?> clazz = listener.getClass();
while (clazz != null) {
for (Method method : clazz.getMethods()) {
Subscribe annotation = method.getAnnotation(Subscribe.class);
method.setAccessible(true);
if (annotation != null) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != 1) {
throw new IllegalArgumentException(
"Method " + method + " has @Subscribe annotation, but requires " +
parameterTypes.length + " arguments. Event handler methods " +
"must require a single argument.");
}
Class<?> eventType = parameterTypes[0];
EventHandler handler = new MethodEventHandler(annotation.priority(), listener, method);
methodsInListener.put(eventType, handler);
}
}
clazz = clazz.getSuperclass();
}
return methodsInListener;
}
}

View File

@ -0,0 +1,229 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.eventbus;
import com.google.common.base.Supplier;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import com.google.common.eventbus.DeadEvent;
import com.sk89q.worldedit.internal.annotation.RequiresNewerGuava;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Dispatches events to listeners, and provides ways for listeners to register
* themselves.
*
* <p>This class is based on Guava's {@link EventBus} but priority is supported
* and events are dispatched at the time of call, rather than being queued up.
* This does allow dispatching during an in-progress dispatch.</p>
*
* <p>This implementation utilizes naive synchronization on all getter and
* setter methods. Dispatch does not occur when a lock has been acquired,
* however.</p>
*/
public class EventBus {
private final Logger logger = Logger.getLogger(EventBus.class.getCanonicalName());
private final SetMultimap<Class<?>, EventHandler> handlersByType =
Multimaps.newSetMultimap(new HashMap<Class<?>, Collection<EventHandler>>(),
new Supplier<Set<EventHandler>>() {
@Override
public Set<EventHandler> get() {
return newHandlerSet();
}
});
/**
* Strategy for finding handler methods in registered objects. Currently,
* only the {@link AnnotatedSubscriberFinder} is supported, but this is
* encapsulated for future expansion.
*/
private final SubscriberFindingStrategy finder = new AnnotatedSubscriberFinder();
@RequiresNewerGuava
private HierarchyCache flattenHierarchyCache = new HierarchyCache();
/**
* Registers the given handler for the given class to receive events.
*
* @param clazz the event class to register
* @param handler the handler to register
*/
public synchronized void subscribe(Class<?> clazz, EventHandler handler) {
checkNotNull(clazz);
checkNotNull(handler);
handlersByType.put(clazz, handler);
}
/**
* Registers the given handler for the given class to receive events.
*
* @param handlers a map of handlers
*/
public synchronized void subscribeAll(Multimap<Class<?>, EventHandler> handlers) {
checkNotNull(handlers);
handlersByType.putAll(handlers);
}
/**
* Unregisters the given handler for the given class.
*
* @param clazz the class
* @param handler the handler
*/
public synchronized void unsubscribe(Class<?> clazz, EventHandler handler) {
checkNotNull(clazz);
checkNotNull(handler);
handlersByType.remove(clazz, handler);
}
/**
* Unregisters the given handlers.
*
* @param handlers a map of handlers
*/
public synchronized void unsubscribeAll(Multimap<Class<?>, EventHandler> handlers) {
checkNotNull(handlers);
for (Map.Entry<Class<?>, Collection<EventHandler>> entry : handlers.asMap().entrySet()) {
Set<EventHandler> currentHandlers = getHandlersForEventType(entry.getKey());
Collection<EventHandler> eventMethodsInListener = entry.getValue();
if (currentHandlers != null &&!currentHandlers.containsAll(entry.getValue())) {
currentHandlers.removeAll(eventMethodsInListener);
}
}
}
/**
* Registers all handler methods on {@code object} to receive events.
* Handler methods are selected and classified using this EventBus's
* {@link SubscriberFindingStrategy}; the default strategy is the
* {@link AnnotatedSubscriberFinder}.
*
* @param object object whose handler methods should be registered.
*/
public void register(Object object) {
subscribeAll(finder.findAllSubscribers(object));
}
/**
* Unregisters all handler methods on a registered {@code object}.
*
* @param object object whose handler methods should be unregistered.
* @throws IllegalArgumentException if the object was not previously registered.
*/
public void unregister(Object object) {
unsubscribeAll(finder.findAllSubscribers(object));
}
/**
* Posts an event to all registered handlers. This method will return
* successfully after the event has been posted to all handlers, and
* regardless of any exceptions thrown by handlers.
*
* <p>If no handlers have been subscribed for {@code event}'s class, and
* {@code event} is not already a {@link DeadEvent}, it will be wrapped in a
* DeadEvent and reposted.
*
* @param event event to post.
*/
public void post(Object event) {
List<EventHandler> dispatching = new ArrayList<EventHandler>();
synchronized (this) {
Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass());
for (Class<?> eventType : dispatchTypes) {
Set<EventHandler> wrappers = getHandlersForEventType(eventType);
if (wrappers != null && !wrappers.isEmpty()) {
dispatching.addAll(wrappers);
}
}
}
Collections.sort(dispatching);
for (EventHandler handler : dispatching) {
dispatch(event, handler);
}
}
/**
* Dispatches {@code event} to the handler in {@code handler}. This method
* is an appropriate override point for subclasses that wish to make
* event delivery asynchronous.
*
* @param event event to dispatch.
* @param handler handler that will call the handler.
*/
protected void dispatch(Object event, EventHandler handler) {
try {
handler.handleEvent(event);
} catch (InvocationTargetException e) {
logger.log(Level.SEVERE,
"Could not dispatch event: " + event + " to handler " + handler, e);
}
}
/**
* Retrieves a mutable set of the currently registered handlers for
* {@code type}. If no handlers are currently registered for {@code type},
* this method may either return {@code null} or an empty set.
*
* @param type type of handlers to retrieve.
* @return currently registered handlers, or {@code null}.
*/
synchronized Set<EventHandler> getHandlersForEventType(Class<?> type) {
return handlersByType.get(type);
}
/**
* Creates a new Set for insertion into the handler map. This is provided
* as an override point for subclasses. The returned set should support
* concurrent access.
*
* @return a new, mutable set for handlers.
*/
protected synchronized Set<EventHandler> newHandlerSet() {
return new HashSet<EventHandler>();
}
/**
* Flattens a class's type hierarchy into a set of Class objects. The set
* will include all superclasses (transitively), and all interfaces
* implemented by these superclasses.
*
* @param concreteClass class whose type hierarchy will be retrieved.
* @return {@code clazz}'s complete type hierarchy, flattened and uniqued.
*/
Set<Class<?>> flattenHierarchy(Class<?> concreteClass) {
return flattenHierarchyCache.get(concreteClass);
}
}

View File

@ -0,0 +1,105 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.eventbus;
import java.lang.reflect.InvocationTargetException;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Event handler object for {@link EventBus} that is able to dispatch
* an event.
*
* <p>Original for Guava, licensed under the Apache License, Version 2.0.</p>
*/
public abstract class EventHandler implements Comparable<EventHandler> {
public enum Priority {
VERY_EARLY,
EARLY,
NORMAL,
LATE,
VERY_LATE
}
private final Priority priority;
/**
* Create a new event handler.
*
* @param priority the priority
*/
protected EventHandler(Priority priority) {
checkNotNull(priority);
this.priority = priority;
}
/**
* Get the priority.
*
* @return the priority
*/
public Priority getPriority() {
return priority;
}
/**
* Dispatch the given event.
*
* <p>Subclasses should override {@link #dispatch(Object)}.</p>
*
* @param event the event
* @throws InvocationTargetException thrown if an exception is thrown during dispatch
*/
public final void handleEvent(Object event) throws InvocationTargetException {
try {
dispatch(event);
} catch (Throwable t) {
throw new InvocationTargetException(t);
}
}
/**
* Dispatch the event.
*
* @param event the event object
* @throws Exception an exception that may be thrown
*/
public abstract void dispatch(Object event) throws Exception;
@Override
public int compareTo(EventHandler o) {
return getPriority().ordinal() - o.getPriority().ordinal();
}
@Override
public abstract int hashCode();
@Override
public abstract boolean equals(Object obj);
@Override
public String toString() {
return "EventHandler{" +
"priority=" + priority +
'}';
}
}

View File

@ -0,0 +1,69 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.eventbus;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.sk89q.worldedit.internal.annotation.RequiresNewerGuava;
import java.util.*;
/**
* Holds a cache of class hierarchy.
*
* <p>This exists because Bukkit has an ancient version of Guava and the cache
* library in Guava has since changed.</>
*/
@RequiresNewerGuava
class HierarchyCache {
private final Map<Class<?>, Set<Class<?>>> cache = new WeakHashMap<Class<?>, Set<Class<?>>>();
public Set<Class<?>> get(Class<?> concreteClass) {
Set<Class<?>> ret = cache.get(concreteClass);
if (ret == null) {
ret = build(concreteClass);
cache.put(concreteClass, ret);
}
return ret;
}
protected Set<Class<?>> build(Class<?> concreteClass) {
List<Class<?>> parents = Lists.newLinkedList();
Set<Class<?>> classes = Sets.newHashSet();
parents.add(concreteClass);
while (!parents.isEmpty()) {
Class<?> clazz = parents.remove(0);
classes.add(clazz);
Class<?> parent = clazz.getSuperclass();
if (parent != null) {
parents.add(parent);
}
Collections.addAll(parents, clazz.getInterfaces());
}
return classes;
}
}

View File

@ -0,0 +1,81 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.eventbus;
import java.lang.reflect.Method;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Invokes a {@link Method} to dispatch an event.
*/
public class MethodEventHandler extends EventHandler {
private final Object object;
private final Method method;
/**
* Create a new event handler.
*
* @param priority the priority
* @param method the method
*/
public MethodEventHandler(Priority priority, Object object, Method method) {
super(priority);
checkNotNull(method);
this.object = object;
this.method = method;
}
/**
* Get the method.
*
* @return the method
*/
public Method getMethod() {
return method;
}
@Override
public void dispatch(Object event) throws Exception {
method.invoke(object, event);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MethodEventHandler that = (MethodEventHandler) o;
if (!method.equals(that.method)) return false;
if (object != null ? !object.equals(that.object) : that.object != null)
return false;
return true;
}
@Override
public int hashCode() {
int result = object != null ? object.hashCode() : 0;
result = 31 * result + method.hashCode();
return result;
}
}

View File

@ -0,0 +1,42 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.eventbus;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
/**
* Used to mark methods as event handlers.
*/
@Retention(RUNTIME)
@Target(METHOD)
public @interface Subscribe {
/**
* The priority as far as order of dispatching is concerned.
*
* @return the priority
*/
EventHandler.Priority priority() default EventHandler.Priority.NORMAL;
}

View File

@ -0,0 +1,43 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.eventbus;
import com.google.common.collect.Multimap;
/**
* A method for finding event handler methods in objects, for use by
* {@link EventBus}.
*/
interface SubscriberFindingStrategy {
/**
* Finds all suitable event handler methods in {@code source}, organizes them
* by the type of event they handle, and wraps them in {@link EventHandler}s.
*
* @param source object whose handlers are desired.
* @return EventHandler objects for each handler method, organized by event
* type.
*
* @throws IllegalArgumentException if {@code source} is not appropriate for
* this strategy (in ways that this interface does not define).
*/
Multimap<Class<?>, EventHandler> findAllSubscribers(Object source);
}

View File

@ -0,0 +1,275 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.formatting;
import com.google.common.base.Joiner;
import java.util.LinkedList;
import java.util.List;
public class ColorCodeBuilder {
private static final ColorCodeBuilder instance = new ColorCodeBuilder();
private static final Joiner newLineJoiner = Joiner.on("\n");
public static final int GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH = 47;
/**
* Convert a message into color-coded text.
*
* @param message the message
* @return a list of lines
*/
public String[] build(StyledFragment message) {
StringBuilder builder = new StringBuilder();
buildFragment(builder, message, message.getStyle(), new StyleSet());
return builder.toString().split("\r?\n");
}
/**
* Build a fragment.
*
* @param builder the string builder
* @param message the message
* @param parentStyle the parent style
* @param lastStyle the last style
* @return the last style used
*/
private StyleSet buildFragment(StringBuilder builder, StyledFragment message, StyleSet parentStyle, StyleSet lastStyle) {
for (Fragment node : message.getChildren()) {
if (node instanceof StyledFragment) {
StyledFragment fragment = (StyledFragment) node;
lastStyle = buildFragment(
builder, fragment,
parentStyle.extend(message.getStyle()), lastStyle);
} else {
StyleSet style = parentStyle.extend(message.getStyle());
builder.append(getAdditive(style, lastStyle));
builder.append(node);
lastStyle = style;
}
}
return lastStyle;
}
/**
* Get the formatting codes.
*
* @param style the style
* @return the color codes
*/
public static String getFormattingCode(StyleSet style) {
StringBuilder builder = new StringBuilder();
if (style.isBold()) {
builder.append(Style.BOLD);
}
if (style.isItalic()) {
builder.append(Style.ITALIC);
}
if (style.isUnderline()) {
builder.append(Style.UNDERLINE);
}
if (style.isStrikethrough()) {
builder.append(Style.STRIKETHROUGH);
}
return builder.toString();
}
/**
* Get the formatting and color codes.
*
* @param style the style
* @return the color codes
*/
public static String getCode(StyleSet style) {
StringBuilder builder = new StringBuilder();
builder.append(getFormattingCode(style));
if (style.getColor() != null) {
builder.append(style.getColor());
}
return builder.toString();
}
/**
* Get the additional color codes needed to set the given style when the current
* style is the other given one.
*
* @param resetTo the style to reset to
* @param resetFrom the style to reset from
* @return the color codes
*/
public static String getAdditive(StyleSet resetTo, StyleSet resetFrom) {
if (!resetFrom.hasFormatting() && resetTo.hasFormatting()) {
StringBuilder builder = new StringBuilder();
builder.append(getFormattingCode(resetTo));
if (resetFrom.getColor() != resetTo.getColor()) {
builder.append(resetTo.getColor());
}
return builder.toString();
} else if (!resetFrom.hasEqualFormatting(resetTo) ||
(resetFrom.getColor() != null && resetTo.getColor() == null)) {
StringBuilder builder = new StringBuilder();
// Have to set reset code and add back all the formatting codes
builder.append(Style.RESET);
builder.append(getCode(resetTo));
return builder.toString();
} else {
if (resetFrom.getColor() != resetTo.getColor()) {
return String.valueOf(resetTo.getColor());
}
}
return "";
}
/**
* Word wrap the given text and maintain color codes throughout lines.
*
* <p>This is borrowed from Bukkit.</p>
*
* @param rawString the raw string
* @param lineLength the maximum line length
* @return a list of lines
*/
private String[] wordWrap(String rawString, int lineLength) {
// A null string is a single line
if (rawString == null) {
return new String[] {""};
}
// A string shorter than the lineWidth is a single line
if (rawString.length() <= lineLength && !rawString.contains("\n")) {
return new String[] {rawString};
}
char[] rawChars = (rawString + ' ').toCharArray(); // add a trailing space to trigger pagination
StringBuilder word = new StringBuilder();
StringBuilder line = new StringBuilder();
List<String> lines = new LinkedList<String>();
int lineColorChars = 0;
for (int i = 0; i < rawChars.length; i++) {
char c = rawChars[i];
// skip chat color modifiers
if (c == Style.COLOR_CHAR) {
word.append(Style.getByChar(rawChars[i + 1]));
lineColorChars += 2;
i++; // Eat the next character as we have already processed it
continue;
}
if (c == ' ' || c == '\n') {
if (line.length() == 0 && word.length() > lineLength) { // special case: extremely long word begins a line
String wordStr = word.toString();
String transformed;
if ((transformed = transform(wordStr)) != null) {
line.append(transformed);
} else {
for (String partialWord : word.toString().split("(?<=\\G.{" + lineLength + "})")) {
lines.add(partialWord);
}
}
} else if (line.length() + word.length() - lineColorChars == lineLength) { // Line exactly the correct length...newline
line.append(' ');
line.append(word);
lines.add(line.toString());
line = new StringBuilder();
lineColorChars = 0;
} else if (line.length() + 1 + word.length() - lineColorChars > lineLength) { // Line too long...break the line
String wordStr = word.toString();
String transformed;
if (word.length() > lineLength && (transformed = transform(wordStr)) != null) {
if (line.length() + 1 + transformed.length() - lineColorChars > lineLength) {
lines.add(line.toString());
line = new StringBuilder(transformed);
lineColorChars = 0;
} else {
if (line.length() > 0) {
line.append(' ');
}
line.append(transformed);
}
} else {
for (String partialWord : wordStr.split("(?<=\\G.{" + lineLength + "})")) {
lines.add(line.toString());
line = new StringBuilder(partialWord);
}
lineColorChars = 0;
}
} else {
if (line.length() > 0) {
line.append(' ');
}
line.append(word);
}
word = new StringBuilder();
if (c == '\n') { // Newline forces the line to flush
lines.add(line.toString());
line = new StringBuilder();
}
} else {
word.append(c);
}
}
if(line.length() > 0) { // Only add the last line if there is anything to add
lines.add(line.toString());
}
// Iterate over the wrapped lines, applying the last color from one line to the beginning of the next
if (lines.get(0).isEmpty() || lines.get(0).charAt(0) != Style.COLOR_CHAR) {
lines.set(0, Style.WHITE + lines.get(0));
}
for (int i = 1; i < lines.size(); i++) {
final String pLine = lines.get(i-1);
final String subLine = lines.get(i);
char color = pLine.charAt(pLine.lastIndexOf(Style.COLOR_CHAR) + 1);
if (subLine.isEmpty() || subLine.charAt(0) != Style.COLOR_CHAR) {
lines.set(i, Style.getByChar(color) + subLine);
}
}
return lines.toArray(new String[lines.size()]);
}
/**
* Callback for transforming a word, such as a URL.
*
* @param word the word
* @return the transformed value, or null to do nothing
*/
protected String transform(String word) {
return null;
}
/**
* Convert the given styled fragment into color codes.
*
* @param fragment the fragment
* @return color codes
*/
public static String asColorCodes(StyledFragment fragment) {
return newLineJoiner.join(instance.build(fragment));
}
}

View File

@ -0,0 +1,92 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.formatting;
/**
* A fragment of text.
*/
public class Fragment {
private final StringBuilder builder = new StringBuilder();
Fragment() {
}
public Fragment append(String str) {
builder.append(Style.stripColor(str));
return this;
}
public Fragment append(Object obj) {
append(String.valueOf(obj));
return this;
}
public Fragment append(StringBuffer sb) {
append(String.valueOf(sb));
return this;
}
public Fragment append(CharSequence s) {
append(String.valueOf(s));
return this;
}
public Fragment append(boolean b) {
append(String.valueOf(b));
return this;
}
public Fragment append(char c) {
append(String.valueOf(c));
return this;
}
public Fragment append(int i) {
append(String.valueOf(i));
return this;
}
public Fragment append(long lng) {
append(String.valueOf(lng));
return this;
}
public Fragment append(float f) {
append(String.valueOf(f));
return this;
}
public Fragment append(double d) {
append(String.valueOf(d));
return this;
}
public Fragment newLine() {
append("\n");
return this;
}
@Override
public String toString() {
return builder.toString();
}
}

View File

@ -0,0 +1,276 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.formatting;
import com.google.common.collect.Maps;
import java.util.Map;
import java.util.regex.Pattern;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* All supported color values for chat.
*
* <p>From Bukkit.</p>
*/
public enum Style {
/**
* Represents black
*/
BLACK('0', 0x00),
/**
* Represents dark blue
*/
BLUE_DARK('1', 0x1),
/**
* Represents dark green
*/
GREEN_DARK('2', 0x2),
/**
* Represents dark blue (aqua)
*/
CYAN_DARK('3', 0x3),
/**
* Represents dark red
*/
RED_DARK('4', 0x4),
/**
* Represents dark purple
*/
PURPLE_DARK('5', 0x5),
/**
* Represents gold
*/
YELLOW_DARK('6', 0x6),
/**
* Represents gray
*/
GRAY('7', 0x7),
/**
* Represents dark gray
*/
GRAY_DARK('8', 0x8),
/**
* Represents blue
*/
BLUE('9', 0x9),
/**
* Represents green
*/
GREEN('a', 0xA),
/**
* Represents aqua
*/
CYAN('b', 0xB),
/**
* Represents red
*/
RED('c', 0xC),
/**
* Represents light purple
*/
PURPLE('d', 0xD),
/**
* Represents yellow
*/
YELLOW('e', 0xE),
/**
* Represents white
*/
WHITE('f', 0xF),
/**
* Represents magical characters that change around randomly
*/
RANDOMIZE('k', 0x10, true),
/**
* Makes the text bold.
*/
BOLD('l', 0x11, true),
/**
* Makes a line appear through the text.
*/
STRIKETHROUGH('m', 0x12, true),
/**
* Makes the text appear underlined.
*/
UNDERLINE('n', 0x13, true),
/**
* Makes the text italic.
*/
ITALIC('o', 0x14, true),
/**
* Resets all previous chat colors or formats.
*/
RESET('r', 0x15);
/**
* The special character which prefixes all chat color codes. Use this if you need to dynamically
* convert color codes from your custom format.
*/
public static final char COLOR_CHAR = '\u00A7';
private static final Pattern STRIP_COLOR_PATTERN = Pattern.compile("(?i)" + COLOR_CHAR + "[0-9A-FK-OR]");
private final int intCode;
private final char code;
private final boolean isFormat;
private final String toString;
private final static Map<Integer, Style> BY_ID = Maps.newHashMap();
private final static Map<Character, Style> BY_CHAR = Maps.newHashMap();
private Style(char code, int intCode) {
this(code, intCode, false);
}
private Style(char code, int intCode, boolean isFormat) {
this.code = code;
this.intCode = intCode;
this.isFormat = isFormat;
this.toString = new String(new char[] {COLOR_CHAR, code});
}
/**
* Gets the char value associated with this color
*
* @return A char value of this color code
*/
public char getChar() {
return code;
}
@Override
public String toString() {
return toString;
}
/**
* Checks if this code is a format code as opposed to a color code.
*
* @return the if the code is a formatting code
*/
public boolean isFormat() {
return isFormat;
}
/**
* Checks if this code is a color code as opposed to a format code.
*
* @return the if the code is a color
*/
public boolean isColor() {
return !isFormat && this != RESET;
}
/**
* Gets the color represented by the specified color code
*
* @param code Code to check
* @return Associative {@link org.bukkit.ChatColor} with the given code, or null if it doesn't exist
*/
public static Style getByChar(char code) {
return BY_CHAR.get(code);
}
/**
* Gets the color represented by the specified color code
*
* @param code Code to check
* @return Associative {@link org.bukkit.ChatColor} with the given code, or null if it doesn't exist
*/
public static Style getByChar(String code) {
checkNotNull(code);
checkArgument(!code.isEmpty(), "Code must have at least one character");
return BY_CHAR.get(code.charAt(0));
}
/**
* Strips the given message of all color codes
*
* @param input String to strip of color
* @return A copy of the input string, without any coloring
*/
public static String stripColor(final String input) {
if (input == null) {
return null;
}
return STRIP_COLOR_PATTERN.matcher(input).replaceAll("");
}
/**
* Translates a string using an alternate color code character into a string that uses the internal
* ChatColor.COLOR_CODE color code character. The alternate color code character will only be replaced
* if it is immediately followed by 0-9, A-F, a-f, K-O, k-o, R or r.
*
* @param altColorChar The alternate color code character to replace. Ex: &
* @param textToTranslate Text containing the alternate color code character.
* @return Text containing the ChatColor.COLOR_CODE color code character.
*/
public static String translateAlternateColorCodes(char altColorChar, String textToTranslate) {
char[] b = textToTranslate.toCharArray();
for (int i = 0; i < b.length - 1; i++) {
if (b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(b[i+1]) > -1) {
b[i] = Style.COLOR_CHAR;
b[i+1] = Character.toLowerCase(b[i+1]);
}
}
return new String(b);
}
/**
* Gets the ChatColors used at the end of the given input string.
*
* @param input Input string to retrieve the colors from.
* @return Any remaining ChatColors to pass onto the next line.
*/
public static String getLastColors(String input) {
String result = "";
int length = input.length();
// Search backwards from the end as it is faster
for (int index = length - 1; index > -1; index--) {
char section = input.charAt(index);
if (section == COLOR_CHAR && index < length - 1) {
char c = input.charAt(index + 1);
Style color = getByChar(c);
if (color != null) {
result = color + result;
// Once we find a color or reset we can stop searching
if (color.isColor() || color == RESET) {
break;
}
}
}
}
return result;
}
static {
for (Style color : values()) {
BY_ID.put(color.intCode, color);
BY_CHAR.put(color.code, color);
}
}
}

View File

@ -0,0 +1,249 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.formatting;
/**
* Represents set of styles, such as color, bold, etc.
*/
public class StyleSet {
private Boolean bold;
private Boolean italic;
private Boolean underline;
private Boolean strikethrough;
private Style color;
/**
* Create a new style set with no properties set.
*/
public StyleSet() {
}
/**
* Create a new style set with the given styles.
*
* <p>{@link Style#RESET} will be ignored if provided.</p>
*
* @param styles a list of styles
*/
public StyleSet(Style... styles) {
for (Style style : styles) {
if (style.isColor()) {
color = style;
} else if (style == Style.BOLD) {
bold = true;
} else if (style == Style.ITALIC) {
italic = true;
} else if (style == Style.UNDERLINE) {
underline = true;
} else if (style == Style.STRIKETHROUGH) {
strikethrough = true;
}
}
}
/**
* Get whether this style set is bold.
*
* @return true, false, or null if unset
*/
public Boolean getBold() {
return bold;
}
/**
* Get whether the text is bold.
*
* @return true if bold
*/
public boolean isBold() {
return getBold() != null && getBold();
}
/**
* Set whether the text is bold.
*
* @param bold true, false, or null to unset
*/
public void setBold(Boolean bold) {
this.bold = bold;
}
/**
* Get whether this style set is italicized.
*
* @return true, false, or null if unset
*/
public Boolean getItalic() {
return italic;
}
/**
* Get whether the text is italicized.
*
* @return true if italicized
*/
public boolean isItalic() {
return getItalic() != null && getItalic();
}
/**
* Set whether the text is italicized.
*
* @param italic false, or null to unset
*/
public void setItalic(Boolean italic) {
this.italic = italic;
}
/**
* Get whether this style set is underlined.
*
* @return true, false, or null if unset
*/
public Boolean getUnderline() {
return underline;
}
/**
* Get whether the text is underlined.
*
* @return true if underlined
*/
public boolean isUnderline() {
return getUnderline() != null && getUnderline();
}
/**
* Set whether the text is underline.
*
* @param underline false, or null to unset
*/
public void setUnderline(Boolean underline) {
this.underline = underline;
}
/**
* Get whether this style set is stricken through.
*
* @return true, false, or null if unset
*/
public Boolean getStrikethrough() {
return strikethrough;
}
/**
* Get whether the text is stricken through.
*
* @return true if there is strikethrough applied
*/
public boolean isStrikethrough() {
return getStrikethrough() != null && getStrikethrough();
}
/**
* Set whether the text is stricken through.
*
* @param strikethrough false, or null to unset
*/
public void setStrikethrough(Boolean strikethrough) {
this.strikethrough = strikethrough;
}
/**
* Get the color of the text.
*
* @return true, false, or null if unset
*/
public Style getColor() {
return color;
}
/**
* Set the color of the text.
*
* @param color the color
*/
public void setColor(Style color) {
this.color = color;
}
/**
* Return whether text formatting (bold, italics, underline, strikethrough) is set.
*
* @return true if formatting is set
*/
public boolean hasFormatting() {
return getBold() != null || getItalic() != null
|| getUnderline() != null || getStrikethrough() != null;
}
/**
* Return where the text formatting of the given style set is different from
* that assigned to this one.
*
* @param other the other style set
* @return true if there is a difference
*/
public boolean hasEqualFormatting(StyleSet other) {
return getBold() == other.getBold() && getItalic() == other.getItalic()
&& getUnderline() == other.getUnderline() &&
getStrikethrough() == other.getStrikethrough();
}
/**
* Create a new instance with styles inherited from this one but with new styles
* from the given style set.
*
* @param style the style set
* @return a new style set instance
*/
public StyleSet extend(StyleSet style) {
StyleSet newStyle = clone();
if (style.getBold() != null) {
newStyle.setBold(style.getBold());
}
if (style.getItalic() != null) {
newStyle.setItalic(style.getItalic());
}
if (style.getUnderline() != null) {
newStyle.setUnderline(style.getUnderline());
}
if (style.getStrikethrough() != null) {
newStyle.setStrikethrough(style.getStrikethrough());
}
if (style.getColor() != null) {
newStyle.setColor(style.getColor());
}
return newStyle;
}
@Override
public StyleSet clone() {
StyleSet style = new StyleSet();
style.setBold(getBold());
style.setItalic(getItalic());
style.setUnderline(getUnderline());
style.setStrikethrough(getStrikethrough());
style.setColor(getColor());
return style;
}
}

View File

@ -0,0 +1,150 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.formatting;
import java.util.ArrayList;
import java.util.List;
/**
* A fragment of text that can be styled.
*/
public class StyledFragment extends Fragment {
private final List<Fragment> children = new ArrayList<Fragment>();
private StyleSet style;
private Fragment lastText;
public StyledFragment() {
style = new StyleSet();
}
public StyledFragment(StyleSet style) {
this.style = style;
}
public StyledFragment(Style... styles) {
this.style = new StyleSet(styles);
}
public StyleSet getStyle() {
return style;
}
public void setStyles(StyleSet style) {
this.style = style;
}
public List<Fragment> getChildren() {
return children;
}
protected Fragment lastText() {
Fragment text;
if (!children.isEmpty()) {
text = children.get(children.size() - 1);
if (text == lastText) {
return text;
}
}
text = new Fragment();
this.lastText = text;
children.add(text);
return text;
}
public StyledFragment createFragment(Style... styles) {
StyledFragment fragment = new StyledFragment(styles);
append(fragment);
return fragment;
}
public StyledFragment append(StyledFragment fragment) {
children.add(fragment);
return this;
}
@Override
public StyledFragment append(String str) {
lastText().append(str);
return this;
}
@Override
public StyledFragment append(Object obj) {
append(String.valueOf(obj));
return this;
}
@Override
public StyledFragment append(StringBuffer sb) {
append(String.valueOf(sb));
return this;
}
@Override
public StyledFragment append(CharSequence s) {
append(String.valueOf(s));
return this;
}
@Override
public StyledFragment append(boolean b) {
append(String.valueOf(b));
return this;
}
@Override
public StyledFragment append(char c) {
append(String.valueOf(c));
return this;
}
@Override
public StyledFragment append(int i) {
append(String.valueOf(i));
return this;
}
@Override
public StyledFragment append(long lng) {
append(String.valueOf(lng));
return this;
}
@Override
public StyledFragment append(float f) {
append(String.valueOf(f));
return this;
}
@Override
public StyledFragment append(double d) {
append(String.valueOf(d));
return this;
}
@Override
public StyledFragment newLine() {
append("\n");
return this;
}
}

View File

@ -0,0 +1,37 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.formatting.component;
import com.sk89q.worldedit.util.formatting.Style;
import com.sk89q.worldedit.util.formatting.StyledFragment;
/**
* Represents a fragment representing a command that is to be typed.
*/
public class Code extends StyledFragment {
/**
* Create a new instance.
*/
public Code() {
super(Style.CYAN);
}
}

View File

@ -0,0 +1,47 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.formatting.component;
import com.sk89q.worldedit.util.formatting.Style;
public class CommandListBox extends MessageBox {
private boolean first = true;
/**
* Create a new box.
*
* @param title the title
*/
public CommandListBox(String title) {
super(title);
}
public CommandListBox appendCommand(String alias, String description) {
if (!first) {
getContents().newLine();
}
getContents().createFragment(Style.YELLOW_DARK).append(alias).append(": ");
getContents().append(description);
first = false;
return this;
}
}

View File

@ -0,0 +1,110 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.formatting.component;
import com.sk89q.minecraft.util.commands.CommandLocals;
import com.sk89q.worldedit.extension.platform.CommandManager;
import com.sk89q.worldedit.util.command.CommandCallable;
import com.sk89q.worldedit.util.command.CommandMapping;
import com.sk89q.worldedit.util.command.Description;
import com.sk89q.worldedit.util.command.Dispatcher;
import com.sk89q.worldedit.util.command.PrimaryAliasComparator;
import com.sk89q.worldedit.util.formatting.StyledFragment;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* A box to describe usage of a command.
*/
public class CommandUsageBox extends StyledFragment {
/**
* Create a new usage box.
*
* @param command the command to describe
* @param commandString the command that was used, such as "/we" or "/brush sphere"
*/
public CommandUsageBox(CommandCallable command, String commandString) {
this(command, commandString, null);
}
/**
* Create a new usage box.
*
* @param command the command to describe
* @param commandString the command that was used, such as "/we" or "/brush sphere"
* @param locals list of locals to use
*/
public CommandUsageBox(CommandCallable command, String commandString, @Nullable CommandLocals locals) {
checkNotNull(command);
checkNotNull(commandString);
if (command instanceof Dispatcher) {
attachDispatcherUsage((Dispatcher) command, commandString, locals);
} else {
attachCommandUsage(command.getDescription(), commandString);
}
}
private void attachDispatcherUsage(Dispatcher dispatcher, String commandString, @Nullable CommandLocals locals) {
CommandListBox box = new CommandListBox("Subcommands");
String prefix = !commandString.isEmpty() ? commandString + " " : "";
List<CommandMapping> list = new ArrayList<CommandMapping>(dispatcher.getCommands());
Collections.sort(list, new PrimaryAliasComparator(CommandManager.COMMAND_CLEAN_PATTERN));
for (CommandMapping mapping : list) {
if (locals == null || mapping.getCallable().testPermission(locals)) {
box.appendCommand(prefix + mapping.getPrimaryAlias(), mapping.getDescription().getShortDescription());
}
}
append(box);
}
private void attachCommandUsage(Description description, String commandString) {
MessageBox box = new MessageBox("Help for " + commandString);
StyledFragment contents = box.getContents();
if (description.getUsage() != null) {
contents.append(new Label().append("Usage: "));
contents.append(description.getUsage());
} else {
contents.append(new Subtle().append("Usage information is not available."));
}
contents.newLine();
if (description.getHelp() != null) {
contents.append(description.getHelp());
} else if (description.getShortDescription() != null) {
contents.append(description.getShortDescription());
} else {
contents.append(new Subtle().append("No further help is available."));
}
append(box);
}
}

View File

@ -0,0 +1,37 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.formatting.component;
import com.sk89q.worldedit.util.formatting.Style;
import com.sk89q.worldedit.util.formatting.StyledFragment;
/**
* Represents a fragment representing a label.
*/
public class Label extends StyledFragment {
/**
* Create a new instance.
*/
public Label() {
super(Style.YELLOW);
}
}

View File

@ -0,0 +1,74 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.formatting.component;
import com.sk89q.worldedit.util.formatting.ColorCodeBuilder;
import com.sk89q.worldedit.util.formatting.Style;
import com.sk89q.worldedit.util.formatting.StyledFragment;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Makes for a box with a border above and below.
*/
public class MessageBox extends StyledFragment {
private final StyledFragment contents = new StyledFragment();
/**
* Create a new box.
*/
public MessageBox(String title) {
checkNotNull(title);
int leftOver = ColorCodeBuilder.GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH - title.length() - 2;
int leftSide = (int) Math.floor(leftOver * 1.0/3);
int rightSide = (int) Math.floor(leftOver * 2.0/3);
if (leftSide > 0) {
createFragment(Style.YELLOW).append(createBorder(leftSide));
}
append(" ");
append(title);
append(" ");
if (rightSide > 0) {
createFragment(Style.YELLOW).append(createBorder(rightSide));
}
newLine();
append(contents);
}
private String createBorder(int count) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < count; i++) {
builder.append("-");
}
return builder.toString();
}
/**
* Get the internal contents.
*
* @return the contents
*/
public StyledFragment getContents() {
return contents;
}
}

View File

@ -0,0 +1,37 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.formatting.component;
import com.sk89q.worldedit.util.formatting.Style;
import com.sk89q.worldedit.util.formatting.StyledFragment;
/**
* Represents a subtle part of the message.
*/
public class Subtle extends StyledFragment {
/**
* Create a new instance.
*/
public Subtle() {
super(Style.GRAY);
}
}

View File

@ -0,0 +1,192 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.function;
import com.google.common.base.Function;
import javax.annotation.Nullable;
import java.util.regex.Pattern;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Provides a Levenshtein distance between a given string and each string
* that this function is applied to.
*/
public class LevenshteinDistance implements Function<String, Integer> {
public final static Pattern STANDARD_CHARS = Pattern.compile("[ _\\-]");
private final String baseString;
private final boolean caseSensitive;
private final Pattern replacePattern;
/**
* Create a new instance.
*
* @param baseString the string to compare to
* @param caseSensitive true to make case sensitive comparisons
*/
public LevenshteinDistance(String baseString, boolean caseSensitive) {
this(baseString, caseSensitive, null);
}
/**
* Create a new instance.
*
* @param baseString the string to compare to
* @param caseSensitive true to make case sensitive comparisons
* @param replacePattern pattern to match characters to be removed in both the input and test strings (may be null)
*/
public LevenshteinDistance(String baseString, boolean caseSensitive, @Nullable Pattern replacePattern) {
checkNotNull(baseString);
this.caseSensitive = caseSensitive;
this.replacePattern = replacePattern;
baseString = caseSensitive ? baseString : baseString.toLowerCase();
baseString = replacePattern != null ? replacePattern.matcher(baseString).replaceAll("") : baseString;
this.baseString = baseString;
}
@Nullable
@Override
public Integer apply(String input) {
if (input == null) {
return null;
}
if (replacePattern != null) {
input = replacePattern.matcher(input).replaceAll("");
}
if (caseSensitive) {
return distance(baseString, input);
} else {
return distance(baseString, input.toLowerCase());
}
}
/**
* <p>Find the Levenshtein distance between two Strings.</p>
*
* <p>This is the number of changes needed to change one String into
* another, where each change is a single character modification (deletion,
* insertion or substitution).</p>
*
* <p>The previous implementation of the Levenshtein distance algorithm
* was from <a href="http://www.merriampark.com/ld.htm">http://www.merriampark.com/ld.htm</a></p>
*
* <p>Chas Emerick has written an implementation in Java, which avoids an OutOfMemoryError
* which can occur when my Java implementation is used with very large strings.<br>
* This implementation of the Levenshtein distance algorithm
* is from <a href="http://www.merriampark.com/ldjava.htm">http://www.merriampark.com/ldjava.htm</a></p>
*
* <pre>
* distance(null, *) = IllegalArgumentException
* distance(*, null) = IllegalArgumentException
* distance("","") = 0
* distance("","a") = 1
* distance("aaapppp", "") = 7
* distance("frog", "fog") = 1
* distance("fly", "ant") = 3
* distance("elephant", "hippo") = 7
* distance("hippo", "elephant") = 7
* distance("hippo", "zzzzzzzz") = 8
* distance("hello", "hallo") = 1
* </pre>
*
* @param s the first String, must not be null
* @param t the second String, must not be null
* @return result distance
* @throws IllegalArgumentException if either String input {@code null}
*/
public static int distance(String s, String t) {
if (s == null || t == null) {
throw new IllegalArgumentException("Strings must not be null");
}
/*
* The difference between this impl. and the previous is that, rather
* than creating and retaining a matrix of size s.length()+1 by
* t.length()+1, we maintain two single-dimensional arrays of length
* s.length()+1. The first, d, is the 'current working' distance array
* that maintains the newest distance cost counts as we iterate through
* the characters of String s. Each time we increment the index of
* String t we are comparing, d is copied to p, the second int[]. Doing
* so allows us to retain the previous cost counts as required by the
* algorithm (taking the minimum of the cost count to the left, up one,
* and diagonally up and to the left of the current cost count being
* calculated). (Note that the arrays aren't really copied anymore, just
* switched...this is clearly much better than cloning an array or doing
* a System.arraycopy() each time through the outer loop.)
*
* Effectively, the difference between the two implementations is this
* one does not cause an out of memory condition when calculating the LD
* over two very large strings.
*/
int n = s.length(); // length of s
int m = t.length(); // length of t
if (n == 0) {
return m;
} else if (m == 0) {
return n;
}
int[] p = new int[n + 1]; // 'previous' cost array, horizontally
int[] d = new int[n + 1]; // cost array, horizontally
int[] _d; // placeholder to assist in swapping p and d
// indexes into strings s and t
int i; // iterates through s
int j; // iterates through t
char tj; // jth character of t
int cost; // cost
for (i = 0; i <= n; ++i) {
p[i] = i;
}
for (j = 1; j <= m; ++j) {
tj = t.charAt(j - 1);
d[0] = j;
for (i = 1; i <= n; ++i) {
cost = s.charAt(i - 1) == tj ? 0 : 1;
// minimum of cell to the left+1, to the top+1, diagonally left
// and up +cost
d[i] = Math.min(Math.min(d[i - 1] + 1, p[i] + 1), p[i - 1]
+ cost);
}
// copy current distance counts to 'previous row' distance counts
_d = p;
p = d;
d = _d;
}
// our last action in the above loop was to switch d and p, so p now
// actually has the most recent cost counts
return p[n];
}
}

View File

@ -0,0 +1,44 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.gson;
import com.google.gson.GsonBuilder;
import com.sk89q.worldedit.Vector;
/**
* Utility methods for Google's GSON library.
*/
public final class GsonUtil {
private GsonUtil() {
}
/**
* Create a standard {@link GsonBuilder} for WorldEdit.
*
* @return a builder
*/
public static GsonBuilder createBuilder() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Vector.class, new VectorAdapter());
return gsonBuilder;
}
}

View File

@ -0,0 +1,49 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.sk89q.worldedit.Vector;
import java.lang.reflect.Type;
/**
* Deserializes {@code Vector}s for GSON.
*/
public class VectorAdapter implements JsonDeserializer<Vector> {
@Override
public Vector deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonArray jsonArray = json.getAsJsonArray();
if (jsonArray.size() != 3) {
throw new JsonParseException("Expected array of 3 length for Vector");
}
double x = jsonArray.get(0).getAsDouble();
double y = jsonArray.get(1).getAsDouble();
double z = jsonArray.get(2).getAsDouble();
return new Vector(x, y, z);
}
}

View File

@ -0,0 +1,233 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.io;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Throwables;
import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.google.common.base.Preconditions.checkNotNull;
public final class Closer implements Closeable {
private static final Logger logger = Logger.getLogger(Closer.class.getCanonicalName());
/**
* The suppressor implementation to use for the current Java version.
*/
private static final Suppressor SUPPRESSOR = SuppressingSuppressor.isAvailable()
? SuppressingSuppressor.INSTANCE
: LoggingSuppressor.INSTANCE;
/**
* Creates a new {@link Closer}.
*/
public static Closer create() {
return new Closer(SUPPRESSOR);
}
@VisibleForTesting
final Suppressor suppressor;
// only need space for 2 elements in most cases, so try to use the smallest array possible
private final Deque<Closeable> stack = new ArrayDeque<Closeable>(4);
private Throwable thrown;
@VisibleForTesting Closer(Suppressor suppressor) {
this.suppressor = checkNotNull(suppressor); // checkNotNull to satisfy null tests
}
/**
* Registers the given {@code closeable} to be closed when this {@code Closer} is
* {@linkplain #close closed}.
*
* @return the given {@code closeable}
*/
// close. this word no longer has any meaning to me.
public <C extends Closeable> C register(C closeable) {
stack.push(closeable);
return closeable;
}
/**
* Stores the given throwable and rethrows it. It will be rethrown as is if it is an
* {@code IOException}, {@code RuntimeException} or {@code Error}. Otherwise, it will be rethrown
* wrapped in a {@code RuntimeException}. <b>Note:</b> Be sure to declare all of the checked
* exception types your try block can throw when calling an overload of this method so as to avoid
* losing the original exception type.
*
* <p>This method always throws, and as such should be called as
* {@code throw closer.rethrow(e);} to ensure the compiler knows that it will throw.
*
* @return this method does not return; it always throws
* @throws IOException when the given throwable is an IOException
*/
public RuntimeException rethrow(Throwable e) throws IOException {
thrown = e;
Throwables.propagateIfPossible(e, IOException.class);
throw Throwables.propagate(e);
}
/**
* Stores the given throwable and rethrows it. It will be rethrown as is if it is an
* {@code IOException}, {@code RuntimeException}, {@code Error} or a checked exception of the
* given type. Otherwise, it will be rethrown wrapped in a {@code RuntimeException}. <b>Note:</b>
* Be sure to declare all of the checked exception types your try block can throw when calling an
* overload of this method so as to avoid losing the original exception type.
*
* <p>This method always throws, and as such should be called as
* {@code throw closer.rethrow(e, ...);} to ensure the compiler knows that it will throw.
*
* @return this method does not return; it always throws
* @throws IOException when the given throwable is an IOException
* @throws X when the given throwable is of the declared type X
*/
public <X extends Exception> RuntimeException rethrow(Throwable e,
Class<X> declaredType) throws IOException, X {
thrown = e;
Throwables.propagateIfPossible(e, IOException.class);
Throwables.propagateIfPossible(e, declaredType);
throw Throwables.propagate(e);
}
/**
* Stores the given throwable and rethrows it. It will be rethrown as is if it is an
* {@code IOException}, {@code RuntimeException}, {@code Error} or a checked exception of either
* of the given types. Otherwise, it will be rethrown wrapped in a {@code RuntimeException}.
* <b>Note:</b> Be sure to declare all of the checked exception types your try block can throw
* when calling an overload of this method so as to avoid losing the original exception type.
*
* <p>This method always throws, and as such should be called as
* {@code throw closer.rethrow(e, ...);} to ensure the compiler knows that it will throw.
*
* @return this method does not return; it always throws
* @throws IOException when the given throwable is an IOException
* @throws X1 when the given throwable is of the declared type X1
* @throws X2 when the given throwable is of the declared type X2
*/
public <X1 extends Exception, X2 extends Exception> RuntimeException rethrow(
Throwable e, Class<X1> declaredType1, Class<X2> declaredType2) throws IOException, X1, X2 {
thrown = e;
Throwables.propagateIfPossible(e, IOException.class);
Throwables.propagateIfPossible(e, declaredType1, declaredType2);
throw Throwables.propagate(e);
}
/**
* Closes all {@code Closeable} instances that have been added to this {@code Closer}. If an
* exception was thrown in the try block and passed to one of the {@code exceptionThrown} methods,
* any exceptions thrown when attempting to close a closeable will be suppressed. Otherwise, the
* <i>first</i> exception to be thrown from an attempt to close a closeable will be thrown and any
* additional exceptions that are thrown after that will be suppressed.
*/
@Override
public void close() throws IOException {
Throwable throwable = thrown;
// close closeables in LIFO order
while (!stack.isEmpty()) {
Closeable closeable = stack.pop();
try {
closeable.close();
} catch (Throwable e) {
if (throwable == null) {
throwable = e;
} else {
suppressor.suppress(closeable, throwable, e);
}
}
}
if (thrown == null && throwable != null) {
Throwables.propagateIfPossible(throwable, IOException.class);
throw new AssertionError(throwable); // not possible
}
}
/**
* Suppression strategy interface.
*/
@VisibleForTesting interface Suppressor {
/**
* Suppresses the given exception ({@code suppressed}) which was thrown when attempting to close
* the given closeable. {@code thrown} is the exception that is actually being thrown from the
* method. Implementations of this method should not throw under any circumstances.
*/
void suppress(Closeable closeable, Throwable thrown, Throwable suppressed);
}
/**
* Suppresses exceptions by logging them.
*/
@VisibleForTesting static final class LoggingSuppressor implements Suppressor {
static final LoggingSuppressor INSTANCE = new LoggingSuppressor();
@Override
public void suppress(Closeable closeable, Throwable thrown, Throwable suppressed) {
// log to the same place as Closeables
logger.log(Level.WARNING, "Suppressing exception thrown when closing " + closeable, suppressed);
}
}
/**
* Suppresses exceptions by adding them to the exception that will be thrown using JDK7's
* addSuppressed(Throwable) mechanism.
*/
@VisibleForTesting static final class SuppressingSuppressor implements Suppressor {
static final SuppressingSuppressor INSTANCE = new SuppressingSuppressor();
static boolean isAvailable() {
return addSuppressed != null;
}
static final Method addSuppressed = getAddSuppressed();
private static Method getAddSuppressed() {
try {
return Throwable.class.getMethod("addSuppressed", Throwable.class);
} catch (Throwable e) {
return null;
}
}
@Override
public void suppress(Closeable closeable, Throwable thrown, Throwable suppressed) {
// ensure no exceptions from addSuppressed
if (thrown == suppressed) {
return;
}
try {
addSuppressed.invoke(thrown, suppressed);
} catch (Throwable e) {
// if, somehow, IllegalAccessException or another exception is thrown, fall back to logging
LoggingSuppressor.INSTANCE.suppress(closeable, thrown, suppressed);
}
}
}
}

View File

@ -0,0 +1,102 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.io;
import java.io.IOException;
import java.io.InputStream;
public class ForwardSeekableInputStream extends InputStream {
protected InputStream parent;
protected long position = 0;
public ForwardSeekableInputStream(InputStream parent) {
this.parent = parent;
}
@Override
public int read() throws IOException {
int ret = parent.read();
++position;
return ret;
}
@Override
public int available() throws IOException {
return parent.available();
}
@Override
public void close() throws IOException {
parent.close();
}
@Override
public synchronized void mark(int readlimit) {
parent.mark(readlimit);
}
@Override
public boolean markSupported() {
return parent.markSupported();
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int read = super.read(b, off, len);
position += read;
return read;
}
@Override
public int read(byte[] b) throws IOException {
int read = parent.read(b);
position += read;
return read;
}
@Override
public synchronized void reset() throws IOException {
parent.reset();
}
@Override
public long skip(long n) throws IOException {
long skipped = parent.skip(n);
position += skipped;
return skipped;
}
public void seek(long n) throws IOException {
long diff = n - position;
if (diff < 0) {
throw new IOException("Can't seek backwards");
}
if (diff == 0) {
return;
}
if (skip(diff) < diff) {
throw new IOException("Failed to seek " + diff + " bytes");
}
}
}

View File

@ -0,0 +1,32 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.io.file;
public class FileSelectionAbortedException extends FilenameException {
public FileSelectionAbortedException() {
super("");
}
public FileSelectionAbortedException(String msg) {
super("", msg);
}
}

View File

@ -0,0 +1,42 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.io.file;
import com.sk89q.worldedit.WorldEditException;
public class FilenameException extends WorldEditException {
private String filename;
public FilenameException(String filename) {
super();
this.filename = filename;
}
public FilenameException(String filename, String msg) {
super(msg);
this.filename = filename;
}
public String getFilename() {
return filename;
}
}

View File

@ -0,0 +1,32 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.io.file;
public class FilenameResolutionException extends FilenameException {
public FilenameResolutionException(String filename) {
super(filename);
}
public FilenameResolutionException(String filename, String msg) {
super(filename, msg);
}
}

View File

@ -0,0 +1,32 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.io.file;
public class InvalidFilenameException extends FilenameException {
public InvalidFilenameException(String filename) {
super(filename);
}
public InvalidFilenameException(String filename, String msg) {
super(filename, msg);
}
}

View File

@ -0,0 +1,179 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.logging;
import javax.annotation.Nullable;
import java.io.UnsupportedEncodingException;
import java.util.logging.*;
/**
* A {@link StreamHandler} delegate that allows for the swap and disable of
* another handler. When {@link #setHandler(StreamHandler)} is called with
* null, then records passed onto this handler will be dropped. Otherwise,
* the delegate handler will receive those records.
*/
public class DynamicStreamHandler extends StreamHandler {
private @Nullable StreamHandler handler;
private @Nullable Formatter formatter;
private @Nullable Filter filter;
private @Nullable String encoding;
private Level level = Level.ALL;
/**
* Get the delegate handler.
*
* @return the delegate handler (Which may be null)
*/
public @Nullable synchronized StreamHandler getHandler() {
return handler;
}
/**
* Set the handler.
*
* @param handler the delegate handler (which can be null)
*/
public synchronized void setHandler(@Nullable StreamHandler handler) {
if (this.handler != null) {
this.handler.close();
}
this.handler = handler;
if (handler != null) {
handler.setFormatter(formatter);
handler.setFilter(filter);
try {
handler.setEncoding(encoding);
} catch (UnsupportedEncodingException ignore) {
}
handler.setLevel(level);
}
}
@Override
public synchronized void publish(LogRecord record) {
if (handler != null) {
handler.publish(record);
}
}
@Override
public synchronized void close() throws SecurityException {
if (handler != null) {
handler.close();
}
}
@Override
public void setEncoding(@Nullable String encoding) throws SecurityException, UnsupportedEncodingException {
StreamHandler handler = this.handler;
this.encoding = encoding;
if (handler != null) {
handler.setEncoding(encoding);
}
}
@Override
public boolean isLoggable(LogRecord record) {
StreamHandler handler = this.handler;
return handler != null && handler.isLoggable(record);
}
@Override
public synchronized void flush() {
StreamHandler handler = this.handler;
if (handler != null) {
handler.flush();
}
}
@Override
public void setFormatter(@Nullable Formatter newFormatter) throws SecurityException {
StreamHandler handler = this.handler;
this.formatter = newFormatter;
if (handler != null) {
handler.setFormatter(newFormatter);
}
}
@Override
public Formatter getFormatter() {
StreamHandler handler = this.handler;
Formatter formatter = this.formatter;
if (handler != null) {
return handler.getFormatter();
} else if (formatter != null) {
return formatter;
} else {
return new SimpleFormatter();
}
}
@Override
public String getEncoding() {
StreamHandler handler = this.handler;
String encoding = this.encoding;
if (handler != null) {
return handler.getEncoding();
} else {
return encoding;
}
}
@Override
public void setFilter(@Nullable Filter newFilter) throws SecurityException {
StreamHandler handler = this.handler;
this.filter = newFilter;
if (handler != null) {
handler.setFilter(newFilter);
}
}
@Override
public Filter getFilter() {
StreamHandler handler = this.handler;
Filter filter = this.filter;
if (handler != null) {
return handler.getFilter();
} else {
return filter;
}
}
@Override
public synchronized void setLevel(Level newLevel) throws SecurityException {
if (handler != null) {
handler.setLevel(newLevel);
}
this.level = newLevel;
}
@Override
public synchronized Level getLevel() {
if (handler != null) {
return handler.getLevel();
} else {
return level;
}
}
}

View File

@ -0,0 +1,63 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.logging;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
import java.util.logging.Level;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* A standard logging format for WorldEdit.
*/
public class LogFormat extends Formatter {
@Override
public String format(LogRecord record) {
StringBuilder text = new StringBuilder();
Level level = record.getLevel();
if (level == Level.FINEST) {
text.append("[FINEST] ");
} else if (level == Level.FINER) {
text.append("[FINER] ");
} else if (level == Level.FINE) {
text.append("[FINE] ");
} else if (level == Level.INFO) {
text.append("[INFO] ");
} else if (level == Level.WARNING) {
text.append("[WARNING] ");
} else if (level == Level.SEVERE) {
text.append("[SEVERE] ");
}
text.append(record.getMessage());
text.append("\r\n");
Throwable t = record.getThrown();
if (t != null) {
StringWriter writer = new StringWriter();
t.printStackTrace(new PrintWriter(writer));
text.append(writer);
}
return text.toString();
}
}

View File

@ -0,0 +1,59 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.logging;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
/**
* Adds a WorldEdit prefix to WorldEdit's logger messages using a handler.
*/
public final class WorldEditPrefixHandler extends Handler {
private WorldEditPrefixHandler() {
}
@Override
public void publish(LogRecord record) {
String message = record.getMessage();
if (!message.startsWith("WorldEdit: ") && !message.startsWith("[WorldEdit] ")) {
record.setMessage("[WorldEdit] " + message);
}
}
@Override
public void flush() {
}
@Override
public void close() throws SecurityException {
}
/**
* Add the handler to the following logger name.
*
* @param name the logger name
*/
public static void register(String name) {
Logger.getLogger(name).addHandler(new WorldEditPrefixHandler());
}
}