package cn.dreampie.common.plugin.lesscss.compiler;
import org.codehaus.plexus.util.StringUtils;
import org.lesscss.LessCompiler;
import org.lesscss.LessException;
import org.lesscss.LessSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Arrays;
/**
* Created by wangrenhui on 2014/7/11.
*/
public class LessCssCompiler extends AbstractLessCss {
private Logger logger = LoggerFactory.getLogger(getClass());
private Object lessCompiler;
/**
* The directory for compiled CSS stylesheets.
* <p/>
* parameter expression="${lesscss.outputDirectory}" default-value="${project.build.directory}"
* required
*/
protected File outputDirectory;
/**
* When <code>true</code> the LESS compiler will compress the CSS stylesheets.
* <p/>
* parameter expression="${lesscss.compress}" default-value="false"
*/
private boolean compress;
/**
* When <code>true</code> the plugin will watch for changes in LESS files and compile if it detects one.
* <p/>
* parameter expression="${lesscss.watch}" default-value="false"
*/
protected boolean watch = false;
/**
* When <code>true</code> the plugin will watch for changes in LESS files and compile if it detects one.
* <p/>
* parameter expression="${lesscss.watchInterval}" default-value="1000"
*/
private int watchInterval = 1000;
/**
* The character encoding the LESS compiler will use for writing the CSS stylesheets.
* <p/>
* parameter expression="${lesscss.encoding}" default-value="${project.build.sourceEncoding}"
*/
private String encoding;
/**
* When <code>true</code> forces the LESS compiler to always compile the LESS sources. By default LESS sources are only compiled when modified (including imports) or the CSS stylesheet does not exists.
* <p/>
* parameter expression="${lesscss.force}" default-value="false"
*/
private boolean force;
/**
* The location of the LESS JavasSript file.
* <p/>
* parameter
*/
private File lessJs;
/**
* The location of the NodeJS executable.
* <p/>
* parameter
*/
private String nodeExecutable;
/**
* The format of the output file names.
* <p/>
* parameter
*/
private String outputFileFormat;
private static final String FILE_NAME_FORMAT_PARAMETER_REGEX = "\\{fileName\\}";
private long lastErrorModified = 0;
/**
* Execute the MOJO.
*
* @throws cn.dreampie.common.plugin.lesscss.compiler.LessCssException if something unexpected occurs.
*/
public void execute() throws LessCssException {
if (logger.isDebugEnabled()) {
logger.debug("sourceDirectory = " + sourceDirectory);
logger.debug("outputDirectory = " + outputDirectory);
logger.debug("includes = " + Arrays.toString(includes));
logger.debug("excludes = " + Arrays.toString(excludes));
logger.debug("force = " + force);
logger.debug("lessJs = " + lessJs);
logger.debug("skip = " + skip);
}
if (!skip) {
// Akka.system().scheduler().scheduleOnce(Duration.create(watchInterval, TimeUnit.MILLISECONDS),
// new Runnable() {
// @Override
// public void run() {
if (watch) {
logger.info("Watching " + sourceDirectory);
if (force) {
force = false;
logger.info("Disabled the 'force' flag in watch mode.");
}
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
while (watch && !Thread.currentThread().isInterrupted()) {
executeInternal();
try {
Thread.sleep(watchInterval);
} catch (InterruptedException e) {
logger.error("interrupted");
}
}
} else {
executeInternal();
}
// }
// }, Akka.system().dispatcher());
} else {
logger.info("Skipping plugin execution per configuration");
}
}
private void executeInternal() throws LessCssException {
long start = System.currentTimeMillis();
String[] files = getIncludedFiles();
if (files == null || files.length < 1) {
logger.info("Nothing to compile - no LESS sources found");
} else {
if (logger.isDebugEnabled()) {
logger.debug("included files = " + Arrays.toString(files));
}
Object lessCompiler = initLessCompiler();
// if (watch) {
// logger.info("Watching " + sourceDirectory);
// if (force) {
// force = false;
// logger.info("Disabled the 'force' flag in watch mode.");
// }
// Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
// while (watch && !Thread.currentThread().isInterrupted()) {
// compileIfChanged(files, lessCompiler);
// try {
// Thread.sleep(watchInterval);
// } catch (InterruptedException e) {
// logger.error("interrupted");
// }
// }
// } else {
compileIfChanged(files, lessCompiler);
// }
// logger.info("Complete Less compile job finished in " + (System.currentTimeMillis() - start) + " ms");
}
}
private void compileIfChanged(String[] files, Object lessCompiler) throws LessCssException {
try {
for (String file : files) {
File input = new File(sourceDirectory, file);
buildContext.removeMessages(input);
if (outputFileFormat != null) {
file = outputFileFormat.replaceAll(FILE_NAME_FORMAT_PARAMETER_REGEX, file.replace(".less", ""));
}
File output = new File(outputDirectory, file.replace(".less", ".css"));
if (!output.getParentFile().exists() && !output.getParentFile().mkdirs()) {
throw new LessCssException("Cannot create output directory " + output.getParentFile());
}
try {
LessSource lessSource = new LessSource(input);
long lessLastModified = lessSource.getLastModifiedIncludingImports();
if ((force || !output.exists() || output.lastModified() < lessLastModified) && lastErrorModified < lessLastModified) {
lastErrorModified = lessLastModified;
long compilationStarted = System.currentTimeMillis();
logger.info("Compiling LESS source: " + file + "...");
if (lessCompiler instanceof LessCompiler) {
((LessCompiler) lessCompiler).compile(lessSource, output, force);
} else {
((NodeJsLessCssCompiler) lessCompiler).compile(lessSource, output, force);
}
buildContext.refresh(output);
logger.info("Finished compilation to " + outputDirectory + " in " + (System.currentTimeMillis() - compilationStarted) + " ms");
} else if (!watch) {
logger.info("Bypassing LESS source: " + file + " (not modified)");
}
} catch (IOException e) {
// buildContext.addMessage(input, 0, 0, "Error compiling LESS source", BuildContext.SEVERITY_ERROR, e);
throw new LessCssException("Error while compiling LESS source: " + file, e);
} catch (LessException e) {
String message = e.getMessage();
if (StringUtils.isEmpty(message)) {
message = "Error compiling LESS source";
}
// buildContext.addMessage(input, 0, 0, "Error compiling LESS source", BuildContext.SEVERITY_ERROR, e);
throw new LessCssException("Error while compiling LESS source: " + file, e);
} catch (InterruptedException e) {
// buildContext.addMessage(input, 0, 0, "Error compiling LESS source", BuildContext.SEVERITY_ERROR, e);
throw new LessCssException("Error while compiling LESS source: " + file, e);
}
}
} finally {
if (lessCompiler instanceof NodeJsLessCssCompiler) {
((NodeJsLessCssCompiler) lessCompiler).close();
}
}
}
private Object initLessCompiler() throws LessCssException {
if (lessCompiler == null) {
if (nodeExecutable != null) {
NodeJsLessCssCompiler nodeJsLessCssCompiler;
try {
nodeJsLessCssCompiler = new NodeJsLessCssCompiler(nodeExecutable, compress, encoding, logger);
} catch (IOException e) {
throw new LessCssException(e.getMessage(), e);
}
if (lessJs != null) {
throw new LessCssException(
"Custom LESS JavaScript is not currently supported when using nodeExecutable");
}
lessCompiler = nodeJsLessCssCompiler;
} else {
LessCompiler newLessCompiler = new LessCompiler();
newLessCompiler.setCompress(compress);
newLessCompiler.setEncoding(encoding);
if (lessJs != null) {
try {
newLessCompiler.setLessJs(lessJs.toURI().toURL());
} catch (MalformedURLException e) {
throw new LessCssException(
"Error while loading LESS JavaScript: " + lessJs.getAbsolutePath(), e);
}
}
lessCompiler = newLessCompiler;
}
}
return lessCompiler;
}
public File getOutputDirectory() {
return outputDirectory;
}
public void setOutputDirectory(File outputDirectory) {
this.outputDirectory = outputDirectory;
}
public boolean isCompress() {
return compress;
}
public void setCompress(boolean compress) {
this.compress = compress;
}
public boolean isWatch() {
return watch;
}
public void setWatch(boolean watch) {
this.watch = watch;
}
public int getWatchInterval() {
return watchInterval;
}
public void setWatchInterval(int watchInterval) {
this.watchInterval = watchInterval;
}
public String getEncoding() {
return encoding;
}
public void setEncoding(String encoding) {
this.encoding = encoding;
}
public boolean isForce() {
return force;
}
public void setForce(boolean force) {
this.force = force;
}
public File getLessJs() {
return lessJs;
}
public void setLessJs(File lessJs) {
this.lessJs = lessJs;
}
public String getNodeExecutable() {
return nodeExecutable;
}
public void setNodeExecutable(String nodeExecutable) {
this.nodeExecutable = nodeExecutable;
}
public String getOutputFileFormat() {
return outputFileFormat;
}
public void setOutputFileFormat(String outputFileFormat) {
this.outputFileFormat = outputFileFormat;
}
public long getLastErrorModified() {
return lastErrorModified;
}
public void setLastErrorModified(long lastErrorModified) {
this.lastErrorModified = lastErrorModified;
}
}