diff --git a/config/checkstyle/import-control.xml b/config/checkstyle/import-control.xml index 8b40e60d5..eedd07857 100644 --- a/config/checkstyle/import-control.xml +++ b/config/checkstyle/import-control.xml @@ -14,6 +14,7 @@ + diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitConfiguration.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitConfiguration.java index 5a20cdfbc..494a464ea 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitConfiguration.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitConfiguration.java @@ -21,6 +21,7 @@ package com.sk89q.worldedit.bukkit; import com.sk89q.util.yaml.YAMLProcessor; import com.sk89q.worldedit.util.YAMLConfiguration; +import com.sk89q.worldedit.util.report.Unreported; import java.io.File; @@ -30,7 +31,7 @@ import java.io.File; public class BukkitConfiguration extends YAMLConfiguration { public boolean noOpPermissions = false; - private final WorldEditPlugin plugin; + @Unreported private final WorldEditPlugin plugin; public BukkitConfiguration(YAMLProcessor config, WorldEditPlugin plugin) { super(config, plugin.getLogger()); diff --git a/worldedit-core/build.gradle b/worldedit-core/build.gradle index 57a5eb2ca..da2c61c44 100644 --- a/worldedit-core/build.gradle +++ b/worldedit-core/build.gradle @@ -11,6 +11,7 @@ dependencies { compile 'com.thoughtworks.paranamer:paranamer:2.6' compile 'com.google.code.gson:gson:2.8.0' compile 'com.sk89q.lib:jlibnoise:1.0.0' + compile 'com.googlecode.json-simple:json-simple:1.1.1' //compile 'net.sf.trove4j:trove4j:3.0.3' testCompile 'org.mockito:mockito-core:1.9.0-rc1' } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/WorldEdit.java b/worldedit-core/src/main/java/com/sk89q/worldedit/WorldEdit.java index 4952dea8f..3b3130180 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/WorldEdit.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/WorldEdit.java @@ -54,6 +54,8 @@ import com.sk89q.worldedit.util.io.file.FilenameException; import com.sk89q.worldedit.util.io.file.FilenameResolutionException; import com.sk89q.worldedit.util.io.file.InvalidFilenameException; import com.sk89q.worldedit.util.logging.WorldEditPrefixHandler; +import com.sk89q.worldedit.util.task.SimpleSupervisor; +import com.sk89q.worldedit.util.task.Supervisor; import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.registry.BundledBlockData; @@ -99,6 +101,7 @@ public class WorldEdit { private final PlatformManager platformManager = new PlatformManager(this); private final EditSessionFactory editSessionFactory = new EditSessionFactory.EditSessionFactoryImpl(eventBus); private final SessionManager sessions = new SessionManager(this); + private final Supervisor supervisor = new SimpleSupervisor(); private final BlockFactory blockFactory = new BlockFactory(this); private final ItemFactory itemFactory = new ItemFactory(this); @@ -148,6 +151,15 @@ public class WorldEdit { return eventBus; } + /** + * Get the supervisor. + * + * @return the supervisor + */ + public Supervisor getSupervisor() { + return supervisor; + } + /** * Get the block factory from which new {@link BlockStateHolder}s can be * constructed. diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/WorldEditCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/WorldEditCommands.java index c849d1469..639acd50b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/WorldEditCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/WorldEditCommands.java @@ -19,6 +19,7 @@ package com.sk89q.worldedit.command; +import com.google.common.io.Files; import com.sk89q.minecraft.util.commands.Command; import com.sk89q.minecraft.util.commands.CommandContext; import com.sk89q.minecraft.util.commands.CommandPermissions; @@ -32,7 +33,14 @@ import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extension.platform.Platform; import com.sk89q.worldedit.extension.platform.PlatformManager; +import com.sk89q.worldedit.util.paste.ActorCallbackPaste; +import com.sk89q.worldedit.util.report.ConfigReport; +import com.sk89q.worldedit.util.report.ReportList; +import com.sk89q.worldedit.util.report.SystemInfoReport; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; @@ -86,6 +94,28 @@ public class WorldEditCommands { actor.print("Configuration reloaded!"); } + @Command(aliases = {"report"}, desc = "Writes a report on WorldEdit", flags = "p", max = 0) + @CommandPermissions({"worldedit.report"}) + public void report(Actor actor, CommandContext args) throws WorldEditException { + ReportList report = new ReportList("Report"); + report.add(new SystemInfoReport()); + report.add(new ConfigReport()); + String result = report.toString(); + + try { + File dest = new File(we.getWorkingDirectoryFile(we.getConfiguration().saveDir), "report.txt"); + Files.write(result, dest, Charset.forName("UTF-8")); + actor.print("WorldEdit report written to " + dest.getAbsolutePath()); + } catch (IOException e) { + actor.printError("Failed to write report: " + e.getMessage()); + } + + if (args.hasFlag('p')) { + actor.checkPermission("worldedit.report.pastebin"); + ActorCallbackPaste.pastebin(we.getSupervisor(), actor, result, "WorldEdit report: %s.report"); + } + } + @Command( aliases = { "cui" }, usage = "", diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/AsyncCommandHelper.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/AsyncCommandHelper.java new file mode 100644 index 000000000..0f521336d --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/AsyncCommandHelper.java @@ -0,0 +1,135 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.command.util; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.util.task.FutureForwardingTask; +import com.sk89q.worldedit.util.task.Supervisor; +import com.sk89q.worldedit.world.World; + +import javax.annotation.Nullable; + +public class AsyncCommandHelper { + + private final ListenableFuture future; + private final Supervisor supervisor; + private final Actor sender; + @Nullable + private Object[] formatArgs; + + private AsyncCommandHelper(ListenableFuture future, Supervisor supervisor, Actor sender) { + checkNotNull(future); + checkNotNull(supervisor); + checkNotNull(sender); + + this.future = future; + this.supervisor = supervisor; + this.sender = sender; + } + + public AsyncCommandHelper formatUsing(Object... args) { + this.formatArgs = args; + return this; + } + + private String format(String message) { + if (formatArgs != null) { + return String.format(message, formatArgs); + } else { + return message; + } + } + + public AsyncCommandHelper registerWithSupervisor(String description) { + supervisor.monitor( + FutureForwardingTask.create( + future, format(description), sender)); + return this; + } + + public AsyncCommandHelper sendMessageAfterDelay(String message) { + FutureProgressListener.addProgressListener(future, sender, format(message)); + return this; + } + + public AsyncCommandHelper thenRespondWith(String success, String failure) { + // Send a response message + Futures.addCallback( + future, + new MessageFutureCallback.Builder(sender) + .onSuccess(format(success)) + .onFailure(format(failure)) + .build()); + return this; + } + + public AsyncCommandHelper thenTellErrorsOnly(String failure) { + // Send a response message + Futures.addCallback( + future, + new MessageFutureCallback.Builder(sender) + .onFailure(format(failure)) + .build()); + return this; + } + + public AsyncCommandHelper forRegionDataLoad(World world, boolean silent) { + checkNotNull(world); + + formatUsing(world.getName()); + registerWithSupervisor("Loading region data for '%s'"); + if (silent) { + thenTellErrorsOnly("Failed to load regions '%s'"); + } else { + sendMessageAfterDelay("(Please wait... loading the region data for '%s')"); + thenRespondWith( + "Loaded region data for '%s'", + "Failed to load regions '%s'"); + } + + return this; + } + + public AsyncCommandHelper forRegionDataSave(World world, boolean silent) { + checkNotNull(world); + + formatUsing(world.getName()); + registerWithSupervisor("Saving region data for '%s'"); + if (silent) { + thenTellErrorsOnly("Failed to save regions '%s'"); + } else { + sendMessageAfterDelay("(Please wait... saving the region data for '%s')"); + thenRespondWith( + "Saved region data for '%s'", + "Failed to load regions '%s'"); + } + + return this; + } + + public static AsyncCommandHelper wrap(ListenableFuture future, Supervisor supervisor, Actor sender) { + return new AsyncCommandHelper(future, supervisor, sender); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/FutureProgressListener.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/FutureProgressListener.java new file mode 100644 index 000000000..54d366ba4 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/FutureProgressListener.java @@ -0,0 +1,54 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.command.util; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import com.sk89q.worldedit.extension.platform.Actor; + +import java.util.Timer; + +public class FutureProgressListener implements Runnable { + + private static final Timer timer = new Timer(); + private static final int MESSAGE_DELAY = 1000; + + private final MessageTimerTask task; + + public FutureProgressListener(Actor sender, String message) { + checkNotNull(sender); + checkNotNull(message); + + task = new MessageTimerTask(sender, message); + timer.schedule(task, MESSAGE_DELAY); + } + + @Override + public void run() { + task.cancel(); + } + + public static void addProgressListener(ListenableFuture future, Actor sender, String message) { + future.addListener(new FutureProgressListener(sender, message), MoreExecutors.directExecutor()); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/MessageFutureCallback.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/MessageFutureCallback.java new file mode 100644 index 000000000..3abb73545 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/MessageFutureCallback.java @@ -0,0 +1,114 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.command.util; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.util.concurrent.FutureCallback; +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.util.command.parametric.ExceptionConverter; + +import javax.annotation.Nullable; + +public class MessageFutureCallback implements FutureCallback { + + private final ExceptionConverter exceptionConverter; + private final Actor sender; + @Nullable + private final String success; + @Nullable + private final String failure; + + private MessageFutureCallback(ExceptionConverter exceptionConverter, Actor sender, @Nullable String success, @Nullable String failure) { + this.exceptionConverter = exceptionConverter; + this.sender = sender; + this.success = success; + this.failure = failure; + } + + @Override + public void onSuccess(@Nullable V v) { + if (success != null) { + sender.print(success); + } + } + + @Override + public void onFailure(@Nullable Throwable throwable) { + try { + exceptionConverter.convert(throwable); + } catch (CommandException e) { + String failure = this.failure != null ? this.failure : "An error occurred"; + String message = e.getMessage() != null ? e.getMessage() : "An unknown error occurred. Please see the console!"; + sender.printError(failure + ": " + message); + } + } + + public static class Builder { + private final Actor sender; + @Nullable + private String success; + @Nullable + private String failure; + private ExceptionConverter exceptionConverter; + + public Builder(Actor sender) { + checkNotNull(sender); + + this.sender = sender; + } + + public Builder exceptionConverter(ExceptionConverter exceptionConverter) { + this.exceptionConverter = exceptionConverter; + return this; + } + + public Builder onSuccess(@Nullable String message) { + this.success = message; + return this; + } + + public Builder onFailure(@Nullable String message) { + this.failure = message; + return this; + } + + public MessageFutureCallback build() { + checkNotNull(exceptionConverter); + return new MessageFutureCallback<>(exceptionConverter, sender, success, failure); + } + } + + public static MessageFutureCallback createRegionLoadCallback(ExceptionConverter exceptionConverter, Actor sender) { + return new Builder(sender) + .exceptionConverter(exceptionConverter) + .onSuccess("Successfully load the region data.") + .build(); + } + + public static MessageFutureCallback createRegionSaveCallback(ExceptionConverter exceptionConverter, Actor sender) { + return new Builder(sender) + .exceptionConverter(exceptionConverter) + .onSuccess("Successfully saved the region data.") + .build(); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/MessageTimerTask.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/MessageTimerTask.java new file mode 100644 index 000000000..4a2549318 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/MessageTimerTask.java @@ -0,0 +1,46 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.command.util; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.sk89q.worldedit.extension.platform.Actor; + +import java.util.TimerTask; + +public class MessageTimerTask extends TimerTask { + + private final Actor sender; + private final String message; + + MessageTimerTask(Actor sender, String message) { + checkNotNull(sender); + checkNotNull(message); + + this.sender = sender; + this.message = message; + } + + @Override + public void run() { + sender.printDebug(message); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/PropertiesConfiguration.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/PropertiesConfiguration.java index 2454581f1..9ec39e35e 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/PropertiesConfiguration.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/PropertiesConfiguration.java @@ -1,280 +1,281 @@ -/* - * WorldEdit, a Minecraft world manipulation toolkit - * Copyright (C) sk89q - * 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 . - */ - -// $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.registry.LegacyMapper; -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.Arrays; -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() { - try (InputStream stream = new FileInputStream(path)) { - properties.load(stream); - } catch (FileNotFoundException ignored) { - } catch (IOException e) { - log.log(Level.WARNING, "Failed to read configuration", e); - } - - loadExtra(); - - profile = getBool("profile", profile); - traceUnflushedSessions = getBool("trace-unflushed-sessions", traceUnflushedSessions); - disallowedBlocks = getStringSet("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); - logFormat = getString("log-format", logFormat); - registerHelp = getBool("register-help", registerHelp); - wandItem = getString("wand-item", wandItem); - try { - wandItem = LegacyMapper.getInstance().getItemFromLegacy(Integer.parseInt(wandItem)).getId(); - } catch (Throwable e) { - } - 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 = getString("nav-wand-item", navigationWand); - try { - navigationWand = LegacyMapper.getInstance().getItemFromLegacy(Integer.parseInt(navigationWand)).getId(); - } catch (Throwable e) { - } - navigationWandMaxDistance = getInt("nav-wand-distance", navigationWandMaxDistance); - navigationUseGlass = getBool("nav-use-glass", navigationUseGlass); - scriptTimeout = getInt("scripting-timeout", scriptTimeout); - calculationTimeout = getInt("calculation-timeout", calculationTimeout); - 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); - serverSideCUI = getBool("server-side-cui", serverSideCUI); - - LocalSession.MAX_HISTORY_SIZE = Math.max(15, getInt("history-size", 15)); - - String snapshotsDir = getString("snapshots-dir", ""); - if (!snapshotsDir.isEmpty()) { - snapshotRepo = new SnapshotRepository(snapshotsDir); - } - - path.getParentFile().mkdirs(); - try (OutputStream output = new FileOutputStream(path)) { - properties.store(output, "Don't put comments; they get removed"); - } catch (IOException e) { - log.log(Level.WARNING, "Failed to write configuration", e); - } - } - - /** - * Called to load extra configuration. - */ - protected void loadExtra() { - } - - /** - * 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 getIntSet(String key, int[] def) { - String val = properties.getProperty(key); - if (val == null) { - properties.setProperty(key, StringUtil.joinString(def, ",", 0)); - Set set = new HashSet<>(); - for (int i : def) { - set.add(i); - } - return set; - } else { - Set set = new HashSet<>(); - String[] parts = val.split(","); - for (String part : parts) { - try { - int v = Integer.parseInt(part.trim()); - set.add(v); - } catch (NumberFormatException ignored) { - } - } - return set; - } - } - - /** - * Get a String set. - * - * @param key the key - * @param def the default value - * @return the value - */ - protected Set getStringSet(String key, String[] def) { - String val = properties.getProperty(key); - if (val == null) { - properties.setProperty(key, StringUtil.joinString(def, ",", 0)); - return new HashSet<>(Arrays.asList(def)); - } else { - Set set = new HashSet<>(); - String[] parts = val.split(","); - for (String part : parts) { - try { - String v = part.trim(); - set.add(v); - } catch (NumberFormatException ignored) { - } - } - return set; - } - } - -} +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +// $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.util.report.Unreported; +import com.sk89q.worldedit.world.registry.LegacyMapper; +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.Arrays; +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 { + + @Unreported private static final Logger log = Logger.getLogger(PropertiesConfiguration.class.getCanonicalName()); + + @Unreported protected Properties properties; + @Unreported 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() { + try (InputStream stream = new FileInputStream(path)) { + properties.load(stream); + } catch (FileNotFoundException ignored) { + } catch (IOException e) { + log.log(Level.WARNING, "Failed to read configuration", e); + } + + loadExtra(); + + profile = getBool("profile", profile); + traceUnflushedSessions = getBool("trace-unflushed-sessions", traceUnflushedSessions); + disallowedBlocks = getStringSet("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); + logFormat = getString("log-format", logFormat); + registerHelp = getBool("register-help", registerHelp); + wandItem = getString("wand-item", wandItem); + try { + wandItem = LegacyMapper.getInstance().getItemFromLegacy(Integer.parseInt(wandItem)).getId(); + } catch (Throwable e) { + } + 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 = getString("nav-wand-item", navigationWand); + try { + navigationWand = LegacyMapper.getInstance().getItemFromLegacy(Integer.parseInt(navigationWand)).getId(); + } catch (Throwable e) { + } + navigationWandMaxDistance = getInt("nav-wand-distance", navigationWandMaxDistance); + navigationUseGlass = getBool("nav-use-glass", navigationUseGlass); + scriptTimeout = getInt("scripting-timeout", scriptTimeout); + calculationTimeout = getInt("calculation-timeout", calculationTimeout); + 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); + serverSideCUI = getBool("server-side-cui", serverSideCUI); + + LocalSession.MAX_HISTORY_SIZE = Math.max(15, getInt("history-size", 15)); + + String snapshotsDir = getString("snapshots-dir", ""); + if (!snapshotsDir.isEmpty()) { + snapshotRepo = new SnapshotRepository(snapshotsDir); + } + + path.getParentFile().mkdirs(); + try (OutputStream output = new FileOutputStream(path)) { + properties.store(output, "Don't put comments; they get removed"); + } catch (IOException e) { + log.log(Level.WARNING, "Failed to write configuration", e); + } + } + + /** + * Called to load extra configuration. + */ + protected void loadExtra() { + } + + /** + * 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 getIntSet(String key, int[] def) { + String val = properties.getProperty(key); + if (val == null) { + properties.setProperty(key, StringUtil.joinString(def, ",", 0)); + Set set = new HashSet<>(); + for (int i : def) { + set.add(i); + } + return set; + } else { + Set set = new HashSet<>(); + String[] parts = val.split(","); + for (String part : parts) { + try { + int v = Integer.parseInt(part.trim()); + set.add(v); + } catch (NumberFormatException ignored) { + } + } + return set; + } + } + + /** + * Get a String set. + * + * @param key the key + * @param def the default value + * @return the value + */ + protected Set getStringSet(String key, String[] def) { + String val = properties.getProperty(key); + if (val == null) { + properties.setProperty(key, StringUtil.joinString(def, ",", 0)); + return new HashSet<>(Arrays.asList(def)); + } else { + Set set = new HashSet<>(); + String[] parts = val.split(","); + for (String part : parts) { + try { + String v = part.trim(); + set.add(v); + } catch (NumberFormatException ignored) { + } + } + return set; + } + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/YAMLConfiguration.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/YAMLConfiguration.java index 697e77e56..dfbd586b2 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/YAMLConfiguration.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/YAMLConfiguration.java @@ -1,133 +1,134 @@ -/* - * WorldEdit, a Minecraft world manipulation toolkit - * Copyright (C) sk89q - * 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 . - */ - -package com.sk89q.worldedit.util; - -import com.google.common.collect.Lists; -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); - traceUnflushedSessions = config.getBoolean("debugging.trace-unflushed-sessions", traceUnflushedSessions); - wandItem = convertLegacyItem(config.getString("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<>(config.getStringList("limits.disallowed-blocks", Lists.newArrayList(defaultDisallowedBlocks))); - allowedDataCycleBlocks = new HashSet<>(config.getStringList("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); - logFormat = config.getString("logging.format", logFormat); - - 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 = convertLegacyItem(config.getString("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); - - calculationTimeout = config.getInt("calculation.timeout", calculationTimeout); - - 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); - serverSideCUI = config.getBoolean("server-side-cui", true); - - String snapshotsDir = config.getString("snapshots.directory", ""); - if (!snapshotsDir.isEmpty()) { - snapshotRepo = new SnapshotRepository(snapshotsDir); - } - - String type = config.getString("shell-save-type", "").trim(); - shellSaveType = type.isEmpty() ? null : type; - - } - - public void unload() { - } - -} +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util; + +import com.google.common.collect.Lists; +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.util.report.Unreported; +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 { + + @Unreported protected final YAMLProcessor config; + @Unreported 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); + traceUnflushedSessions = config.getBoolean("debugging.trace-unflushed-sessions", traceUnflushedSessions); + wandItem = convertLegacyItem(config.getString("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<>(config.getStringList("limits.disallowed-blocks", Lists.newArrayList(defaultDisallowedBlocks))); + allowedDataCycleBlocks = new HashSet<>(config.getStringList("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); + logFormat = config.getString("logging.format", logFormat); + + 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 = convertLegacyItem(config.getString("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); + + calculationTimeout = config.getInt("calculation.timeout", calculationTimeout); + + 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); + serverSideCUI = config.getBoolean("server-side-cui", true); + + String snapshotsDir = config.getString("snapshots.directory", ""); + if (!snapshotsDir.isEmpty()) { + snapshotRepo = new SnapshotRepository(snapshotsDir); + } + + String type = config.getString("shell-save-type", "").trim(); + shellSaveType = type.isEmpty() ? null : type; + + } + + public void unload() { + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/net/HttpRequest.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/net/HttpRequest.java new file mode 100644 index 000000000..8421210e4 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/net/HttpRequest.java @@ -0,0 +1,490 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.net; + +import com.sk89q.worldedit.util.io.Closer; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class HttpRequest implements Closeable { + + private static final int CONNECT_TIMEOUT = 1000 * 5; + private static final int READ_TIMEOUT = 1000 * 5; + private static final int READ_BUFFER_SIZE = 1024 * 8; + + private final Map headers = new HashMap<>(); + private final String method; + private final URL url; + private String contentType; + private byte[] body; + private HttpURLConnection conn; + private InputStream inputStream; + + private long contentLength = -1; + private long readBytes = 0; + + /** + * Create a new HTTP request. + * + * @param method the method + * @param url the URL + */ + private HttpRequest(String method, URL url) { + this.method = method; + this.url = url; + } + + /** + * Submit data. + * + * @return this object + */ + public HttpRequest body(String data) { + body = data.getBytes(); + return this; + } + + /** + * Submit form data. + * + * @param form the form + * @return this object + */ + public HttpRequest bodyForm(Form form) { + contentType = "application/x-www-form-urlencoded"; + body = form.toString().getBytes(); + return this; + } + + /** + * Add a header. + * + * @param key the header key + * @param value the header value + * @return this object + */ + public HttpRequest header(String key, String value) { + if (key.equalsIgnoreCase("Content-Type")) { + contentType = value; + } else { + headers.put(key, value); + } + return this; + } + + /** + * Execute the request. + *

+ * After execution, {@link #close()} should be called. + * + * @return this object + * @throws java.io.IOException on I/O error + */ + public HttpRequest execute() throws IOException { + boolean successful = false; + + try { + if (conn != null) { + throw new IllegalArgumentException("Connection already executed"); + } + + conn = (HttpURLConnection) reformat(url).openConnection(); + conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Java)"); + + if (body != null) { + conn.setRequestProperty("Content-Type", contentType); + conn.setRequestProperty("Content-Length", Integer.toString(body.length)); + conn.setDoInput(true); + } + + for (Map.Entry entry : headers.entrySet()) { + conn.setRequestProperty(entry.getKey(), entry.getValue()); + } + + conn.setRequestMethod(method); + conn.setUseCaches(false); + conn.setDoOutput(true); + conn.setConnectTimeout(CONNECT_TIMEOUT); + conn.setReadTimeout(READ_TIMEOUT); + + conn.connect(); + + if (body != null) { + DataOutputStream out = new DataOutputStream(conn.getOutputStream()); + out.write(body); + out.flush(); + out.close(); + } + + inputStream = conn.getResponseCode() == HttpURLConnection.HTTP_OK ? + conn.getInputStream() : conn.getErrorStream(); + + successful = true; + } finally { + if (!successful) { + close(); + } + } + + return this; + } + + /** + * Require that the response code is one of the given response codes. + * + * @param codes a list of codes + * @return this object + * @throws java.io.IOException if there is an I/O error or the response code is not expected + */ + public HttpRequest expectResponseCode(int... codes) throws IOException { + int responseCode = getResponseCode(); + + for (int code : codes) { + if (code == responseCode) { + return this; + } + } + + close(); + throw new IOException("Did not get expected response code, got " + responseCode + " for " + url); + } + + /** + * Get the response code. + * + * @return the response code + * @throws java.io.IOException on I/O error + */ + public int getResponseCode() throws IOException { + if (conn == null) { + throw new IllegalArgumentException("No connection has been made"); + } + + return conn.getResponseCode(); + } + + /** + * Get the input stream. + * + * @return the input stream + */ + public InputStream getInputStream() { + return inputStream; + } + + /** + * Buffer the returned response. + * + * @return the buffered response + * @throws java.io.IOException on I/O error + * @throws InterruptedException on interruption + */ + public BufferedResponse returnContent() throws IOException, InterruptedException { + if (inputStream == null) { + throw new IllegalArgumentException("No input stream available"); + } + + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + int b = 0; + while ((b = inputStream.read()) != -1) { + bos.write(b); + } + return new BufferedResponse(bos.toByteArray()); + } finally { + close(); + } + } + + /** + * Save the result to a file. + * + * @param file the file + * @return this object + * @throws java.io.IOException on I/O error + * @throws InterruptedException on interruption + */ + public HttpRequest saveContent(File file) throws IOException, InterruptedException { + Closer closer = Closer.create(); + + try { + FileOutputStream fos = closer.register(new FileOutputStream(file)); + BufferedOutputStream bos = closer.register(new BufferedOutputStream(fos)); + + saveContent(bos); + } finally { + closer.close(); + } + + return this; + } + + /** + * Save the result to an output stream. + * + * @param out the output stream + * @return this object + * @throws java.io.IOException on I/O error + * @throws InterruptedException on interruption + */ + public HttpRequest saveContent(OutputStream out) throws IOException, InterruptedException { + BufferedInputStream bis; + + try { + String field = conn.getHeaderField("Content-Length"); + if (field != null) { + long len = Long.parseLong(field); + if (len >= 0) { // Let's just not deal with really big numbers + contentLength = len; + } + } + } catch (NumberFormatException ignored) { + } + + try { + bis = new BufferedInputStream(inputStream); + + byte[] data = new byte[READ_BUFFER_SIZE]; + int len = 0; + while ((len = bis.read(data, 0, READ_BUFFER_SIZE)) >= 0) { + out.write(data, 0, len); + readBytes += len; + } + } finally { + close(); + } + + return this; + } + + @Override + public void close() throws IOException { + if (conn != null) conn.disconnect(); + } + + /** + * Perform a GET request. + * + * @param url the URL + * @return a new request object + */ + public static HttpRequest get(URL url) { + return request("GET", url); + } + + /** + * Perform a POST request. + * + * @param url the URL + * @return a new request object + */ + public static HttpRequest post(URL url) { + return request("POST", url); + } + + /** + * Perform a request. + * + * @param method the method + * @param url the URL + * @return a new request object + */ + public static HttpRequest request(String method, URL url) { + return new HttpRequest(method, url); + } + + /** + * Create a new {@link java.net.URL} and throw a {@link RuntimeException} if the URL + * is not valid. + * + * @param url the url + * @return a URL object + * @throws RuntimeException if the URL is invalid + */ + public static URL url(String url) { + try { + return new URL(url); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + /** + * URL may contain spaces and other nasties that will cause a failure. + * + * @param existing the existing URL to transform + * @return the new URL, or old one if there was a failure + */ + private static URL reformat(URL existing) { + try { + URL url = new URL(existing.toString()); + URI uri = new URI( + url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), + url.getPath(), url.getQuery(), url.getRef()); + url = uri.toURL(); + return url; + } catch (MalformedURLException e) { + return existing; + } catch (URISyntaxException e) { + return existing; + } + } + + /** + * Used with {@link #bodyForm(Form)}. + */ + public final static class Form { + public final List elements = new ArrayList<>(); + + private Form() { + } + + /** + * Add a key/value to the form. + * + * @param key the key + * @param value the value + * @return this object + */ + public Form add(String key, String value) { + try { + elements.add(URLEncoder.encode(key, "UTF-8") + + "=" + URLEncoder.encode(value, "UTF-8")); + return this; + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + boolean first = true; + for (String element : elements) { + if (first) { + first = false; + } else { + builder.append("&"); + } + builder.append(element); + } + return builder.toString(); + } + + /** + * Create a new form. + * + * @return a new form + */ + public static Form create() { + return new Form(); + } + } + + /** + * Used to buffer the response in memory. + */ + public class BufferedResponse { + private final byte[] data; + + private BufferedResponse(byte[] data) { + this.data = data; + } + + /** + * Return the result as bytes. + * + * @return the data + */ + public byte[] asBytes() { + return data; + } + + /** + * Return the result as a string. + * + * @param encoding the encoding + * @return the string + * @throws java.io.IOException on I/O error + */ + public String asString(String encoding) throws IOException { + return new String(data, encoding); + } + + /** + * Save the result to a file. + * + * @param file the file + * @return this object + * @throws java.io.IOException on I/O error + * @throws InterruptedException on interruption + */ + public BufferedResponse saveContent(File file) throws IOException, InterruptedException { + Closer closer = Closer.create(); + file.getParentFile().mkdirs(); + + try { + FileOutputStream fos = closer.register(new FileOutputStream(file)); + BufferedOutputStream bos = closer.register(new BufferedOutputStream(fos)); + + saveContent(bos); + } finally { + closer.close(); + } + + return this; + } + + /** + * Save the result to an output stream. + * + * @param out the output stream + * @return this object + * @throws java.io.IOException on I/O error + * @throws InterruptedException on interruption + */ + public BufferedResponse saveContent(OutputStream out) throws IOException, InterruptedException { + out.write(data); + + return this; + } + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/ActorCallbackPaste.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/ActorCallbackPaste.java new file mode 100644 index 000000000..2eb306686 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/ActorCallbackPaste.java @@ -0,0 +1,70 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.paste; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.sk89q.worldedit.command.util.AsyncCommandHelper; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.util.task.Supervisor; + +import java.net.URL; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ActorCallbackPaste { + + private static final Logger LOGGER = Logger.getLogger(ActorCallbackPaste.class.getSimpleName()); + + private ActorCallbackPaste() { + } + + /** + * Submit data to a pastebin service and inform the sender of + * success or failure. + * + * @param supervisor The supervisor instance + * @param sender The sender + * @param content The content + * @param successMessage The message, formatted with {@link String#format(String, Object...)} on success + */ + public static void pastebin(Supervisor supervisor, final Actor sender, String content, final String successMessage) { + ListenableFuture future = new EngineHubPaste().paste(content); + + AsyncCommandHelper.wrap(future, supervisor, sender) + .registerWithSupervisor("Submitting content to a pastebin service...") + .sendMessageAfterDelay("(Please wait... sending output to pastebin...)"); + + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(URL url) { + sender.print(String.format(successMessage, url)); + } + + @Override + public void onFailure(Throwable throwable) { + LOGGER.log(Level.WARNING, "Failed to submit pastebin", throwable); + sender.printError("Failed to submit to a pastebin. Please see console for the error."); + } + }); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/EngineHubPaste.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/EngineHubPaste.java new file mode 100644 index 000000000..da6b701fd --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/EngineHubPaste.java @@ -0,0 +1,78 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.paste; + +import com.google.common.util.concurrent.ListenableFuture; +import com.sk89q.worldedit.util.net.HttpRequest; +import org.json.simple.JSONValue; + +import java.io.IOException; +import java.net.URL; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class EngineHubPaste implements Paster { + + private static final Pattern URL_PATTERN = Pattern.compile("https?://.+$"); + + @Override + public ListenableFuture paste(String content) { + return Pasters.getExecutor().submit(new PasteTask(content)); + } + + private final class PasteTask implements Callable { + private final String content; + + private PasteTask(String content) { + this.content = content; + } + + @Override + public URL call() throws IOException, InterruptedException { + HttpRequest.Form form = HttpRequest.Form.create(); + form.add("content", content); + form.add("from", "worldguard"); + + URL url = HttpRequest.url("http://paste.enginehub.org/paste"); + String result = HttpRequest.post(url) + .bodyForm(form) + .execute() + .expectResponseCode(200) + .returnContent() + .asString("UTF-8").trim(); + + Object object = JSONValue.parse(result); + if (object instanceof Map) { + @SuppressWarnings("unchecked") + String urlString = String.valueOf(((Map) object).get("url")); + Matcher m = URL_PATTERN.matcher(urlString); + + if (m.matches()) { + return new URL(urlString); + } + } + + throw new IOException("Failed to save paste; instead, got: " + result); + } + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/Pastebin.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/Pastebin.java new file mode 100644 index 000000000..dcbef09b0 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/Pastebin.java @@ -0,0 +1,93 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.paste; + +import com.google.common.util.concurrent.ListenableFuture; +import com.sk89q.worldedit.util.net.HttpRequest; + +import java.io.IOException; +import java.net.URL; +import java.util.concurrent.Callable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Pastebin implements Paster { + + private static final Pattern URL_PATTERN = Pattern.compile("https?://pastebin.com/([^/]+)$"); + + private boolean mungingLinks = true; + + public boolean isMungingLinks() { + return mungingLinks; + } + + public void setMungingLinks(boolean mungingLinks) { + this.mungingLinks = mungingLinks; + } + + @Override + public ListenableFuture paste(String content) { + if (mungingLinks) { + content = content.replaceAll("http://", "http_//"); + } + + return Pasters.getExecutor().submit(new PasteTask(content)); + } + + private final class PasteTask implements Callable { + private final String content; + + private PasteTask(String content) { + this.content = content; + } + + @Override + public URL call() throws IOException, InterruptedException { + HttpRequest.Form form = HttpRequest.Form.create(); + form.add("api_option", "paste"); + form.add("api_dev_key", "4867eae74c6990dbdef07c543cf8f805"); + form.add("api_paste_code", content); + form.add("api_paste_private", "0"); + form.add("api_paste_name", ""); + form.add("api_paste_expire_date", "1W"); + form.add("api_paste_format", "text"); + form.add("api_user_key", ""); + + URL url = HttpRequest.url("http://pastebin.com/api/api_post.php"); + String result = HttpRequest.post(url) + .bodyForm(form) + .execute() + .expectResponseCode(200) + .returnContent() + .asString("UTF-8").trim(); + + Matcher m = URL_PATTERN.matcher(result); + + if (m.matches()) { + return new URL("http://pastebin.com/raw.php?i=" + m.group(1)); + } else if (result.matches("^https?://.+")) { + return new URL(result); + } else { + throw new IOException("Failed to save paste; instead, got: " + result); + } + } + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/Paster.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/Paster.java new file mode 100644 index 000000000..7a7d74cac --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/Paster.java @@ -0,0 +1,30 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.paste; + +import com.google.common.util.concurrent.ListenableFuture; + +import java.net.URL; + +public interface Paster { + + ListenableFuture paste(String content); + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/Pasters.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/Pasters.java new file mode 100644 index 000000000..b809f1836 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/Pasters.java @@ -0,0 +1,44 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.paste; + +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; + +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +final class Pasters { + + private static final ListeningExecutorService executor = + MoreExecutors.listeningDecorator( + new ThreadPoolExecutor(0, Integer.MAX_VALUE, + 2L, TimeUnit.SECONDS, + new SynchronousQueue<>())); + + private Pasters() { + } + + static ListeningExecutorService getExecutor() { + return executor; + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/report/ConfigReport.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/report/ConfigReport.java new file mode 100644 index 000000000..1a4b486a0 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/report/ConfigReport.java @@ -0,0 +1,32 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.report; + +import com.sk89q.worldedit.WorldEdit; + +public class ConfigReport extends DataReport { + + public ConfigReport() { + super("WorldEdit Configuration"); + + append("Configuration", new HierarchyObjectReport("Configuration", WorldEdit.getInstance().getConfiguration())); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/report/HierarchyObjectReport.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/report/HierarchyObjectReport.java new file mode 100644 index 000000000..522693919 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/report/HierarchyObjectReport.java @@ -0,0 +1,32 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.report; + +public class HierarchyObjectReport extends ShallowObjectReport { + + public HierarchyObjectReport(String title, Object object) { + super(title, object); + + Class type = object.getClass(); + while ((type = type.getSuperclass()) != null) { + scanClass(object, type); + } + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/report/ShallowObjectReport.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/report/ShallowObjectReport.java index b94b63e65..bb4abb1f3 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/report/ShallowObjectReport.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/report/ShallowObjectReport.java @@ -34,8 +34,10 @@ public class ShallowObjectReport extends DataReport { super(title); checkNotNull(object, "object"); - Class type = object.getClass(); + scanClass(object, object.getClass()); + } + void scanClass(Object object, Class type) { for (Field field : type.getDeclaredFields()) { if (Modifier.isStatic(field.getModifiers())) { continue; @@ -54,5 +56,4 @@ public class ShallowObjectReport extends DataReport { } } } - } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/AbstractTask.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/AbstractTask.java new file mode 100644 index 000000000..e81e2bf22 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/AbstractTask.java @@ -0,0 +1,74 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.task; + +import com.google.common.util.concurrent.AbstractFuture; + +import javax.annotation.Nullable; +import java.util.Date; +import java.util.UUID; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * An abstract task that stores a name and owner. + * + * @param the type returned + */ +public abstract class AbstractTask extends AbstractFuture implements Task { + + private final UUID uniqueId = UUID.randomUUID(); + private final String name; + private final Object owner; + private final Date creationDate = new Date(); + + /** + * Create a new instance. + * + * @param name the name + * @param owner the owner + */ + protected AbstractTask(String name, @Nullable Object owner) { + checkNotNull(name); + this.name = name; + this.owner = owner; + } + + @Override + public UUID getUniqueId() { + return uniqueId; + } + + @Override + public String getName() { + return name; + } + + @Nullable + @Override + public Object getOwner() { + return owner; + } + + @Override + public Date getCreationDate() { + return creationDate; + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/FutureForwardingTask.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/FutureForwardingTask.java new file mode 100644 index 000000000..61f5d75a9 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/FutureForwardingTask.java @@ -0,0 +1,121 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.task; + +import com.google.common.util.concurrent.ListenableFuture; +import com.sk89q.worldedit.util.task.progress.Progress; + +import javax.annotation.Nullable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A task that wraps a {@code ListenableFuture}. + * + *

{@link Task.State#SCHEDULED} is never returned because it is not possible + * to test whether the future has "started," so {@link Task.State#RUNNING} is + * returned in its place.

+ * + *

Use {@link #create(ListenableFuture, String, Object)} to create a new + * instance.

+ * + * @param the type returned + */ +public class FutureForwardingTask extends AbstractTask { + + private final ListenableFuture future; + + private FutureForwardingTask(ListenableFuture future, String name, @Nullable Object owner) { + super(name, owner); + checkNotNull(future); + this.future = future; + } + + @Override + public void addListener(Runnable listener, Executor executor) { + future.addListener(listener, executor); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return future.cancel(mayInterruptIfRunning); + } + + @Override + public boolean isCancelled() { + return future.isCancelled(); + } + + @Override + public boolean isDone() { + return future.isDone(); + } + + @Override + public V get() throws InterruptedException, ExecutionException { + return future.get(); + } + + @Override + public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return future.get(timeout, unit); + } + + @Override + public State getState() { + if (isCancelled()) { + return State.CANCELLED; + } else if (isDone()) { + try { + get(); + return State.SUCCEEDED; + } catch (InterruptedException e) { + return State.CANCELLED; + } catch (ExecutionException e) { + return State.FAILED; + } + } else { + return State.RUNNING; + } + } + + @Override + public Progress getProgress() { + return Progress.indeterminate(); + } + + /** + * Create a new instance. + * + * @param future the future + * @param name the name of the task + * @param owner the owner of the task, or {@code null} + * @param the type returned by the future + * @return a new instance + */ + public static FutureForwardingTask create(ListenableFuture future, String name, @Nullable Object owner) { + return new FutureForwardingTask<>(future, name, owner); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/SimpleSupervisor.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/SimpleSupervisor.java new file mode 100644 index 000000000..0712cc5dd --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/SimpleSupervisor.java @@ -0,0 +1,59 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.task; + +import com.google.common.util.concurrent.MoreExecutors; + +import java.util.ArrayList; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * An implementation of a {@code Supervisor}. + */ +public class SimpleSupervisor implements Supervisor { + + private final List> monitored = new ArrayList<>(); + private final Object lock = new Object(); + + @Override + public List> getTasks() { + synchronized (lock) { + return new ArrayList<>(monitored); + } + } + + @Override + public void monitor(final Task task) { + checkNotNull(task); + + synchronized (lock) { + monitored.add(task); + } + + task.addListener(() -> { + synchronized (lock) { + monitored.remove(task); + } + }, MoreExecutors.directExecutor()); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/Supervisor.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/Supervisor.java new file mode 100644 index 000000000..9b470700e --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/Supervisor.java @@ -0,0 +1,44 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.task; + +import java.util.List; + +/** + * Manages running tasks and informs users of their progress, but does not + * execute the task. + */ +public interface Supervisor { + + /** + * Get a list of running or queued tasks. + * + * @return a list of tasks + */ + List> getTasks(); + + /** + * Monitor the given task. + * + * @param task the task + */ + void monitor(Task task); + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/Task.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/Task.java new file mode 100644 index 000000000..169cf1718 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/Task.java @@ -0,0 +1,97 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.task; + +import com.google.common.util.concurrent.ListenableFuture; +import com.sk89q.worldedit.util.task.progress.ProgressObservable; + +import javax.annotation.Nullable; +import java.util.Date; +import java.util.UUID; + +/** + * A task is a job that can be scheduled, run, or cancelled. Tasks can report + * on their own status. Tasks have owners. + */ +public interface Task extends ListenableFuture, ProgressObservable { + + /** + * Get the unique ID of this task. + * + * @return this task's unique ID + */ + UUID getUniqueId(); + + /** + * Get the name of the task so it can be printed to the user. + * + * @return the name of the task + */ + String getName(); + + /** + * Get the owner of the task. + * + * @return an owner object, if one is known or valid, otherwise {@code null} + */ + @Nullable + Object getOwner(); + + /** + * Get the state of the task. + * + * @return the state of the task + */ + State getState(); + + /** + * Get the time at which the task was created. + * + * @return a date + */ + Date getCreationDate(); + + /** + * Represents the state of a task. + */ + enum State { + /** + * The task has been scheduled to run but is not running yet. + */ + SCHEDULED, + /** + * The task has been cancelled and may be stopped or will stop. + */ + CANCELLED, + /** + * The task is currently running. + */ + RUNNING, + /** + * The task has failed. + */ + FAILED, + /** + * The task has succeeded. + */ + SUCCEEDED + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/TaskStateComparator.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/TaskStateComparator.java new file mode 100644 index 000000000..38b77e73e --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/TaskStateComparator.java @@ -0,0 +1,43 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.task; + +import java.util.Comparator; + +/** + * Compares task states according to the order of the {@link Task.State} + * enumeration. + */ +public class TaskStateComparator implements Comparator> { + + @Override + public int compare(com.sk89q.worldedit.util.task.Task o1, Task o2) { + int ordinal1 = o1.getState().ordinal(); + int ordinal2 = o2.getState().ordinal(); + if (ordinal1 < ordinal2) { + return -1; + } else if (ordinal1 > ordinal2) { + return 1; + } else { + return 0; + } + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/progress/Progress.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/progress/Progress.java new file mode 100644 index 000000000..d3d0571ae --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/progress/Progress.java @@ -0,0 +1,183 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.task.progress; + +import java.util.Arrays; +import java.util.Collection; + +/** + * A progress object describes the progress of an operation, specifying + * either a percentage of completion or a status of indeterminacy. + * + *

Progress objects are immutable.

+ * + *

To create a new instance, use one of the static constructors + * on this class.

+ */ +public abstract class Progress { + + /** + * Create a new instance. + */ + private Progress() { + } + + /** + * Return whether the current progress is indeterminate. + * + * @return true if indeterminate + */ + public abstract boolean isIndeterminate(); + + /** + * Get the progress percentage. + * + *

If {@link #isIndeterminate()} returns {@code true}, the behavior + * of this method is undefined.

+ * + * @return a number in the range [0, 1] + */ + public abstract double getProgress(); + + /** + * Get a static progress object that is indeterminate. + * + * @return a progress object + */ + public static Progress indeterminate() { + return INDETERMINATE; + } + + /** + * Get a static progress object that is complete. + * + * @return a progress object + */ + public static Progress completed() { + return COMPLETED; + } + + /** + * Create a new progress object with the given percentage. + * + * @param value the percentage, which will be clamped to [0, 1] + * @return a progress object + */ + public static Progress of(double value) { + if (value < 0) { + value = 0; + } else if (value > 1) { + value = 1; + } + + final double finalValue = value; + return new Progress() { + @Override + public boolean isIndeterminate() { + return false; + } + + @Override + public double getProgress() { + return finalValue; + } + }; + } + + /** + * Create a new progress object with progress split equally between the + * given progress objects. + * + * @param objects an array of progress objects + * @return a new progress value + */ + public static Progress split(Progress... objects) { + return split(Arrays.asList(objects)); + } + + /** + * Create a new progress object with progress split equally between the + * given progress objects. + * + * @param progress a collection of progress objects + * @return a new progress value + */ + public static Progress split(Collection progress) { + int count = 0; + double total = 0; + + for (Progress p : progress) { + if (p.isIndeterminate()) { + return indeterminate(); + } + total += p.getProgress(); + } + + return of(total / count); + } + + /** + * Create a new progress object with progress split equally between the + * given {@link ProgressObservable}s. + * + * @param observables an array of observables + * @return a new progress value + */ + public static Progress splitObservables(ProgressObservable... observables) { + return splitObservables(Arrays.asList(observables)); + } + + /** + * Create a new progress object with progress split equally between the + * given {@link ProgressObservable}s. + * + * @param observables a collection of observables + * @return a new progress value + */ + public static Progress splitObservables(Collection observables) { + int count = 0; + double total = 0; + + for (ProgressObservable observable : observables) { + Progress p = observable.getProgress(); + if (p.isIndeterminate()) { + return indeterminate(); + } + total += p.getProgress(); + } + + return of(total / count); + } + + private static final Progress COMPLETED = of(1); + + private static final Progress INDETERMINATE = new Progress() { + @Override + public boolean isIndeterminate() { + return true; + } + + @Override + public double getProgress() { + return 0; + } + }; + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/progress/ProgressIterator.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/progress/ProgressIterator.java new file mode 100644 index 000000000..11eb34f32 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/progress/ProgressIterator.java @@ -0,0 +1,100 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.task.progress; + +import java.util.Iterator; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * An iterator that keeps track of how many entries have been visited and + * calculates a "percent completed" using a provided total count. + * + *

The returned progress percentage will always be between 0 or 1 + * (inclusive). If the iterator returns more entries than the total count, + * then 100% will be returned for the progress.

+ * + * @param the type + */ +public class ProgressIterator implements Iterator, ProgressObservable { + + private final Iterator iterator; + private final int count; + private int visited = 0; + + /** + * Create a new instance. + * + * @param iterator the iterator + * @param count the count + */ + private ProgressIterator(Iterator iterator, int count) { + checkNotNull(iterator); + this.iterator = iterator; + this.count = count; + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public V next() { + V value = iterator.next(); + visited++; + return value; + } + + @Override + public void remove() { + iterator.remove(); + } + + @Override + public Progress getProgress() { + return Progress.of(count > 0 ? Math.min(1, Math.max(0, (visited / (double) count))) : 1); + } + + /** + * Create a new instance. + * + * @param iterator the iterator + * @param count the number of objects + * @param the type + * @return an instance + */ + public static ProgressIterator create(Iterator iterator, int count) { + return new ProgressIterator(iterator, count); + } + + /** + * Create a new instance from a list. + * + * @param list a list + * @param the type + * @return an instance + */ + public static ProgressIterator create(List list) { + return create(list.iterator(), list.size()); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/progress/ProgressObservable.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/progress/ProgressObservable.java new file mode 100644 index 000000000..b109e12a2 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/progress/ProgressObservable.java @@ -0,0 +1,34 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.task.progress; + +/** + * An object that is able to report on its progress. + */ +public interface ProgressObservable { + + /** + * Get the current percentage of completion. + * + * @return a progress object + */ + Progress getProgress(); + +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/config/ConfigurateConfiguration.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/config/ConfigurateConfiguration.java index ed9f76c5c..eb4c64d50 100644 --- a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/config/ConfigurateConfiguration.java +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/config/ConfigurateConfiguration.java @@ -23,6 +23,7 @@ import com.google.common.reflect.TypeToken; import com.sk89q.worldedit.LocalConfiguration; import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.session.SessionManager; +import com.sk89q.worldedit.util.report.Unreported; import com.sk89q.worldedit.world.registry.LegacyMapper; import com.sk89q.worldedit.world.snapshot.SnapshotRepository; import ninja.leaping.configurate.ConfigurationOptions; @@ -36,10 +37,10 @@ import java.util.HashSet; public class ConfigurateConfiguration extends LocalConfiguration { - protected final ConfigurationLoader config; - protected final Logger logger; + @Unreported protected final ConfigurationLoader config; + @Unreported protected final Logger logger; - protected CommentedConfigurationNode node; + @Unreported protected CommentedConfigurationNode node; public ConfigurateConfiguration(ConfigurationLoader config, Logger logger) { this.config = config;