package io.mangoo.utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.github.sommeri.less4j.Less4jException;
import com.github.sommeri.less4j.LessCompiler;
import com.github.sommeri.less4j.LessCompiler.CompilationResult;
import com.github.sommeri.less4j.core.DefaultLessCompiler;
import io.bit3.jsass.CompilationException;
import io.bit3.jsass.Compiler;
import io.bit3.jsass.Options;
import io.bit3.jsass.Output;
import io.mangoo.configuration.Config;
import io.mangoo.enums.Default;
import io.mangoo.enums.Jvm;
import io.mangoo.enums.Mode;
import io.mangoo.enums.Suffix;
import net.jawr.web.minification.CSSMinifier;
import net.jawr.web.minification.JSMin;
import net.jawr.web.minification.JSMin.JSMinException;
/**
* Convenient class for minification of CSS and JS files
*
*
* @author svenkubiak
*
*/
@SuppressWarnings("all")
public final class MinificationUtils {
private static final Logger LOG = LogManager.getLogger(MinificationUtils.class);
private static final int HUNDRED_PERCENT = 100;
private static final String JS = "js";
private static final String CSS = "css";
private static final String LESS = "less";
private static final String SASS = "sass";
private static final String MIN = "min";
private static String basePath;
private static volatile Config config; //NOSONAR
private MinificationUtils() {
config = new Config(basePath + Default.CONFIG_PATH.toString(), Mode.DEV);
}
public static void setBasePath(String path) {
synchronized (MinificationUtils.class) {
basePath = path;
}
}
public static void setConfig(Config configuration) {
synchronized (Config.class) {
config = configuration;
}
}
/**
* Minifies a JS or CSS file to a corresponding JS or CSS file
*
* @param absolutePath The absolute path to the file
*/
public static void minify(String absolutePath) {
if (absolutePath == null || absolutePath.contains(MIN)) {
return;
}
if (config == null) {
System.setProperty(Jvm.APPLICATION_CONFIG.toString(), basePath + Default.CONFIG_PATH.toString());
config = new Config(basePath + Default.CONFIG_PATH.toString(), Mode.DEV);
}
if (config.isMinifyCSS() && absolutePath.endsWith(JS)) {
minifyJS(new File(absolutePath));
} else if (config.isMinifyJS() && absolutePath.endsWith(CSS)) {
minifyCSS(new File(absolutePath));
}
}
/**
* Compiles a LESS or SASS file to a corresponding CSS file
*
* @param absolutePath The absolute path to the file
*/
public static void preprocess(String absolutePath) {
if (absolutePath == null) {
return;
}
if (config == null) {
System.setProperty(Jvm.APPLICATION_CONFIG.toString(), basePath + Default.CONFIG_PATH.toString());
config = new Config(basePath + Default.CONFIG_PATH.toString(), Mode.DEV);
}
if (config.isPreprocessLess() && absolutePath.endsWith(LESS)) {
lessify(new File(absolutePath));
} else if (config.isPreprocessSass() && absolutePath.endsWith(SASS)) {
sassify(new File(absolutePath));
}
}
private static void lessify(File lessFile) {
final LessCompiler compiler = new DefaultLessCompiler();
try {
final File outputFile = getOutputFile(lessFile, Suffix.CSS);
final CompilationResult compilationResult = compiler.compile(lessFile);
FileUtils.writeStringToFile(outputFile, compilationResult.getCss(), Default.ENCODING.toString());
logPreprocess(lessFile, outputFile);
} catch (Less4jException | IOException e) {
LOG.error("Failed to preprocess LESS file", e);
}
}
private static void sassify(File sassFile) {
final File outputFile = getOutputFile(sassFile, Suffix.CSS);
final URI inputURI = sassFile.toURI();
final URI outputURI = outputFile.toURI();
final Compiler compiler = new Compiler();
try {
final Output output = compiler.compileFile(inputURI, outputURI, new Options());
FileUtils.writeStringToFile(outputFile, output.getCss(), Default.ENCODING.toString());
logPreprocess(sassFile, outputFile);
} catch (CompilationException | IOException e) {
LOG.error("Failed to preprocess SASS file", e);
}
}
private static void minifyJS(File inputFile) {
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
final File outputFile = getOutputFile(inputFile, Suffix.JS_MIN);
fileInputStream = new FileInputStream(inputFile);
fileOutputStream = new FileOutputStream(outputFile);
final JSMin jsMin = new JSMin(fileInputStream, fileOutputStream);
jsMin.jsmin();
logMinification(inputFile, outputFile);
} catch (IOException | JSMinException e) {
LOG.error("Failed to minify JS", e);
} finally {
IOUtils.closeQuietly(fileInputStream);
IOUtils.closeQuietly(fileOutputStream);
}
}
private static void logMinification(File inputFile, File outputFile) {
LOG.info(String.format("Minified asset %s (%db) -> %s (%db) [compressed to %d%% of original size]", inputFile.getName(), inputFile.length(), outputFile.getName(), outputFile.length(), ratioOfSize(inputFile, outputFile)));
}
private static void logPreprocess(File inputFile, File outputFile) {
LOG.info(String.format("Preprocessed asset %s -> %s", inputFile.getName(), outputFile.getName()));
}
private static void minifyCSS(File inputFile) {
try {
final File outputFile = getOutputFile(inputFile, Suffix.CSS_MIN);
final StringBuffer stringBuffer = new StringBuffer();
final CSSMinifier cssMinifier = new CSSMinifier();
stringBuffer.append(FileUtils.readFileToString(inputFile, Default.ENCODING.toString()));
final StringBuffer minifyCSS = cssMinifier.minifyCSS(stringBuffer);
FileUtils.write(outputFile, minifyCSS.toString(), Default.ENCODING.toString());
logMinification(inputFile, outputFile);
} catch (final IOException e) {
LOG.error("Failed to minify CSS", e);
}
}
private static File getOutputFile(File inputfile, Suffix targetSuffix) {
String fileName = inputfile.getName();
fileName = fileName.substring(0, fileName.lastIndexOf('.'));
fileName = fileName + targetSuffix.toString();
if (!basePath.endsWith("/")) {
basePath = basePath + "/";
}
String assetPath = config.getAssetsPath();
if (assetPath.startsWith("/")) {
assetPath = assetPath.substring(1);
}
if (!assetPath.endsWith("/")) {
assetPath = assetPath + "/";
}
String subpath = null;
if (Suffix.CSS.equals(targetSuffix) || Suffix.CSS_MIN.equals(targetSuffix)) {
subpath = Default.STYLESHEET_FOLDER.toString() + "/" + fileName;
} else if (Suffix.JS.equals(targetSuffix) || Suffix.JS_MIN.equals(targetSuffix)) {
subpath = Default.JAVASCRIPT_FOLDER.toString() + "/" + fileName;
}
return new File(basePath + assetPath + subpath);
}
private static long ratioOfSize(File inputFile, File outputFile) {
final long inFile = Math.max(inputFile.length(), 1);
final long outFile = Math.max(outputFile.length(), 1);
return (outFile * HUNDRED_PERCENT) / inFile;
}
}