package com.github.sommeri.less4j.core; import java.io.File; import java.net.URL; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import com.github.sommeri.less4j.Less4jException; import com.github.sommeri.less4j.LessCompiler; import com.github.sommeri.less4j.LessSource; import com.github.sommeri.less4j.core.ast.ASTCssNode; import com.github.sommeri.less4j.core.problems.BugHappened; public class TimeoutedLessCompiler implements LessCompiler { private final long timeout; private final TimeUnit unit; private final long afterInterruptTimeout; private final TimeUnit afterInterruptUnit; public TimeoutedLessCompiler(long timeout, TimeUnit unit) { this(timeout, unit, 80, TimeUnit.MILLISECONDS); } public TimeoutedLessCompiler(long timeout, TimeUnit unit, long afterInterruptTimeout, TimeUnit afterInterruptunit) { super(); this.timeout = timeout; this.unit = unit; this.afterInterruptTimeout = afterInterruptTimeout; this.afterInterruptUnit = afterInterruptunit; } @Override public CompilationResult compile(String lessContent) throws Less4jException { return compile(new LessSource.StringSource(lessContent), null); } @Override public CompilationResult compile(String lessContent, Configuration options) throws Less4jException { return compile(new LessSource.StringSource(lessContent), options); } @Override public CompilationResult compile(File lessFile) throws Less4jException { LessSource.FileSource lessSource = new LessSource.FileSource(lessFile); return compile(lessSource, null); } @Override public CompilationResult compile(File lessFile, Configuration options) throws Less4jException { return compile(new LessSource.FileSource(lessFile, "utf-8"), options); } @Override public CompilationResult compile(URL lessURL) throws Less4jException { return compile(new LessSource.URLSource(lessURL)); } @Override public CompilationResult compile(URL lessURL, Configuration options) throws Less4jException { return compile(new LessSource.URLSource(lessURL), options); } @Override public CompilationResult compile(LessSource source) throws Less4jException { return compile(source, new Configuration()); } @Override public CompilationResult compile(LessSource source, Configuration options) throws Less4jException { final LessSource iSource = source; final Configuration iOption = options; Callable<CompilationResult> task = new Callable<CompilationResult>() { public CompilationResult call() { ThreadUnsafeLessCompiler compiler = new ThreadUnsafeLessCompiler(); try { return compiler.compile(iSource, iOption); } catch (Less4jException ex) { throw new Less4jRuntimeException(ex); } } }; ExecutorService executor = Executors.newSingleThreadExecutor(); Future<CompilationResult> future = executor.submit(task); try { boolean terminatedNormally = executor.awaitTermination(timeout, unit); if (terminatedNormally) { CompilationResult result = future.get(); return result; } else { executor.shutdownNow(); executor.awaitTermination(afterInterruptTimeout, afterInterruptUnit); return future.get(); } } catch (CancellationException e) { throw new BugHappened("Unexpected future cancellation.", (ASTCssNode) null); } catch (InterruptedException e) { throw new BugHappened("Unexpected thread interrupt.", (ASTCssNode) null); } catch (ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof Less4jRuntimeException) { Less4jRuntimeException wrapper = (Less4jRuntimeException) cause; throw (Less4jException) wrapper.getCause(); } if (cause instanceof RuntimeException) { RuntimeException runtimeException = (RuntimeException) cause; throw runtimeException; } if (cause instanceof Error) { Error error = (Error) cause; throw error; } throw new RuntimeException("Unexpected state, this should not be possible", cause); } } @SuppressWarnings("serial") private class Less4jRuntimeException extends RuntimeException { public Less4jRuntimeException(Less4jException ex) { super(ex); } } }