diff --git a/src/main/java/fr/moribus/imageonmap/commands/maptool/NewCommand.java b/src/main/java/fr/moribus/imageonmap/commands/maptool/NewCommand.java index 403720a..ae749c8 100644 --- a/src/main/java/fr/moribus/imageonmap/commands/maptool/NewCommand.java +++ b/src/main/java/fr/moribus/imageonmap/commands/maptool/NewCommand.java @@ -18,11 +18,13 @@ package fr.moribus.imageonmap.commands.maptool; +import fr.moribus.imageonmap.PluginLogger; import fr.moribus.imageonmap.commands.Command; import fr.moribus.imageonmap.commands.CommandException; import fr.moribus.imageonmap.commands.CommandInfo; import fr.moribus.imageonmap.commands.Commands; import fr.moribus.imageonmap.image.ImageRendererExecutor; +import fr.moribus.imageonmap.map.ImageMap; import fr.moribus.imageonmap.worker.WorkerCallback; import java.net.MalformedURLException; import java.net.URL; @@ -39,6 +41,7 @@ public class NewCommand extends Command protected void run() throws CommandException { final Player player = playerSender(); + boolean scaling = false; URL url; if(args.length < 1) throwInvalidArgument("You must give an URL to take the image from."); @@ -50,26 +53,29 @@ public class NewCommand extends Command catch(MalformedURLException ex) { throwInvalidArgument("Invalid URL."); + return; } - if(args.length < 2) + if(args.length >= 2) { - + if(args[1].equals("resize")) scaling = true; } - info("Working ..."); - ImageRendererExecutor.Test(new WorkerCallback() + info("Rendering ..."); + ImageRendererExecutor.Render(url, scaling, player.getUniqueId(), new WorkerCallback() { @Override - public void finished(Object... args) + public void finished(ImageMap result) { - player.sendMessage("Long task finished !"); + player.sendMessage("§7Rendering finished !"); + result.give(player.getInventory()); } @Override public void errored(Throwable exception) { - player.sendMessage("Whoops, an error occured !"); + player.sendMessage("§cMap rendering failed : " + exception.getMessage()); + PluginLogger.LogWarning("Rendering from '" + player.getName() + "' failed", exception); } }); } diff --git a/src/main/java/fr/moribus/imageonmap/image/ImageIOExecutor.java b/src/main/java/fr/moribus/imageonmap/image/ImageIOExecutor.java index 6c6294d..4d2c971 100644 --- a/src/main/java/fr/moribus/imageonmap/image/ImageIOExecutor.java +++ b/src/main/java/fr/moribus/imageonmap/image/ImageIOExecutor.java @@ -48,14 +48,28 @@ public class ImageIOExecutor extends Worker static public void loadImage(final File file, final Renderer mapRenderer) { - instance.submitQuery(new WorkerRunnable() + instance.submitQuery(new WorkerRunnable() { @Override - public void run() throws Exception + public Void run() throws Exception { BufferedImage image = ImageIO.read(file); mapRenderer.setImage(image); + return null; } }); } + + static public void saveImage(final File file, final BufferedImage image) + { + instance.submitQuery(new WorkerRunnable() + { + @Override + public Void run() throws Throwable + { + ImageIO.write(image, "png", file); + return null; + } + }); + } } diff --git a/src/main/java/fr/moribus/imageonmap/image/ImageRendererExecutor.java b/src/main/java/fr/moribus/imageonmap/image/ImageRendererExecutor.java index f33e355..da2da9f 100644 --- a/src/main/java/fr/moribus/imageonmap/image/ImageRendererExecutor.java +++ b/src/main/java/fr/moribus/imageonmap/image/ImageRendererExecutor.java @@ -18,9 +18,21 @@ package fr.moribus.imageonmap.image; +import fr.moribus.imageonmap.ImageOnMap; +import fr.moribus.imageonmap.PluginLogger; +import fr.moribus.imageonmap.map.ImageMap; +import fr.moribus.imageonmap.map.MapManager; import fr.moribus.imageonmap.worker.Worker; import fr.moribus.imageonmap.worker.WorkerCallback; import fr.moribus.imageonmap.worker.WorkerRunnable; +import java.awt.Graphics; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.net.URL; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import javax.imageio.ImageIO; public class ImageRendererExecutor extends Worker { @@ -41,19 +53,132 @@ public class ImageRendererExecutor extends Worker private ImageRendererExecutor() { - super("Image IO"); + super("Image IO", true); } static public void Test(WorkerCallback callback) { - instance.submitQuery(new WorkerRunnable() + instance.submitQuery(new WorkerRunnable() { @Override - public void run() throws Throwable + public Void run() throws Throwable { Thread.sleep(5000); + return null; } }, callback); } + static public void Render(final URL url, final boolean scaling, final UUID playerUUID, WorkerCallback callback) + { + instance.submitQuery(new WorkerRunnable() + { + @Override + public ImageMap run() throws Throwable + { + final BufferedImage image = ImageIO.read(url); + if(image == null) throw new IOException("The given URL is not a valid image"); + + if(scaling) return RenderSingle(image, playerUUID); + else return RenderPoster(image, playerUUID); + } + }, callback); + } + + static private ImageMap RenderSingle(final BufferedImage image, final UUID playerUUID) throws Throwable + { + final short mapID = instance.submitToMainThread(new Callable() + { + @Override + public Short call() throws Exception + { + return MapManager.getNewMapsIds(1)[0]; + } + }).get(); + + final BufferedImage finalImage = ResizeImage(image, ImageMap.WIDTH, ImageMap.HEIGHT); + + ImageIOExecutor.saveImage(ImageOnMap.getPlugin().getImageFile(mapID), finalImage); + + final ImageMap newMap = instance.submitToMainThread(new Callable() + { + @Override + public ImageMap call() throws Exception + { + Renderer.installRenderer(finalImage, mapID); + return MapManager.createMap(playerUUID, mapID); + } + + }).get(); + + return newMap; + } + + static private ImageMap RenderPoster(final BufferedImage image, final UUID playerUUID) throws Throwable + { + final PosterImage poster = new PosterImage(image); + final int mapCount = poster.getImagesCount(); + + final Future futureMapsIds = instance.submitToMainThread(new Callable() + { + @Override + public short[] call() throws Exception + { + return MapManager.getNewMapsIds(mapCount); + } + }); + + poster.splitImages(); + + final short[] mapsIDs = futureMapsIds.get(); + + for(short mapID : mapsIDs) + { + ImageIOExecutor.saveImage(ImageOnMap.getPlugin().getImageFile(mapID), image); + } + + final ImageMap newMap = instance.submitToMainThread(new Callable() + { + @Override + public ImageMap call() throws Exception + { + Renderer.installRenderer(poster, mapsIDs); + return MapManager.createMap(poster, playerUUID, mapsIDs); + } + + }).get(); + + return newMap; + } + + static private BufferedImage ResizeImage(BufferedImage source, int destinationW, int destinationH) + { + float ratioW = (float)destinationW / (float)source.getWidth(); + float ratioH = (float)destinationH / (float)source.getHeight(); + int finalW, finalH; + + if(ratioW < ratioH) + { + finalW = destinationW; + finalH = (int)(source.getHeight() * ratioW); + } + else + { + finalW = (int)(source.getWidth() * ratioH); + finalH = destinationH; + } + + int x, y; + x = (destinationW - finalW) / 2; + y = (destinationH - finalH) / 2; + PluginLogger.LogInfo(finalW + " " + finalH + " : " + x + " " + y); + + BufferedImage newImage = new BufferedImage(destinationW, destinationH, BufferedImage.TYPE_INT_ARGB); + + Graphics graphics = newImage.getGraphics(); + graphics.drawImage(source, x, y, finalW, finalH, null); + graphics.dispose(); + return newImage; + } + } diff --git a/src/main/java/fr/moribus/imageonmap/image/PosterImage.java b/src/main/java/fr/moribus/imageonmap/image/PosterImage.java index de01cfa..e096d4c 100644 --- a/src/main/java/fr/moribus/imageonmap/image/PosterImage.java +++ b/src/main/java/fr/moribus/imageonmap/image/PosterImage.java @@ -29,6 +29,7 @@ public class PosterImage static private final int WIDTH = 128; static private final int HEIGHT = 128; + private BufferedImage originalImage; private BufferedImage[] cutImages; private int lines; private int columns; @@ -36,15 +37,16 @@ public class PosterImage private int remainderX, remainderY; /** - * Creates and splits a new Poster from an entire image + * Creates a new Poster from an entire image * @param originalImage the original image */ public PosterImage(BufferedImage originalImage) { - splitImages(originalImage); + this.originalImage = originalImage; + calculateDimensions(); } - private void splitImages(BufferedImage originalImage) + private void calculateDimensions() { int originalWidth = originalImage.getWidth(); int originalHeight = originalImage.getHeight(); @@ -59,6 +61,10 @@ public class PosterImage if(remainderY > 0) lines++; cutImagesCount = columns * lines; + } + + public void splitImages() + { cutImages = new BufferedImage[cutImagesCount]; int imageX; @@ -73,6 +79,8 @@ public class PosterImage } imageY += HEIGHT; } + + originalImage = null; } /** diff --git a/src/main/java/fr/moribus/imageonmap/image/Renderer.java b/src/main/java/fr/moribus/imageonmap/image/Renderer.java index 88d14c1..8cf4d8a 100644 --- a/src/main/java/fr/moribus/imageonmap/image/Renderer.java +++ b/src/main/java/fr/moribus/imageonmap/image/Renderer.java @@ -19,6 +19,7 @@ package fr.moribus.imageonmap.image; import java.awt.image.BufferedImage; +import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.map.MapCanvas; import org.bukkit.map.MapRenderer; @@ -35,6 +36,19 @@ public class Renderer extends MapRenderer return false; } + static public void installRenderer(PosterImage image, short[] mapsIds) + { + for(int i = 0; i < mapsIds.length; i++) + { + installRenderer(image.getImageAt(i), mapsIds[i]); + } + } + + static public void installRenderer(BufferedImage image, short mapID) + { + installRenderer(Bukkit.getMap(mapID)).setImage(image); + } + static public Renderer installRenderer(MapView map) { Renderer renderer = new Renderer(); diff --git a/src/main/java/fr/moribus/imageonmap/map/ImageMap.java b/src/main/java/fr/moribus/imageonmap/map/ImageMap.java index 000346f..fbaa806 100644 --- a/src/main/java/fr/moribus/imageonmap/map/ImageMap.java +++ b/src/main/java/fr/moribus/imageonmap/map/ImageMap.java @@ -21,8 +21,11 @@ package fr.moribus.imageonmap.map; import java.util.HashMap; import java.util.Map; import java.util.UUID; +import org.bukkit.Material; import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; public abstract class ImageMap implements ConfigurationSerializable { @@ -48,6 +51,16 @@ public abstract class ImageMap implements ConfigurationSerializable public abstract short[] getMapsIDs(); public abstract boolean managesMap(short mapID); + public void give(Inventory inventory) + { + short[] mapsIDs = getMapsIDs(); + for(short mapID : mapsIDs) + { + ItemStack itemMap = new ItemStack(Material.MAP, 1, mapID); + inventory.addItem(itemMap); + } + } + /* ====== Serialization methods ====== */ static public ImageMap fromConfig(Map map, UUID userUUID) throws InvalidConfigurationException @@ -73,7 +86,7 @@ public abstract class ImageMap implements ConfigurationSerializable protected ImageMap(Map map, UUID userUUID, Type mapType) throws InvalidConfigurationException { this(userUUID, mapType); - this.imageName = getFieldValue(map, "name"); + this.imageName = getNullableFieldValue(map, "name"); } protected abstract void postSerialize(Map map); @@ -84,10 +97,18 @@ public abstract class ImageMap implements ConfigurationSerializable Map map = new HashMap(); map.put("type", mapType.toString()); map.put("name", imageName); + this.postSerialize(map); return map; } static protected T getFieldValue(Map map, String fieldName) throws InvalidConfigurationException + { + T value = getNullableFieldValue(map, fieldName); + if(value == null) throw new InvalidConfigurationException("Field value not found for \"" + fieldName + "\""); + return value; + } + + static protected T getNullableFieldValue(Map map, String fieldName) throws InvalidConfigurationException { try { diff --git a/src/main/java/fr/moribus/imageonmap/map/MapManager.java b/src/main/java/fr/moribus/imageonmap/map/MapManager.java index 2f5128d..a6cec80 100644 --- a/src/main/java/fr/moribus/imageonmap/map/MapManager.java +++ b/src/main/java/fr/moribus/imageonmap/map/MapManager.java @@ -19,6 +19,7 @@ package fr.moribus.imageonmap.map; import fr.moribus.imageonmap.ImageOnMap; +import fr.moribus.imageonmap.image.PosterImage; import java.util.ArrayList; import java.util.UUID; import org.bukkit.Bukkit; @@ -37,8 +38,8 @@ abstract public class MapManager static public void exit() { - playerMaps.clear(); save(); + playerMaps.clear(); if(autosaveTask != null) autosaveTask.cancel(); } @@ -54,6 +55,43 @@ abstract public class MapManager return false; } + static public ImageMap createMap(UUID playerUUID, short mapID) + { + ImageMap newMap = new SingleMap(playerUUID, mapID); + addMap(newMap, playerUUID); + return newMap; + } + + static public ImageMap createMap(PosterImage image, UUID playerUUID, short[] mapsIDs) + { + ImageMap newMap; + if(image.getImagesCount() == 1) + { + newMap = new SingleMap(playerUUID, mapsIDs[0]); + } + else + { + newMap = new PosterMap(playerUUID, mapsIDs, image.getColumns(), image.getLines()); + } + addMap(newMap, playerUUID); + return newMap; + } + + static public short[] getNewMapsIds(int amount) + { + short[] mapsIds = new short[amount]; + for(int i = 0; i < amount; i++) + { + mapsIds[i] = Bukkit.createMap(Bukkit.getWorlds().get(0)).getId(); + } + return mapsIds; + } + + static public void addMap(ImageMap map, UUID playerUUID) + { + getPlayerMapStore(playerUUID).addMap(map); + } + static public void notifyModification(UUID playerUUID) { getPlayerMapStore(playerUUID).notifyModification(); diff --git a/src/main/java/fr/moribus/imageonmap/map/PlayerMapStore.java b/src/main/java/fr/moribus/imageonmap/map/PlayerMapStore.java index 5ccad9e..cb8bc9a 100644 --- a/src/main/java/fr/moribus/imageonmap/map/PlayerMapStore.java +++ b/src/main/java/fr/moribus/imageonmap/map/PlayerMapStore.java @@ -54,6 +54,12 @@ public class PlayerMapStore implements ConfigurationSerializable return false; } + public void addMap(ImageMap map) + { + mapList.add(map); + notifyModification(); + } + /* ===== Getters & Setters ===== */ public UUID getUUID() @@ -104,7 +110,7 @@ public class PlayerMapStore implements ConfigurationSerializable } catch(InvalidConfigurationException ex) { - PluginLogger.LogWarning("Could not load map data", ex); + PluginLogger.LogWarning("Could not load map data : " + ex.getMessage()); } } } @@ -145,6 +151,7 @@ public class PlayerMapStore implements ConfigurationSerializable { PluginLogger.LogError("Could not save maps file for player " + playerUUID.toString(), ex); } + PluginLogger.LogInfo("Saving maps file for " + playerUUID.toString()); modified = false; } } diff --git a/src/main/java/fr/moribus/imageonmap/map/PosterMap.java b/src/main/java/fr/moribus/imageonmap/map/PosterMap.java index 2627bee..700e725 100644 --- a/src/main/java/fr/moribus/imageonmap/map/PosterMap.java +++ b/src/main/java/fr/moribus/imageonmap/map/PosterMap.java @@ -61,7 +61,7 @@ public class PosterMap extends ImageMap columnCount = getFieldValue(map, "columns"); rowCount = getFieldValue(map, "rows"); - mapsIDs = getFieldValue(map, "mapIDs"); + mapsIDs = getFieldValue(map, "mapsIDs"); } @Override diff --git a/src/main/java/fr/moribus/imageonmap/map/SingleMap.java b/src/main/java/fr/moribus/imageonmap/map/SingleMap.java index 648279b..18e4bd0 100644 --- a/src/main/java/fr/moribus/imageonmap/map/SingleMap.java +++ b/src/main/java/fr/moribus/imageonmap/map/SingleMap.java @@ -49,7 +49,8 @@ public class SingleMap extends ImageMap public SingleMap(Map map, UUID userUUID) throws InvalidConfigurationException { super(map, userUUID, Type.SINGLE); - mapID = getFieldValue(map, "mapID"); + int _mapID = getFieldValue(map, "mapID"); + mapID = (short) _mapID;//Meh } @Override diff --git a/src/main/java/fr/moribus/imageonmap/worker/Worker.java b/src/main/java/fr/moribus/imageonmap/worker/Worker.java index 6dec9df..eb534b5 100644 --- a/src/main/java/fr/moribus/imageonmap/worker/Worker.java +++ b/src/main/java/fr/moribus/imageonmap/worker/Worker.java @@ -20,6 +20,8 @@ package fr.moribus.imageonmap.worker; import fr.moribus.imageonmap.PluginLogger; import java.util.ArrayDeque; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; public abstract class Worker { @@ -27,12 +29,19 @@ public abstract class Worker private final ArrayDeque runQueue = new ArrayDeque<>(); private final WorkerCallbackManager callbackManager; + private final WorkerMainThreadExecutor mainThreadExecutor; private Thread thread; protected Worker(String name) + { + this(name, false); + } + + protected Worker(String name, boolean runMainThreadExecutor) { this.name = name; this.callbackManager = new WorkerCallbackManager(name); + this.mainThreadExecutor = runMainThreadExecutor ? new WorkerMainThreadExecutor(name) : null; } public void init() @@ -43,6 +52,7 @@ public abstract class Worker exit(); } callbackManager.init(); + if(mainThreadExecutor != null) mainThreadExecutor.init(); thread = createThread(); thread.start(); } @@ -51,6 +61,7 @@ public abstract class Worker { thread.interrupt(); callbackManager.exit(); + if(mainThreadExecutor != null) mainThreadExecutor.exit(); thread = null; } @@ -75,12 +86,11 @@ public abstract class Worker try { - currentRunnable.run(); - callbackManager.callback(currentRunnable); + callbackManager.callback(currentRunnable, currentRunnable.run()); } catch(Throwable ex) { - callbackManager.callback(currentRunnable, ex); + callbackManager.callback(currentRunnable, null, ex); } } } @@ -94,12 +104,17 @@ public abstract class Worker } } - protected void submitQuery(WorkerRunnable runnable, WorkerCallback callback, Object... args) + protected void submitQuery(WorkerRunnable runnable, WorkerCallback callback) { - callbackManager.setupCallback(runnable, callback, args); + callbackManager.setupCallback(runnable, callback); submitQuery(runnable); } + protected Future submitToMainThread(Callable callable) + { + if(mainThreadExecutor != null) return mainThreadExecutor.submit(callable); + return null; + } private Thread createThread() { diff --git a/src/main/java/fr/moribus/imageonmap/worker/WorkerCallback.java b/src/main/java/fr/moribus/imageonmap/worker/WorkerCallback.java index 379e6a6..e7c7535 100644 --- a/src/main/java/fr/moribus/imageonmap/worker/WorkerCallback.java +++ b/src/main/java/fr/moribus/imageonmap/worker/WorkerCallback.java @@ -17,8 +17,8 @@ */ package fr.moribus.imageonmap.worker; -public interface WorkerCallback +public interface WorkerCallback { - public void finished(Object... args); + public void finished(T result); public void errored(Throwable exception); } diff --git a/src/main/java/fr/moribus/imageonmap/worker/WorkerCallbackManager.java b/src/main/java/fr/moribus/imageonmap/worker/WorkerCallbackManager.java index c92e2ce..0c5b0e6 100644 --- a/src/main/java/fr/moribus/imageonmap/worker/WorkerCallbackManager.java +++ b/src/main/java/fr/moribus/imageonmap/worker/WorkerCallbackManager.java @@ -26,7 +26,7 @@ import org.bukkit.scheduler.BukkitTask; class WorkerCallbackManager implements Runnable { - static private final int WATCH_LOOP_DELAY = 40; + static private final int WATCH_LOOP_DELAY = 5; private final HashMap callbacks; private final ArrayDeque callbackQueue; @@ -47,28 +47,29 @@ class WorkerCallbackManager implements Runnable selfTask = Bukkit.getScheduler().runTaskTimer(ImageOnMap.getPlugin(), this, 0, WATCH_LOOP_DELAY); } - public void setupCallback(WorkerRunnable runnable, WorkerCallback callback, Object[] args) + public void setupCallback(WorkerRunnable runnable, WorkerCallback callback) { synchronized(callbacks) { - callbacks.put(runnable, new WorkerRunnableInfo(callback, args)); + callbacks.put(runnable, new WorkerRunnableInfo(callback)); } } - public void callback(WorkerRunnable runnable) + public void callback(WorkerRunnable runnable, T result) { - callback(runnable, null); + callback(runnable, result, null); } - public void callback(WorkerRunnable runnable, Throwable exception) + public void callback(WorkerRunnable runnable, T result, Throwable exception) { - WorkerRunnableInfo runnableInfo; + WorkerRunnableInfo runnableInfo; synchronized(callbacks) { runnableInfo = callbacks.get(runnable); } if(runnableInfo == null) return; runnableInfo.setRunnableException(exception); + runnableInfo.setResult(result); enqueueCallback(runnableInfo); } @@ -99,16 +100,15 @@ class WorkerCallbackManager implements Runnable currentRunnableInfo.runCallback(); } - private class WorkerRunnableInfo + private class WorkerRunnableInfo { - private final WorkerCallback callback; - private final Object[] args; + private final WorkerCallback callback; + private T result; private Throwable runnableException; - public WorkerRunnableInfo(WorkerCallback callback, Object[] args) + public WorkerRunnableInfo(WorkerCallback callback) { this.callback = callback; - this.args = args; this.runnableException = null; } @@ -125,9 +125,14 @@ class WorkerCallbackManager implements Runnable } else { - callback.finished(args); + callback.finished(result); } } + + public void setResult(T result) + { + this.result = result; + } public Throwable getRunnableException() { diff --git a/src/main/java/fr/moribus/imageonmap/worker/WorkerMainThreadExecutor.java b/src/main/java/fr/moribus/imageonmap/worker/WorkerMainThreadExecutor.java new file mode 100644 index 0000000..4b4628d --- /dev/null +++ b/src/main/java/fr/moribus/imageonmap/worker/WorkerMainThreadExecutor.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2013 Moribus + * Copyright (C) 2015 ProkopyL + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package fr.moribus.imageonmap.worker; + +import fr.moribus.imageonmap.ImageOnMap; +import java.util.ArrayDeque; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.bukkit.Bukkit; +import org.bukkit.scheduler.BukkitTask; + +class WorkerMainThreadExecutor implements Runnable +{ + static private final int WATCH_LOOP_DELAY = 1; + + private final String name; + private final ArrayDeque mainThreadQueue = new ArrayDeque<>(); + private BukkitTask mainThreadTask; + + public WorkerMainThreadExecutor(String name) + { + this.name = name; + } + + public void init() + { + mainThreadTask = Bukkit.getScheduler().runTaskTimer(ImageOnMap.getPlugin(), this, 0, WATCH_LOOP_DELAY); + } + + public void exit() + { + mainThreadTask.cancel(); + mainThreadTask = null; + } + + public Future submit(Callable callable) + { + WorkerFuture future = new WorkerFuture(callable); + synchronized(mainThreadQueue) + { + mainThreadQueue.add(future); + } + return future; + } + + @Override + public void run() + { + WorkerFuture currentFuture; + synchronized(mainThreadQueue) + { + if(mainThreadQueue.isEmpty()) return; + currentFuture = mainThreadQueue.pop(); + } + + currentFuture.runCallable(); + } + + private class WorkerFuture implements Future + { + private final Callable callable; + private boolean isCancelled; + private boolean isDone; + private Exception executionException; + private T value; + + public WorkerFuture(Callable callable) + { + this.callable = callable; + } + + public void runCallable() + { + try + { + value = callable.call(); + } + catch(Exception ex) + { + executionException = ex; + } + finally + { + isDone = true; + synchronized(this){this.notifyAll();} + } + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) + { + if(this.isCancelled || this.isDone) return false; + this.isCancelled = true; + this.isDone = true; + return true; + } + + @Override + public boolean isCancelled() + { + return this.isCancelled; + } + + @Override + public boolean isDone() + { + return this.isDone; + } + + @Override + public T get() throws InterruptedException, ExecutionException + { + waitForCompletion(); + if(executionException != null) throw new ExecutionException(executionException); + return value; + } + + @Override + public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException + { + waitForCompletion(timeout, unit); + if(executionException != null) throw new ExecutionException(executionException); + return value; + } + + private void waitForCompletion(long timeout) throws InterruptedException, TimeoutException + { + synchronized(this) + { + long remainingTime; + long timeoutTime = System.currentTimeMillis() + timeout; + while(!isDone) + { + remainingTime = timeoutTime - System.currentTimeMillis(); + if(remainingTime <= 0) throw new TimeoutException(); + this.wait(remainingTime); + } + } + } + + private void waitForCompletion() throws InterruptedException + { + synchronized(this) + { + while(!isDone) this.wait(); + } + } + + private void waitForCompletion(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException + { + long millis = 0; + switch(unit) + { + case NANOSECONDS: + millis = timeout / 10^6; + break; + case MICROSECONDS: + millis = timeout / 10^3; + break; + case MILLISECONDS: + millis = timeout; + break; + case SECONDS: + millis = timeout * 10^3; + break; + case MINUTES: + millis = timeout * 10^3 * 60; + break; + case HOURS: + millis = timeout * 10^3 * 3600; + break; + case DAYS: + millis = timeout * 10^3 * 3600 * 24; + } + waitForCompletion(millis); + } + + + } +} diff --git a/src/main/java/fr/moribus/imageonmap/worker/WorkerRunnable.java b/src/main/java/fr/moribus/imageonmap/worker/WorkerRunnable.java index 0bf71c8..dbf4300 100644 --- a/src/main/java/fr/moribus/imageonmap/worker/WorkerRunnable.java +++ b/src/main/java/fr/moribus/imageonmap/worker/WorkerRunnable.java @@ -17,7 +17,7 @@ */ package fr.moribus.imageonmap.worker; -public interface WorkerRunnable +public interface WorkerRunnable { - public void run() throws Throwable; + public T run() throws Throwable; }