Major command changes that don't work yet.

This commit is contained in:
MattBDev
2019-07-05 20:46:48 -04:00
parent ffc2092d93
commit 8108d0a936
399 changed files with 13558 additions and 7985 deletions

View File

@ -19,7 +19,8 @@
package com.sk89q.worldedit.internal.annotation;
import com.sk89q.worldedit.math.Vector3;
import com.sk89q.worldedit.math.BlockVector3;
import org.enginehub.piston.inject.InjectAnnotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@ -27,12 +28,13 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotates a {@link Vector3} parameter to inject a direction.
* Annotates a {@link BlockVector3} parameter to inject a direction.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@InjectAnnotation
public @interface Direction {
String AIM = "me";
boolean includeDiagonals() default false;

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.internal.annotation;
import org.enginehub.piston.inject.InjectAnnotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotates a {@code List<BlockVector3>} parameter to inject multiple direction.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@InjectAnnotation
public @interface MultiDirection {
boolean includeDiagonals() default false;
}

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.internal.annotation;
import org.enginehub.piston.inject.InjectAnnotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotates a {@code double} parameter to inject multiple radii values.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@InjectAnnotation
public @interface Radii {
/**
* Number of radii values to inject at maximum. May inject less.
*/
int value();
}

View File

@ -19,6 +19,8 @@
package com.sk89q.worldedit.internal.annotation;
import org.enginehub.piston.inject.InjectAnnotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@ -29,6 +31,6 @@ import java.lang.annotation.Target;
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@InjectAnnotation
public @interface Selection {
}

View File

@ -0,0 +1,346 @@
/*
* 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.internal.anvil;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.sk89q.worldedit.math.BlockVector2;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class ChunkDeleter {
public static final String DELCHUNKS_FILE_NAME = "delete_chunks.json";
private static final Logger logger = LoggerFactory.getLogger(ChunkDeleter.class);
private static final Comparator<BlockVector2> chunkSorter = Comparator.comparing(
pos -> (pos.getBlockX() & 31) + (pos.getBlockZ() & 31) * 32);
private static Gson chunkDeleterGson = new GsonBuilder()
.registerTypeAdapter(BlockVector2.class, new BlockVector2Adapter())
.setPrettyPrinting()
.create();
public static ChunkDeletionInfo readInfo(Path chunkFile) throws IOException, JsonSyntaxException {
String json = new String(Files.readAllBytes(chunkFile), StandardCharsets.UTF_8);
return chunkDeleterGson.fromJson(json, ChunkDeletionInfo.class);
}
public static void writeInfo(ChunkDeletionInfo info, Path chunkFile) throws IOException, JsonIOException {
String json = chunkDeleterGson.toJson(info, new TypeToken<ChunkDeletionInfo>() {}.getType());
try (BufferedWriter writer = Files.newBufferedWriter(chunkFile)) {
writer.write(json);
}
}
public static void runFromFile(Path chunkFile, boolean deleteOnSuccess) {
ChunkDeleter chunkDeleter;
try {
chunkDeleter = createFromFile(chunkFile);
} catch (JsonSyntaxException | IOException e) {
logger.error("Could not parse chunk deletion file. Invalid file?", e);
return;
}
logger.info("Found chunk deletions. Proceeding with deletion...");
long start = System.currentTimeMillis();
if (chunkDeleter.runDeleter()) {
logger.info("Successfully deleted {} matching chunks (out of {}, taking {} ms).",
chunkDeleter.getDeletedChunkCount(), chunkDeleter.getDeletionsRequested(),
System.currentTimeMillis() - start);
if (deleteOnSuccess) {
boolean deletedFile = false;
try {
deletedFile = Files.deleteIfExists(chunkFile);
} catch (IOException ignored) {
}
if (!deletedFile) {
logger.warn("Chunk deletion file could not be cleaned up. This may have unintended consequences" +
" on next startup, or if /delchunks is used again.");
}
}
} else {
logger.error("Error occurred while deleting chunks. " +
"If world errors occur, stop the server and restore the *.bak backup files.");
}
}
private ChunkDeleter(ChunkDeletionInfo chunkDeletionInfo) {
this.chunkDeletionInfo = chunkDeletionInfo;
}
private static ChunkDeleter createFromFile(Path chunkFile) throws IOException {
ChunkDeletionInfo info = readInfo(chunkFile);
if (info == null) {
throw new IOException("Read null json. Empty file?");
}
return new ChunkDeleter(info);
}
private final ChunkDeletionInfo chunkDeletionInfo;
private Set<Path> backedUpRegions = new HashSet<>();
private boolean shouldPreload;
private int debugRate = 100;
private int totalChunksDeleted = 0;
private int deletionsRequested = 0;
private boolean runDeleter() {
return chunkDeletionInfo.batches.stream().allMatch(this::runBatch);
}
private boolean runBatch(ChunkDeletionInfo.ChunkBatch chunkBatch) {
int chunkCount = chunkBatch.getChunkCount();
logger.debug("Processing deletion batch with {} chunks.", chunkCount);
final Map<Path, Stream<BlockVector2>> regionToChunkList = groupChunks(chunkBatch);
BiPredicate<RegionAccess, BlockVector2> predicate = createPredicates(chunkBatch.deletionPredicates);
shouldPreload = chunkBatch.chunks == null;
deletionsRequested += chunkCount;
debugRate = chunkCount / 10;
return regionToChunkList.entrySet().stream().allMatch(entry -> {
Path regionPath = entry.getKey();
if (!Files.exists(regionPath)) return true;
if (chunkBatch.backup && !backedUpRegions.contains(regionPath)) {
try {
backupRegion(regionPath);
} catch (IOException e) {
logger.warn("Error backing up region file: " + regionPath + ". Aborting the process.", e);
return false;
}
}
return deleteChunks(regionPath, entry.getValue(), predicate);
});
}
private Map<Path, Stream<BlockVector2>> groupChunks(ChunkDeletionInfo.ChunkBatch chunkBatch) {
Path worldPath = Paths.get(chunkBatch.worldPath);
if (chunkBatch.chunks != null) {
return chunkBatch.chunks.stream()
.collect(Collectors.groupingBy(RegionFilePos::new))
.entrySet().stream().collect(Collectors.toMap(
e -> worldPath.resolve("region").resolve(e.getKey().getFileName()),
e -> e.getValue().stream().sorted(chunkSorter)));
} else {
final BlockVector2 minChunk = chunkBatch.minChunk;
final BlockVector2 maxChunk = chunkBatch.maxChunk;
final RegionFilePos minRegion = new RegionFilePos(minChunk);
final RegionFilePos maxRegion = new RegionFilePos(maxChunk);
Map<Path, Stream<BlockVector2>> groupedChunks = new HashMap<>();
for (int regX = minRegion.getX(); regX <= maxRegion.getX(); regX++) {
for (int regZ = minRegion.getZ(); regZ <= maxRegion.getZ(); regZ++) {
final Path regionPath = worldPath.resolve("region").resolve(new RegionFilePos(regX, regZ).getFileName());
if (!Files.exists(regionPath)) continue;
int startX = regX << 5;
int endX = (regX << 5) + 31;
int startZ = regZ << 5;
int endZ = (regZ << 5) + 31;
int minX = Math.max(Math.min(startX, endX), minChunk.getBlockX());
int minZ = Math.max(Math.min(startZ, endZ), minChunk.getBlockZ());
int maxX = Math.min(Math.max(startX, endX), maxChunk.getBlockX());
int maxZ = Math.min(Math.max(startZ, endZ), maxChunk.getBlockZ());
Stream<BlockVector2> stream = Stream.iterate(BlockVector2.at(minX, minZ),
bv2 -> {
int nextX = bv2.getBlockX();
int nextZ = bv2.getBlockZ();
if (++nextX > maxX) {
nextX = minX;
if (++nextZ > maxZ) {
return null;
}
}
return BlockVector2.at(nextX, nextZ);
});
groupedChunks.put(regionPath, stream);
}
}
return groupedChunks;
}
}
private BiPredicate<RegionAccess, BlockVector2> createPredicates(List<ChunkDeletionInfo.DeletionPredicate> deletionPredicates) {
if (deletionPredicates == null) return (r, p) -> true;
return deletionPredicates.stream()
.map(this::createPredicate)
.reduce(BiPredicate::and)
.orElse((r, p) -> true);
}
private BiPredicate<RegionAccess, BlockVector2> createPredicate(ChunkDeletionInfo.DeletionPredicate deletionPredicate) {
if ("modification".equals(deletionPredicate.property)) {
int time;
try {
time = Integer.parseInt(deletionPredicate.value);
} catch (NumberFormatException e) {
throw new IllegalStateException("Modification time predicate specified invalid time: " + deletionPredicate.value);
}
switch (deletionPredicate.comparison) {
case "<":
return (r, p) -> {
try {
return r.getModificationTime(p) < time;
} catch (IOException e) {
return false;
}
};
case ">":
return (r, p) -> {
try {
return r.getModificationTime(p) > time;
} catch (IOException e) {
return false;
}
};
default:
throw new IllegalStateException("Unexpected comparison value: " + deletionPredicate.comparison);
}
}
throw new IllegalStateException("Unexpected property value: " + deletionPredicate.property);
}
private void backupRegion(Path regionFile) throws IOException {
Path backupFile = regionFile.resolveSibling(regionFile.getFileName() + ".bak");
Files.copy(regionFile, backupFile, StandardCopyOption.REPLACE_EXISTING);
backedUpRegions.add(backupFile);
}
private boolean deleteChunks(Path regionFile, Stream<BlockVector2> chunks,
BiPredicate<RegionAccess, BlockVector2> deletionPredicate) {
try (RegionAccess region = new RegionAccess(regionFile, shouldPreload)) {
for (Iterator<BlockVector2> iterator = chunks.iterator(); iterator.hasNext();) {
BlockVector2 chunk = iterator.next();
if (chunk == null) break;
if (deletionPredicate.test(region, chunk)) {
region.deleteChunk(chunk);
totalChunksDeleted++;
if (debugRate != 0 && totalChunksDeleted % debugRate == 0) {
logger.debug("Deleted {} chunks so far.", totalChunksDeleted);
}
} else {
logger.debug("Chunk did not match predicates: " + chunk);
}
}
return true;
} catch (IOException e) {
logger.warn("Error deleting chunks from region: " + regionFile + ". Aborting the process.", e);
return false;
}
}
public int getDeletedChunkCount() {
return totalChunksDeleted;
}
public int getDeletionsRequested() {
return deletionsRequested;
}
private static class BlockVector2Adapter extends TypeAdapter<BlockVector2> {
@Override
public void write(JsonWriter out, BlockVector2 value) throws IOException {
out.beginArray();
out.value(value.getBlockX());
out.value(value.getBlockZ());
out.endArray();
}
@Override
public BlockVector2 read(JsonReader in) throws IOException {
in.beginArray();
int x = in.nextInt();
int z = in.nextInt();
in.endArray();
return BlockVector2.at(x, z);
}
}
private static class RegionFilePos {
private final int x;
private final int z;
RegionFilePos(BlockVector2 chunk) {
this.x = chunk.getBlockX() >> 5;
this.z = chunk.getBlockZ() >> 5;
}
RegionFilePos(int regX, int regZ) {
this.x = regX;
this.z = regZ;
}
public int getX() {
return x;
}
public int getZ() {
return z;
}
public String getFileName() {
return "r." + x + "." + z + ".mca";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RegionFilePos that = (RegionFilePos) o;
if (x != that.x) return false;
return z == that.z;
}
@Override
public int hashCode() {
int result = x;
result = 31 * result + z;
return result;
}
@Override
public String toString() {
return "(" + x + ", " + z + ")";
}
}
}

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.internal.anvil;
import com.sk89q.worldedit.math.BlockVector2;
import java.util.List;
/**
* Internal class. Subject to changes.
*/
public class ChunkDeletionInfo {
public List<ChunkBatch> batches;
public static class ChunkBatch {
public String worldPath;
public boolean backup;
public List<DeletionPredicate> deletionPredicates;
// specify either list of chunks, or min-max
public List<BlockVector2> chunks;
public BlockVector2 minChunk;
public BlockVector2 maxChunk;
public int getChunkCount() {
if (chunks != null) return chunks.size();
final BlockVector2 dist = maxChunk.subtract(minChunk).add(1, 1);
return dist.getBlockX() * dist.getBlockZ();
}
}
public static class DeletionPredicate {
public String property;
public String comparison;
public String value;
}
}

View File

@ -0,0 +1,101 @@
/*
* 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.internal.anvil;
import com.sk89q.worldedit.math.BlockVector2;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Path;
/**
* Internal class. Subject to changes.
*/
class RegionAccess implements AutoCloseable {
private RandomAccessFile raf;
private int[] offsets;
private int[] timestamps;
RegionAccess(Path file) throws IOException {
this(file, false);
}
RegionAccess(Path file, boolean preload) throws IOException {
raf = new RandomAccessFile(file.toFile(), "rw");
if (preload) {
readHeaders();
}
}
private void readHeaders() throws IOException {
offsets = new int[1024];
timestamps = new int[1024];
for (int idx = 0; idx < 1024; ++idx) {
offsets[idx] = raf.readInt();
}
for (int idx = 0; idx < 1024; ++idx) {
timestamps[idx] = raf.readInt();
}
}
private static int indexChunk(BlockVector2 pos) {
int x = pos.getBlockX() & 31;
int z = pos.getBlockZ() & 31;
return x + z * 32;
}
int getModificationTime(BlockVector2 pos) throws IOException {
int idx = indexChunk(pos);
if (timestamps != null) {
return timestamps[idx];
}
raf.seek(idx * 4L + 4096);
return raf.readInt();
}
int getChunkSize(BlockVector2 pos) throws IOException {
int idx = indexChunk(pos);
if (offsets != null) {
return offsets[idx] & 0xFF;
}
raf.seek(idx * 4L);
// 3 bytes for offset
raf.read();
raf.read();
raf.read();
// one byte for size - note, yes, could do raf.readInt() & 0xFF but that does extra checks
return raf.read();
}
void deleteChunk(BlockVector2 pos) throws IOException {
int idx = indexChunk(pos);
raf.seek(idx * 4L);
raf.writeInt(0);
if (offsets != null) {
offsets[idx] = 0;
}
}
@Override
public void close() throws IOException {
raf.close();
}
}

View File

@ -0,0 +1,112 @@
/*
* 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.internal.command;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.sk89q.worldedit.internal.util.Substring;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class CommandArgParser {
public static ImmutableList<Substring> spaceSplit(String string) {
ImmutableList.Builder<Substring> result = ImmutableList.builder();
int index = 0;
for (String part : Splitter.on(' ').split(string)) {
result.add(Substring.from(string, index, index + part.length()));
index += part.length() + 1;
}
return result.build();
}
private enum State {
NORMAL,
QUOTE
}
private final Stream.Builder<Substring> args = Stream.builder();
private final List<Substring> input;
private final List<Substring> currentArg = new ArrayList<>();
private int index = 0;
private State state = State.NORMAL;
public CommandArgParser(List<Substring> input) {
this.input = input;
}
public Stream<Substring> parseArgs() {
for (; index < input.size(); index++) {
Substring nextPart = input.get(index);
switch (state) {
case NORMAL:
handleNormal(nextPart);
break;
case QUOTE:
handleQuote(nextPart);
}
}
return args.build();
}
private void handleNormal(Substring part) {
if (part.getSubstring().startsWith("\"")) {
state = State.QUOTE;
currentArg.add(Substring.wrap(
part.getSubstring().substring(1),
part.getStart(), part.getEnd()
));
} else {
currentArg.add(part);
finishArg();
}
}
private void handleQuote(Substring part) {
if (part.getSubstring().endsWith("\"")) {
state = State.NORMAL;
currentArg.add(Substring.wrap(
part.getSubstring().substring(0, part.getSubstring().length() - 1),
part.getStart(), part.getEnd()
));
finishArg();
} else {
currentArg.add(part);
}
}
private void finishArg() {
// Merge the arguments into a single, space-joined, string
// Keep the original start + end points.
int start = currentArg.get(0).getStart();
int end = Iterables.getLast(currentArg).getEnd();
args.add(Substring.wrap(currentArg.stream()
.map(Substring::getSubstring)
.collect(Collectors.joining(" ")),
start, end
));
currentArg.clear();
}
}

View File

@ -19,31 +19,29 @@
package com.sk89q.worldedit.internal.command;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.minecraft.util.commands.Logging;
import static com.google.common.base.Preconditions.checkNotNull;
import com.sk89q.worldedit.IncompleteRegionException;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.command.util.Logging;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.math.Vector3;
import com.sk89q.worldedit.util.command.parametric.AbstractInvokeListener;
import com.sk89q.worldedit.util.command.parametric.InvokeHandler;
import com.sk89q.worldedit.util.command.parametric.ParameterData;
import com.sk89q.worldedit.util.command.parametric.ParameterException;
import org.enginehub.piston.CommandParameters;
import org.enginehub.piston.gen.CommandCallListener;
import org.enginehub.piston.inject.Key;
import java.io.Closeable;
import java.lang.reflect.Method;
import java.util.Optional;
import java.util.logging.Handler;
import java.util.logging.Logger;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Logs called commands to a logger.
*/
public class CommandLoggingHandler extends AbstractInvokeListener implements InvokeHandler, Closeable {
public class CommandLoggingHandler implements CommandCallListener, AutoCloseable {
private final WorldEdit worldEdit;
private final Logger logger;
@ -62,11 +60,7 @@ public class CommandLoggingHandler extends AbstractInvokeListener implements Inv
}
@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 CommandException {
public void beforeCall(Method method, CommandParameters parameters) {
Logging loggingAnnotation = method.getAnnotation(Logging.class);
Logging.LogMode logMode;
StringBuilder builder = new StringBuilder();
@ -77,78 +71,66 @@ public class CommandLoggingHandler extends AbstractInvokeListener implements Inv
logMode = loggingAnnotation.value();
}
Actor sender = context.getLocals().get(Actor.class);
Player player;
Optional<Player> playerOpt = parameters.injectedValue(Key.of(Actor.class))
.filter(Player.class::isInstance)
.map(Player.class::cast);
if (sender == null) {
if (!playerOpt.isPresent()) {
return;
}
if (sender instanceof Player) {
player = (Player) sender;
} else {
return;
}
Player player = playerOpt.get();
builder.append("WorldEdit: ").append(sender.getName());
if (sender.isPlayer()) {
builder.append(" (in \"").append(player.getWorld().getName()).append("\")");
}
builder.append("WorldEdit: ").append(player.getName());
builder.append(" (in \"").append(player.getWorld().getName()).append("\")");
builder.append(": ").append(context.getCommand());
builder.append(": ").append(parameters.getMetadata().getCalledName());
if (context.argsLength() > 0) {
builder.append(" ").append(context.getJoinedStrings(0));
}
builder.append(": ")
.append(Stream.concat(
Stream.of(parameters.getMetadata().getCalledName()),
parameters.getMetadata().getArguments().stream()
).collect(Collectors.joining(" ")));
if (logMode != null && sender.isPlayer()) {
if (logMode != null) {
Vector3 position = player.getLocation().toVector();
LocalSession session = worldEdit.getSessionManager().get(player);
switch (logMode) {
case PLACEMENT:
try {
position = session.getPlacementPosition(player).toVector3();
} catch (IncompleteRegionException e) {
case PLACEMENT:
try {
position = session.getPlacementPosition(player).toVector3();
} catch (IncompleteRegionException e) {
break;
}
/* FALL-THROUGH */
case POSITION:
builder.append(" - Position: ").append(position);
break;
}
/* FALL-THROUGH */
case POSITION:
builder.append(" - Position: ").append(position);
break;
case ALL:
builder.append(" - Position: ").append(position);
/* FALL-THROUGH */
case ALL:
builder.append(" - Position: ").append(position);
/* FALL-THROUGH */
case ORIENTATION_REGION:
builder.append(" - Orientation: ").append(player.getCardinalDirection().name());
/* FALL-THROUGH */
case ORIENTATION_REGION:
builder.append(" - Orientation: ").append(player.getCardinalDirection().name());
/* FALL-THROUGH */
case REGION:
try {
builder.append(" - Region: ")
case REGION:
try {
builder.append(" - Region: ")
.append(session.getSelection(player.getWorld()));
} catch (IncompleteRegionException e) {
} catch (IncompleteRegionException e) {
break;
}
break;
}
break;
}
}
logger.info(builder.toString());
}
@Override
public void postInvoke(Object object, Method method, ParameterData[] parameters, Object[] args, CommandContext context) throws CommandException {
}
@Override
public InvokeHandler createInvokeHandler() {
return this;
}
@Override
public void close() {
for (Handler h : logger.getHandlers()) {

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.internal.command;
import com.google.common.collect.ImmutableList;
import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator;
import org.enginehub.piston.CommandManager;
import org.enginehub.piston.gen.CommandCallListener;
import org.enginehub.piston.gen.CommandRegistration;
import java.util.List;
public class CommandRegistrationHandler {
private static final CommandPermissionsConditionGenerator PERM_GEN = new CommandPermissionsConditionGenerator();
private final List<CommandCallListener> callListeners;
public CommandRegistrationHandler(List<CommandCallListener> callListeners) {
this.callListeners = ImmutableList.copyOf(callListeners);
}
public <CI> void register(CommandManager manager, CommandRegistration<CI> registration, CI instance) {
registration.containerInstance(instance)
.commandManager(manager)
.listeners(callListeners);
if (registration instanceof CommandPermissionsConditionGenerator.Registration) {
((CommandPermissionsConditionGenerator.Registration) registration).commandPermissionsConditionGenerator(
PERM_GEN
);
}
registration.build();
}
}

View File

@ -0,0 +1,132 @@
/*
* 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.internal.command;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.sk89q.worldedit.extension.platform.PlatformCommandManager;
import com.sk89q.worldedit.internal.util.Substring;
import com.sk89q.worldedit.util.formatting.text.Component;
import com.sk89q.worldedit.util.formatting.text.TextComponent;
import static java.util.stream.Collectors.toList;
import org.enginehub.piston.Command;
import org.enginehub.piston.exception.CommandException;
import org.enginehub.piston.inject.InjectedValueAccess;
import org.enginehub.piston.inject.Key;
import org.enginehub.piston.part.SubCommandPart;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
public class CommandUtil {
public static Map<String, Command> getSubCommands(Command currentCommand) {
return currentCommand.getParts().stream()
.filter(p -> p instanceof SubCommandPart)
.flatMap(p -> ((SubCommandPart) p).getCommands().stream())
.collect(Collectors.toMap(Command::getName, Function.identity()));
}
private static String clean(String input) {
return PlatformCommandManager.COMMAND_CLEAN_PATTERN.matcher(input).replaceAll("");
}
private static final Comparator<Command> BY_CLEAN_NAME =
Comparator.comparing(c -> clean(c.getName()));
public static Comparator<Command> byCleanName() {
return BY_CLEAN_NAME;
}
/**
* Fix {@code suggestions} to replace the last space-separated word in {@code arguments}.
*/
public static List<String> fixSuggestions(String arguments, List<Substring> suggestions) {
Substring lastArg = Iterables.getLast(CommandArgParser.spaceSplit(arguments));
return suggestions.stream()
.map(suggestion -> CommandUtil.suggestLast(lastArg, suggestion))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(toList());
}
/**
* Given the last word of a command, mutate the suggestion to replace the last word, if
* possible.
*/
private static Optional<String> suggestLast(Substring last, Substring suggestion) {
if (suggestion.getStart() == last.getEnd()) {
// this suggestion is for the next argument.
if (last.getSubstring().isEmpty()) {
return Optional.of(suggestion.getSubstring());
}
return Optional.of(last.getSubstring() + " " + suggestion.getSubstring());
}
StringBuilder builder = new StringBuilder(last.getSubstring());
int start = suggestion.getStart() - last.getStart();
int end = suggestion.getEnd() - last.getStart();
if (start < 0) {
// Quoted suggestion, can't complete it here.
return Optional.empty();
}
checkState(end <= builder.length(),
"Suggestion ends too late, last=%s, suggestion=", last, suggestion);
builder.replace(start, end, suggestion.getSubstring());
return Optional.of(builder.toString());
}
/**
* Require {@code condition} to be {@code true}, otherwise throw a {@link CommandException}
* with the given message.
*
* @param condition the condition to check
* @param message the message for failure
*/
public static void checkCommandArgument(boolean condition, String message) {
checkCommandArgument(condition, TextComponent.of(message));
}
/**
* Require {@code condition} to be {@code true}, otherwise throw a {@link CommandException}
* with the given message.
*
* @param condition the condition to check
* @param message the message for failure
*/
public static void checkCommandArgument(boolean condition, Component message) {
if (!condition) {
throw new CommandException(message, ImmutableList.of());
}
}
public static <T> T requireIV(Key<T> type, String name, InjectedValueAccess injectedValueAccess) {
return injectedValueAccess.injectedValue(type).orElseThrow(() ->
new IllegalStateException("No injected value for " + name + " (type " + type + ")")
);
}
private CommandUtil() {
}
}

View File

@ -357,7 +357,7 @@ public class WorldEditBinding {
String input = context.next();
if (input != null) {
if (MathMan.isInteger(input)) return BiomeTypes.get(Integer.parseInt(input));
if (MathMan.isInteger(input)) return BiomeTypes.register(Integer.parseInt(input));
BiomeRegistry biomeRegistry = WorldEdit.getInstance().getPlatformManager()
.queryCapability(Capability.GAME_HOOKS).getRegistries().getBiomeRegistry();

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.internal.command.exception;
import org.enginehub.piston.exception.CommandException;
import org.enginehub.piston.exception.CommandExecutionException;
/**
* Used to convert a recognized {@link Throwable} into an appropriate
* {@link CommandException}.
*
* <p>Methods 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 CommandExecutionException} 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,113 @@
/*
* 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.internal.command.exception;
import com.google.common.collect.ImmutableList;
import org.enginehub.piston.exception.CommandException;
import org.enginehub.piston.exception.CommandExecutionException;
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<>();
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();
}
if (e.getCause() instanceof com.sk89q.minecraft.util.commands.CommandException) {
throw new CommandException(e.getCause(), ImmutableList.of());
}
throw new CommandExecutionException(e, ImmutableList.of());
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new CommandExecutionException(e, ImmutableList.of());
}
}
}
}
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 if (o.cls.isAssignableFrom(cls)) {
return -1;
} else {
return cls.getCanonicalName().compareTo(o.cls.getCanonicalName());
}
}
}
}

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.internal.command.exception;
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

@ -17,11 +17,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.internal.command;
package com.sk89q.worldedit.internal.command.exception;
import static com.google.common.base.Preconditions.checkNotNull;
import com.sk89q.minecraft.util.commands.CommandException;
import com.google.common.collect.ImmutableList;
import com.sk89q.worldedit.DisallowedItemException;
import com.sk89q.worldedit.EmptyClipboardException;
import com.sk89q.worldedit.IncompleteRegionException;
@ -37,11 +36,12 @@ import com.sk89q.worldedit.command.InsufficientArgumentsException;
import com.sk89q.worldedit.command.tool.InvalidToolBindException;
import com.sk89q.worldedit.internal.expression.ExpressionException;
import com.sk89q.worldedit.regions.RegionOperationException;
import com.sk89q.worldedit.util.command.parametric.ExceptionConverterHelper;
import com.sk89q.worldedit.util.command.parametric.ExceptionMatch;
import com.sk89q.worldedit.util.formatting.text.TextComponent;
import com.sk89q.worldedit.util.io.file.FileSelectionAbortedException;
import com.sk89q.worldedit.util.io.file.FilenameResolutionException;
import com.sk89q.worldedit.util.io.file.InvalidFilenameException;
import org.enginehub.piston.exception.CommandException;
import org.enginehub.piston.exception.UsageException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -59,105 +59,115 @@ public class WorldEditExceptionConverter extends ExceptionConverterHelper {
this.worldEdit = worldEdit;
}
private CommandException newCommandException(String message, Throwable cause) {
return new CommandException(TextComponent.of(String.valueOf(message)), cause, ImmutableList.of());
}
@ExceptionMatch
public void convert(NumberFormatException e) throws CommandException {
final Matcher matcher = numberFormat.matcher(e.getMessage());
if (matcher.matches()) {
throw new CommandException("Number expected; string \"" + matcher.group(1)
+ "\" given.");
throw newCommandException("Number expected; string \"" + matcher.group(1)
+ "\" given.", e);
} else {
throw new CommandException("Number expected; string given.");
throw newCommandException("Number expected; string given.", e);
}
}
@ExceptionMatch
public void convert(IncompleteRegionException e) throws CommandException {
throw new CommandException("Make a region selection first.");
throw newCommandException("Make a region selection first.", e);
}
@ExceptionMatch
public void convert(UnknownItemException e) throws CommandException {
throw new CommandException("Block name '" + e.getID() + "' was not recognized.");
throw newCommandException("Block name '" + e.getID() + "' was not recognized.", e);
}
@ExceptionMatch
public void convert(InvalidItemException e) throws CommandException {
throw new CommandException(e.getMessage());
throw newCommandException(e.getMessage(), e);
}
@ExceptionMatch
public void convert(DisallowedItemException e) throws CommandException {
throw new CommandException("Block '" + e.getID()
+ "' not allowed (see WorldEdit configuration).");
throw newCommandException("Block '" + e.getID()
+ "' not allowed (see WorldEdit configuration).", e);
}
@ExceptionMatch
public void convert(MaxChangedBlocksException e) throws CommandException {
throw new CommandException("Max blocks changed in an operation reached ("
+ e.getBlockLimit() + ").");
throw newCommandException("Max blocks changed in an operation reached ("
+ e.getBlockLimit() + ").", e);
}
@ExceptionMatch
public void convert(MaxBrushRadiusException e) throws CommandException {
throw new CommandException("Maximum brush radius (in configuration): " + worldEdit.getConfiguration().maxBrushRadius);
throw newCommandException("Maximum brush radius (in configuration): " + worldEdit.getConfiguration().maxBrushRadius, e);
}
@ExceptionMatch
public void convert(MaxRadiusException e) throws CommandException {
throw new CommandException("Maximum radius (in configuration): " + worldEdit.getConfiguration().maxRadius);
throw newCommandException("Maximum radius (in configuration): " + worldEdit.getConfiguration().maxRadius, e);
}
@ExceptionMatch
public void convert(UnknownDirectionException e) throws CommandException {
throw new CommandException("Unknown direction: " + e.getDirection());
throw newCommandException("Unknown direction: " + e.getDirection(), e);
}
@ExceptionMatch
public void convert(InsufficientArgumentsException e) throws CommandException {
throw new CommandException(e.getMessage());
throw newCommandException(e.getMessage(), e);
}
@ExceptionMatch
public void convert(RegionOperationException e) throws CommandException {
throw new CommandException(e.getMessage());
throw newCommandException(e.getMessage(), e);
}
@ExceptionMatch
public void convert(ExpressionException e) throws CommandException {
throw new CommandException(e.getMessage());
throw newCommandException(e.getMessage(), e);
}
@ExceptionMatch
public void convert(EmptyClipboardException e) throws CommandException {
throw new CommandException("Your clipboard is empty. Use //copy first.");
throw newCommandException("Your clipboard is empty. Use //copy first.", e);
}
@ExceptionMatch
public void convert(InvalidFilenameException e) throws CommandException {
throw new CommandException("Filename '" + e.getFilename() + "' invalid: "
+ e.getMessage());
throw newCommandException("Filename '" + e.getFilename() + "' invalid: "
+ e.getMessage(), e);
}
@ExceptionMatch
public void convert(FilenameResolutionException e) throws CommandException {
throw new CommandException(
"File '" + e.getFilename() + "' resolution error: " + e.getMessage());
throw newCommandException(
"File '" + e.getFilename() + "' resolution error: " + e.getMessage(), e);
}
@ExceptionMatch
public void convert(InvalidToolBindException e) throws CommandException {
throw new CommandException("Can't bind tool to " + e.getItemType().getName() + ": " + e.getMessage());
throw newCommandException("Can't bind tool to " + e.getItemType().getName() + ": " + e.getMessage(), e);
}
@ExceptionMatch
public void convert(FileSelectionAbortedException e) throws CommandException {
throw new CommandException("File selection aborted.");
throw newCommandException("File selection aborted.", e);
}
@ExceptionMatch
public void convert(WorldEditException e) throws CommandException {
throw new CommandException(e.getMessage(), e);
throw newCommandException(e.getMessage(), e);
}
// Prevent investigation into UsageExceptions
@ExceptionMatch
public void convert(UsageException e) throws CommandException {
throw e;
}
}

View File

@ -32,6 +32,8 @@ import com.sk89q.worldedit.internal.expression.runtime.Functions;
import com.sk89q.worldedit.internal.expression.runtime.RValue;
import com.sk89q.worldedit.internal.expression.runtime.ReturnException;
import com.sk89q.worldedit.internal.expression.runtime.Variable;
import com.sk89q.worldedit.session.request.Request;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.List;
@ -159,7 +161,18 @@ public class Expression {
}
private double evaluateRootTimed(int timeout) throws EvaluationException {
Future<Double> result = evalThread.submit(this::evaluateRoot);
Request request = Request.request();
Future<Double> result = evalThread.submit(() -> {
Request local = Request.request();
local.setSession(request.getSession());
local.setWorld(request.getWorld());
local.setEditSession(request.getEditSession());
try {
return Expression.this.evaluateRoot();
} finally {
Request.reset();
}
});
try {
return result.get(timeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {

View File

@ -54,7 +54,7 @@ public class Function extends Node {
return invokeMethod(method, args);
}
public static double invokeMethod(Method method, Object[] args) throws EvaluationException {
protected static double invokeMethod(Method method, Object[] args) throws EvaluationException {
try {
return (Double) method.invoke(null, args);
} catch (InvocationTargetException e) {

View File

@ -30,6 +30,48 @@ public final class MCDirections {
}
public static Direction fromHanging(int i) {
switch (i) {
case 0:
return Direction.DOWN;
case 1:
return Direction.UP;
case 2:
return Direction.NORTH;
case 3:
return Direction.SOUTH;
case 4:
return Direction.WEST;
case 5:
return Direction.EAST;
default:
return Direction.DOWN;
}
}
public static int toHanging(Direction direction) {
switch (direction) {
case DOWN:
return 0;
case UP:
return 1;
case NORTH:
return 2;
case SOUTH:
return 3;
case WEST:
return 4;
case EAST:
return 5;
default:
return 0;
}
}
public static Direction fromPre13Hanging(int i) {
return fromHorizontalHanging(i);
}
public static Direction fromHorizontalHanging(int i) {
switch (i) {
case 0:
return Direction.SOUTH;
@ -44,7 +86,7 @@ public final class MCDirections {
}
}
public static int toHanging(Direction direction) {
public static int toHorizontalHanging(Direction direction) {
switch (direction) {
case SOUTH:
return 0;
@ -68,15 +110,6 @@ public final class MCDirections {
}
}
public static byte toLegacyHanging(int i) {
switch (i) {
case 0: return (byte) 2;
case 1: return (byte) 1;
case 2: return (byte) 0;
default: return (byte) 3;
}
}
public static Direction fromRotation(int i) {
switch (i) {
case 0:

View File

@ -29,6 +29,7 @@ import com.sk89q.worldedit.extension.input.ParserContext;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* An abstract implementation of a factory for internal usage.
@ -45,10 +46,13 @@ public abstract class AbstractFactory<E> {
* Create a new factory.
*
* @param worldEdit the WorldEdit instance
* @param defaultParser the parser to fall back to
*/
protected AbstractFactory(WorldEdit worldEdit) {
protected AbstractFactory(WorldEdit worldEdit, InputParser<E> defaultParser) {
checkNotNull(worldEdit);
checkNotNull(defaultParser);
this.worldEdit = worldEdit;
this.parsers.add(defaultParser);
}
/**
@ -76,6 +80,12 @@ public abstract class AbstractFactory<E> {
throw new NoMatchException("No match for '" + input + "'");
}
public List<String> getSuggestions(String input) {
return parsers.stream().flatMap(
p -> p.getSuggestions(input)
).collect(Collectors.toList());
}
/**
* Registers an InputParser to this factory
*
@ -84,6 +94,6 @@ public abstract class AbstractFactory<E> {
public void register(InputParser<E> inputParser) {
checkNotNull(inputParser);
parsers.add(inputParser);
parsers.add(parsers.size() - 1, inputParser);
}
}

View File

@ -23,8 +23,7 @@ import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.extension.input.ParserContext;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
/**
* Input parser interface for {@link AbstractFactory}.
@ -43,11 +42,11 @@ public abstract class InputParser<E> {
public abstract E parseFromInput(String input, ParserContext context) throws InputParseException;
/**
* Gets a list of suggestions of input to this parser.
* Gets a stream of suggestions of input to this parser.
*
* @return a list of suggestions
* @return a stream of suggestions
*/
public List<String> getSuggestions() {
return Collections.emptyList();
public Stream<String> getSuggestions(String input) {
return Stream.empty();
}
}

View File

@ -19,12 +19,13 @@
package com.sk89q.worldedit.internal.registry;
import com.google.common.collect.Lists;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.extension.input.ParserContext;
import java.util.List;
import java.util.Locale;
import java.util.stream.Stream;
/**
* An input parser that only performs a single function from aliases.
@ -65,7 +66,16 @@ public abstract class SimpleInputParser<E> extends InputParser<E> {
}
@Override
public List<String> getSuggestions() {
return Lists.newArrayList(getPrimaryMatcher());
public Stream<String> getSuggestions(String input) {
if (input.isEmpty()) {
return Stream.of(getPrimaryMatcher());
}
final String prefix = input.toLowerCase(Locale.ROOT);
for (String alias : getMatchedAliases()) {
if (alias.startsWith(prefix)) {
return Stream.of(alias);
}
}
return Stream.empty();
}
}

View File

@ -19,7 +19,7 @@
package com.sk89q.worldedit.internal.util;
import com.sk89q.minecraft.util.commands.Command;
import org.enginehub.piston.annotation.Command;
import com.sk89q.minecraft.util.commands.CommandPermissions;
import com.sk89q.minecraft.util.commands.NestedCommand;
import com.sk89q.worldedit.command.*;
@ -79,10 +79,10 @@ public final class DocumentationPrinter {
if (!f.getName().matches("^.*\\.java$")) {
continue;
}
String className = "com.sk89q.worldedit.commands."
+ f.getName().substring(0, f.getName().lastIndexOf("."));
Class<?> cls;
try {
cls = Class.forName(className, true,
@ -90,7 +90,7 @@ public final class DocumentationPrinter {
} catch (ClassNotFoundException e) {
continue;
}
classes.add(cls);
}*/

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.internal.util;
import java.util.Objects;
/**
* An explicit substring. Provides the range from which it was taken.
*/
public final class Substring {
/**
* Take a substring from {@code original}, and {@link #wrap(String, int, int)} it into
* a Substring.
*/
public static Substring from(String original, int start) {
return wrap(original.substring(start), start, original.length());
}
/**
* Take a substring from {@code original}, and {@link #wrap(String, int, int)} it into
* a Substring.
*/
public static Substring from(String original, int start, int end) {
return wrap(original.substring(start, end), start, end);
}
/**
* Wrap the given parameters into a Substring instance.
*/
public static Substring wrap(String substring, int start, int end) {
return new Substring(substring, start, end);
}
private final String substring;
private final int start;
private final int end;
private Substring(String substring, int start, int end) {
this.substring = substring;
this.start = start;
this.end = end;
}
public String getSubstring() {
return substring;
}
public int getStart() {
return start;
}
public int getEnd() {
return end;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Substring substring1 = (Substring) o;
return start == substring1.start &&
end == substring1.end &&
substring.equals(substring1.substring);
}
@Override
public int hashCode() {
return Objects.hash(substring, start, end);
}
@Override
public String toString() {
return "Substring{" +
"substring='" + substring + "'" +
",start=" + start +
",end=" + end +
"}";
}
}