/** * */ package org.smartly.commons.image.resize; import org.smartly.commons.image.resize.Resize.Method; import org.smartly.commons.image.resize.Resize.Mode; import org.smartly.commons.image.resize.Resize.Rotation; import java.awt.*; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.ImagingOpException; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; /** */ @SuppressWarnings("javadoc") public class AsyncResize { /** * System property name used to set the number of threads the default * underlying {@link ExecutorService} will use to process async image * operations. * <p/> * Value is "<code>imgscalr.async.threadCount</code>". */ public static final String THREAD_COUNT_PROPERTY_NAME = "imgscalr.async.threadCount"; /** * Number of threads the internal {@link ExecutorService} will use to * simultaneously execute scale requests. * <p/> * This value can be changed by setting the * <code>imgscalr.async.threadCount</code> system property (see * {@link #THREAD_COUNT_PROPERTY_NAME}) to a valid integer value > 0. * <p/> * Default value is <code>2</code>. */ public static final int THREAD_COUNT = Integer.getInteger( THREAD_COUNT_PROPERTY_NAME, 2); /** * Initializer used to verify the THREAD_COUNT system property. */ static { if (THREAD_COUNT < 1) throw new RuntimeException("System property '" + THREAD_COUNT_PROPERTY_NAME + "' set THREAD_COUNT to " + THREAD_COUNT + ", but THREAD_COUNT must be > 0."); } protected static ExecutorService service; /** * Used to get access to the internal {@link ExecutorService} used by this * class to process scale operations. * <p/> * <strong>NOTE</strong>: You will need to explicitly shutdown any service * currently set on this class before the host JVM exits. * <p/> * You can call {@link ExecutorService#shutdown()} to wait for all scaling * operations to complete first or call * {@link ExecutorService#shutdownNow()} to kill any in-process operations * and purge all pending operations before exiting. * <p/> * Additionally you can use * {@link ExecutorService#awaitTermination(long, TimeUnit)} after issuing a * shutdown command to try and wait until the service has finished all * tasks. * * @return the current {@link ExecutorService} used by this class to process * scale operations. */ public static ExecutorService getService() { return service; } /** * @see Resize#apply(BufferedImage, BufferedImageOp...) */ public static Future<BufferedImage> apply(final BufferedImage src, final BufferedImageOp... ops) throws IllegalArgumentException, ImagingOpException { checkService(); return service.submit(new Callable<BufferedImage>() { public BufferedImage call() throws Exception { return Resize.apply(src, ops); } }); } /** * @see Resize#crop(BufferedImage, int, int, BufferedImageOp...) */ public static Future<BufferedImage> crop(final BufferedImage src, final int width, final int height, final BufferedImageOp... ops) throws IllegalArgumentException, ImagingOpException { checkService(); return service.submit(new Callable<BufferedImage>() { public BufferedImage call() throws Exception { return Resize.crop(src, width, height, ops); } }); } /** * @see Resize#crop(BufferedImage, int, int, int, int, BufferedImageOp...) */ public static Future<BufferedImage> crop(final BufferedImage src, final int x, final int y, final int width, final int height, final BufferedImageOp... ops) throws IllegalArgumentException, ImagingOpException { checkService(); return service.submit(new Callable<BufferedImage>() { public BufferedImage call() throws Exception { return Resize.crop(src, x, y, width, height, ops); } }); } /** * @see Resize#pad(BufferedImage, int, BufferedImageOp...) */ public static Future<BufferedImage> pad(final BufferedImage src, final int padding, final BufferedImageOp... ops) throws IllegalArgumentException, ImagingOpException { checkService(); return service.submit(new Callable<BufferedImage>() { public BufferedImage call() throws Exception { return Resize.pad(src, padding, ops); } }); } /** * @see Resize#pad(BufferedImage, int, Color, BufferedImageOp...) */ public static Future<BufferedImage> pad(final BufferedImage src, final int padding, final Color color, final BufferedImageOp... ops) throws IllegalArgumentException, ImagingOpException { checkService(); return service.submit(new Callable<BufferedImage>() { public BufferedImage call() throws Exception { return Resize.pad(src, padding, color, ops); } }); } /** * @see Resize#resize(BufferedImage, int, BufferedImageOp...) */ public static Future<BufferedImage> resize(final BufferedImage src, final int targetSize, final BufferedImageOp... ops) throws IllegalArgumentException, ImagingOpException { checkService(); return service.submit(new Callable<BufferedImage>() { public BufferedImage call() throws Exception { return Resize.resize(src, targetSize, ops); } }); } /** * @see Resize#resize(BufferedImage, Method, int, BufferedImageOp...) */ public static Future<BufferedImage> resize(final BufferedImage src, final Method scalingMethod, final int targetSize, final BufferedImageOp... ops) throws IllegalArgumentException, ImagingOpException { checkService(); return service.submit(new Callable<BufferedImage>() { public BufferedImage call() throws Exception { return Resize.resize(src, scalingMethod, targetSize, ops); } }); } public static Future<BufferedImage> resize(final BufferedImage src, final Resize.Mode resizeMode, final int targetSize, final BufferedImageOp... ops) throws IllegalArgumentException, ImagingOpException { checkService(); return service.submit(new Callable<BufferedImage>() { public BufferedImage call() throws Exception { return Resize.resize(src, resizeMode, targetSize, ops); } }); } public static Future<BufferedImage> resize(final BufferedImage src, final Method scalingMethod, final Resize.Mode resizeMode, final int targetSize, final BufferedImageOp... ops) throws IllegalArgumentException, ImagingOpException { checkService(); return service.submit(new Callable<BufferedImage>() { public BufferedImage call() throws Exception { return Resize.resize(src, scalingMethod, resizeMode, targetSize, ops); } }); } /** * @see Resize#resize(BufferedImage, int, int, BufferedImageOp...) */ public static Future<BufferedImage> resize(final BufferedImage src, final int targetWidth, final int targetHeight, final BufferedImageOp... ops) throws IllegalArgumentException, ImagingOpException { checkService(); return service.submit(new Callable<BufferedImage>() { public BufferedImage call() throws Exception { return Resize.resize(src, targetWidth, targetHeight, ops); } }); } /** * @see Resize#resize(BufferedImage, Method, int, int, BufferedImageOp...) */ public static Future<BufferedImage> resize(final BufferedImage src, final Method scalingMethod, final int targetWidth, final int targetHeight, final BufferedImageOp... ops) { checkService(); return service.submit(new Callable<BufferedImage>() { public BufferedImage call() throws Exception { return Resize.resize(src, scalingMethod, targetWidth, targetHeight, ops); } }); } /** * @see Resize#resize(BufferedImage, Mode, int, int, BufferedImageOp...) */ public static Future<BufferedImage> resize(final BufferedImage src, final Mode resizeMode, final int targetWidth, final int targetHeight, final BufferedImageOp... ops) throws IllegalArgumentException, ImagingOpException { checkService(); return service.submit(new Callable<BufferedImage>() { public BufferedImage call() throws Exception { return Resize.resize(src, resizeMode, targetWidth, targetHeight, ops); } }); } /** * @see Resize#resize(BufferedImage, Method, Mode, int, int, * BufferedImageOp...) */ public static Future<BufferedImage> resize(final BufferedImage src, final Method scalingMethod, final Mode resizeMode, final int targetWidth, final int targetHeight, final BufferedImageOp... ops) throws IllegalArgumentException, ImagingOpException { checkService(); return service.submit(new Callable<BufferedImage>() { public BufferedImage call() throws Exception { return Resize.resize(src, scalingMethod, resizeMode, targetWidth, targetHeight, ops); } }); } /** * @see Resize#rotate(BufferedImage, Rotation, BufferedImageOp...) */ public static Future<BufferedImage> rotate(final BufferedImage src, final Resize.Rotation rotation, final BufferedImageOp... ops) throws IllegalArgumentException, ImagingOpException { checkService(); return service.submit(new Callable<BufferedImage>() { public BufferedImage call() throws Exception { return Resize.rotate(src, rotation, ops); } }); } protected static ExecutorService createService() { return createService(new DefaultThreadFactory()); } protected static ExecutorService createService(ThreadFactory factory) throws IllegalArgumentException { if (factory == null) throw new IllegalArgumentException("factory cannot be null"); return Executors.newFixedThreadPool(THREAD_COUNT, factory); } /** * Used to verify that the underlying <code>service</code> points at an * active {@link ExecutorService} instance that can be used by this class. * <p/> * If <code>service</code> is <code>null</code>, has been shutdown or * terminated then this method will replace it with a new * {@link ExecutorService} by calling the {@link #createService()} method * and assigning the returned value to <code>service</code>. * <p/> * Any subclass that wants to customize the {@link ExecutorService} or * {@link ThreadFactory} used internally by this class should override the * {@link #createService()}. */ protected static void checkService() { if (service == null || service.isShutdown() || service.isTerminated()) { /* * If service was shutdown or terminated, assigning a new value will * free the reference to the instance, allowing it to be GC'ed when * it is done shutting down (assuming it hadn't already). */ service = createService(); } } /** * Default {@link ThreadFactory} used by the internal * {@link ExecutorService} to creates execution {@link Thread}s for image * scaling. * <p/> * More or less a copy of the hidden class backing the * {@link Executors#defaultThreadFactory()} method, but exposed here to make * it easier for implementors to extend and customize. * * @author Doug Lea * @author Riyad Kalla (software@thebuzzmedia.com) * @since 4.0 */ protected static class DefaultThreadFactory implements ThreadFactory { protected static final AtomicInteger poolNumber = new AtomicInteger(1); protected final ThreadGroup group; protected final AtomicInteger threadNumber = new AtomicInteger(1); protected final String namePrefix; DefaultThreadFactory() { SecurityManager manager = System.getSecurityManager(); /* * Determine the group that threads created by this factory will be * in. */ group = (manager == null ? Thread.currentThread().getThreadGroup() : manager.getThreadGroup()); /* * Define a common name prefix for the threads created by this * factory. */ namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-"; } /** * Used to create a {@link Thread} capable of executing the given * {@link Runnable}. * <p/> * Thread created by this factory are utilized by the parent * {@link ExecutorService} when processing queued up scale operations. */ public Thread newThread(Runnable r) { /* * Create a new thread in our specified group with a meaningful * thread name so it is easy to identify. */ Thread thread = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); // Configure thread according to class or subclass thread.setDaemon(false); thread.setPriority(Thread.NORM_PRIORITY); return thread; } } /** * An extension of the {@link DefaultThreadFactory} class that makes two * changes to the execution {@link Thread}s it generations: * <ol> * <li>Threads are set to be daemon threads instead of user threads.</li> * <li>Threads execute with a priority of {@link Thread#MIN_PRIORITY} to * make them more compatible with server environment deployments.</li> * </ol> * This class is provided as a convenience for subclasses to use if they * want this (common) customization to the {@link Thread}s used internally * by {@link AsyncResize} to process images, but don't want to have to write * the implementation. * * @author Riyad Kalla (software@thebuzzmedia.com) * @since 4.0 */ protected static class ServerThreadFactory extends DefaultThreadFactory { /** * Overridden to set <code>daemon</code> property to <code>true</code> * and decrease the priority of the new thread to * {@link Thread#MIN_PRIORITY} before returning it. */ @Override public Thread newThread(Runnable r) { Thread thread = super.newThread(r); thread.setDaemon(true); thread.setPriority(Thread.MIN_PRIORITY); return thread; } } }