/*
* Copyright 2011 PrimeFaces Extensions.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* $Id: ResourcesOptimizerMojo.java 513 2011-12-01 18:58:10Z ovaraksin@googlemail.com $
*/
package org.primefaces.extensions.optimizerplugin;
import java.io.File;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import com.google.javascript.jscomp.CompilationLevel;
import com.google.javascript.jscomp.WarningLevel;
/**
* Entry point for this plugin.
*
* @author Oleg Varaksin / last modified by $Author: ovaraksin@googlemail.com $
* @version $Revision: 513 $
* @goal optimize
* @phase process-resources
* @since 0.1
*/
public class ResourcesOptimizerMojo extends AbstractMojo {
private static final String[] DEFAULT_INCLUDES = {"**/*.css", "**/*.js"};
private static final String[] DEFAULT_EXCLUDES = {};
/**
* Input directory.
*
* @parameter expression="${inputDir}" default-value="${project.build.directory}/webapp"
*/
private File inputDir;
/**
* Compilation level for Google Closure Compiler.
*
* @parameter expression="${compilationLevel}" default-value="SIMPLE_OPTIMIZATIONS"
*/
private String compilationLevel;
/**
* Warning level for Google Closure Compiler.
*
* @parameter expression="${warningLevel}" default-value="QUIET"
*/
private String warningLevel;
/**
* Encoding to read files.
*
* @parameter expression="${encoding}" default-value="UTF-8"
* @required
*/
private String encoding;
/**
* Flag whether this plugin must stop/fail on warnings.
*
* @parameter
*/
private boolean failOnWarning = false;
/**
* Suffix for compressed / merged files.
*
* @parameter
*/
private String suffix;
/**
* Files to be included. Files selectors follow patterns specified in {@link org.codehaus.plexus.util.DirectoryScanner}.
*
* @parameter
*/
private String[] includes;
/**
* Files to be excluded. Files selectors follow patterns specified in {@link org.codehaus.plexus.util.DirectoryScanner}.
*
* @parameter
*/
private String[] excludes;
/**
* Aggregations.
*
* @parameter
*/
private Aggregation[] aggregations;
/**
* Compile sets.
*
* @parameter
*/
private List<ResourcesSet> resourcesSets;
private long originalFilesSize = 0;
private long optimizedFilesSize = 0;
boolean resFound = false;
/**
* Executes Mojo.
*
* @throws MojoExecutionException
* @throws MojoFailureException
*/
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
getLog().info("Optimization of resources is started ...");
try {
if (resourcesSets == null || resourcesSets.isEmpty()) {
String[] incls = (includes != null && includes.length > 0) ? includes : DEFAULT_INCLUDES;
String[] excls = (excludes != null && excludes.length > 0) ? excludes : DEFAULT_EXCLUDES;
Aggregation[] aggrs;
if (aggregations == null || aggregations.length < 1) {
aggrs = new Aggregation[1];
aggrs[0] = null;
} else {
aggrs = aggregations;
}
for (Aggregation aggr : aggrs) {
aggr = checkAggregation(aggr) ? null : aggr;
// evaluate inputDir
File dir = (aggr != null && aggr.getInputDir() != null) ? aggr.getInputDir() : inputDir;
// prepare CSS und JavaScript files
ResourcesScanner scanner = new ResourcesScanner();
scanner.scan(dir, incls, excls);
if (aggr != null && aggr.getOutputFile() == null) {
// subDirMode = true ==> aggregation for each subfolder
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
ResourcesScanner subDirScanner = new ResourcesScanner();
subDirScanner.scan(file, DEFAULT_INCLUDES, DEFAULT_EXCLUDES);
Set<File> subDirCssFiles =
filterSubDirFiles(scanner.getCssFiles(), subDirScanner.getCssFiles());
if (!subDirCssFiles.isEmpty()) {
// handle CSS files
processCssFiles(file, subDirCssFiles,
getSubDirAggregation(file, aggr, ResourcesScanner.CSS_FILE_EXTENSION),
null);
}
Set<File> subDirJsFiles = filterSubDirFiles(scanner.getJsFiles(), subDirScanner.getJsFiles());
if (!subDirJsFiles.isEmpty()) {
// handle JavaScript files
processJsFiles(file, subDirJsFiles, getCompilationLevel(compilationLevel),
getWarningLevel(warningLevel),
getSubDirAggregation(file, aggr, ResourcesScanner.JS_FILE_EXTENSION),
null);
}
}
}
}
} else {
if (!scanner.getCssFiles().isEmpty()) {
// handle CSS files
processCssFiles(dir, scanner.getCssFiles(), aggr, suffix);
}
if (!scanner.getJsFiles().isEmpty()) {
// handle JavaScript files
processJsFiles(dir, scanner.getJsFiles(), getCompilationLevel(compilationLevel),
getWarningLevel(warningLevel), aggr, suffix);
}
}
}
} else {
for (ResourcesSet rs : resourcesSets) {
// iterate over all resources sets
String[] incls;
if (rs.getIncludes() != null && rs.getIncludes().length > 0) {
incls = rs.getIncludes();
} else if (includes != null && includes.length > 0) {
incls = includes;
} else {
incls = DEFAULT_INCLUDES;
}
String[] excls;
if (rs.getExcludes() != null && rs.getExcludes().length > 0) {
excls = rs.getExcludes();
} else if (excludes != null && excludes.length > 0) {
excls = excludes;
} else {
excls = DEFAULT_EXCLUDES;
}
Aggregation[] aggrs;
if (rs.getAggregations() == null || rs.getAggregations().length < 1) {
if (aggregations == null || aggregations.length < 1) {
aggrs = new Aggregation[1];
aggrs[0] = null;
} else {
aggrs = aggregations;
}
} else {
aggrs = rs.getAggregations();
}
for (Aggregation aggr : aggrs) {
aggr = checkAggregation(aggr) ? null : aggr;
// evaluate inputDir
File dir = (aggr != null && aggr.getInputDir() != null) ? aggr.getInputDir() : rs.getInputDir();
if (dir == null) {
dir = inputDir;
}
// prepare CSS und JavaScript files
ResourcesScanner scanner = new ResourcesScanner();
scanner.scan(dir, incls, excls);
if (aggr != null && aggr.getOutputFile() == null) {
// subDirMode = true ==> aggregation for each subfolder
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
ResourcesScanner subDirScanner = new ResourcesScanner();
subDirScanner.scan(file, DEFAULT_INCLUDES, DEFAULT_EXCLUDES);
Set<File> subDirCssFiles =
filterSubDirFiles(scanner.getCssFiles(), subDirScanner.getCssFiles());
if (!subDirCssFiles.isEmpty()) {
// handle CSS files
processCssFiles(file, subDirCssFiles,
getSubDirAggregation(file, aggr, ResourcesScanner.CSS_FILE_EXTENSION),
null);
}
Set<File> subDirJsFiles =
filterSubDirFiles(scanner.getJsFiles(), subDirScanner.getJsFiles());
if (!subDirJsFiles.isEmpty()) {
CompilationLevel compLevel;
if (rs.getCompilationLevel() != null) {
compLevel = getCompilationLevel(rs.getCompilationLevel());
} else {
compLevel = getCompilationLevel(compilationLevel);
}
WarningLevel warnLevel;
if (rs.getWarningLevel() != null) {
warnLevel = getWarningLevel(rs.getWarningLevel());
} else {
warnLevel = getWarningLevel(warningLevel);
}
// handle JavaScript files
processJsFiles(file, subDirJsFiles, compLevel, warnLevel,
getSubDirAggregation(file, aggr, ResourcesScanner.JS_FILE_EXTENSION),
null);
}
}
}
}
} else {
if (!scanner.getCssFiles().isEmpty()) {
// handle CSS files
processCssFiles(dir, scanner.getCssFiles(), aggr, suffix);
}
if (!scanner.getJsFiles().isEmpty()) {
CompilationLevel compLevel;
if (rs.getCompilationLevel() != null) {
compLevel = getCompilationLevel(rs.getCompilationLevel());
} else {
compLevel = getCompilationLevel(compilationLevel);
}
WarningLevel warnLevel;
if (rs.getWarningLevel() != null) {
warnLevel = getWarningLevel(rs.getWarningLevel());
} else {
warnLevel = getWarningLevel(warningLevel);
}
// handle JavaScript files
processJsFiles(dir, scanner.getJsFiles(), compLevel, warnLevel, aggr, suffix);
}
}
}
}
}
} catch (MojoExecutionException e) {
throw e;
} catch (Exception e) {
throw new MojoExecutionException("Error while executing the mojo " + getClass(), e);
}
if (!resFound) {
getLog().info("No resources found for optimization.");
return;
}
getLog().info("Optimization of resources has been finished successfully.");
outputStatistic();
}
private void processCssFiles(final File inputDir, final Set<File> cssFiles, final Aggregation aggr, final String suffix)
throws MojoExecutionException {
resFound = true;
ResourcesSetAdapter rsa = new ResourcesSetAdapter(inputDir, cssFiles, null, null, aggr, encoding, failOnWarning, suffix);
YuiCompressorOptimizer yuiOptimizer = new YuiCompressorOptimizer();
yuiOptimizer.optimize(rsa, getLog());
originalFilesSize += yuiOptimizer.getTotalOriginalSize();
optimizedFilesSize += yuiOptimizer.getTotalOptimizedSize();
}
private void processJsFiles(final File inputDir, final Set<File> jsFiles, final CompilationLevel compilationLevel,
final WarningLevel warningLevel, final Aggregation aggr, final String suffix)
throws MojoExecutionException {
resFound = true;
ResourcesSetAdapter rsa =
new ResourcesSetAdapter(inputDir, jsFiles, compilationLevel, warningLevel, aggr, encoding, failOnWarning, suffix);
ClosureCompilerOptimizer closureOptimizer = new ClosureCompilerOptimizer();
closureOptimizer.optimize(rsa, getLog());
originalFilesSize += closureOptimizer.getTotalOriginalSize();
optimizedFilesSize += closureOptimizer.getTotalOptimizedSize();
}
private boolean checkAggregation(final Aggregation aggregation) throws MojoExecutionException {
if (aggregation == null) {
return true;
}
if (aggregation.isSubDirMode() && aggregation.getOutputFile() != null) {
final String errMsg = "At least one aggregation tag is ambiguous because both 'subDirMode' and 'outputFile' were set";
if (failOnWarning) {
throw new MojoExecutionException(errMsg);
} else {
getLog().warn(errMsg);
getLog().warn("Using 'outputFile' as aggregation");
aggregation.setSubDirMode(false);
}
return false;
}
if (!aggregation.isSubDirMode() && aggregation.getOutputFile() == null) {
final String errMsg =
"An aggregation tag is available, but no valid aggregation was configured. Check 'subDirMode' and 'outputFile'";
if (failOnWarning) {
throw new MojoExecutionException(errMsg);
} else {
getLog().warn(errMsg);
}
return true;
}
return false;
}
private Aggregation getSubDirAggregation(final File dir, final Aggregation aggr, final String fileExtension) {
Aggregation subDirAggr = new Aggregation();
subDirAggr.setPrependedFile(aggr.getPrependedFile());
subDirAggr.setRemoveIncluded(aggr.isRemoveIncluded());
subDirAggr.setWithoutCompress(aggr.isWithoutCompress());
subDirAggr.setSubDirMode(true);
File outputFile = new File(dir, dir.getName() + "." + fileExtension);
subDirAggr.setOutputFile(outputFile);
return subDirAggr;
}
private CompilationLevel getCompilationLevel(final String compilationLevel) throws MojoExecutionException {
try {
return CompilationLevel.valueOf(compilationLevel);
} catch (Exception e) {
final String errMsg =
"Compilation level '" + compilationLevel
+ "' is wrong. Valid constants are: 'WHITESPACE_ONLY', 'SIMPLE_OPTIMIZATIONS', 'ADVANCED_OPTIMIZATIONS'";
if (failOnWarning) {
throw new MojoExecutionException(errMsg);
} else {
getLog().warn(errMsg);
getLog().warn("Using 'SIMPLE_OPTIMIZATIONS' as compilation level");
return CompilationLevel.SIMPLE_OPTIMIZATIONS;
}
}
}
private WarningLevel getWarningLevel(final String warningLevel) throws MojoExecutionException {
try {
return WarningLevel.valueOf(warningLevel);
} catch (Exception e) {
final String errMsg =
"Warning level '" + warningLevel + "' is wrong. Valid constants are: 'QUIET', 'DEFAULT', 'VERBOSE'";
if (failOnWarning) {
throw new MojoExecutionException(errMsg);
} else {
getLog().warn(errMsg);
getLog().warn("Using 'QUIET' as warning level");
return WarningLevel.QUIET;
}
}
}
private Set<File> filterSubDirFiles(final Set<File> resSetFiles, final Set<File> subDirFiles) {
Set<File> filteredFiles = new LinkedHashSet<File>();
if (subDirFiles == null || subDirFiles.isEmpty() || resSetFiles == null || resSetFiles.isEmpty()) {
return filteredFiles;
}
for (File subDirFile : subDirFiles) {
if (resSetFiles.contains(subDirFile)) {
filteredFiles.add(subDirFile);
}
}
return filteredFiles;
}
private void outputStatistic() {
final String originalSizeTotal;
final String optimizedSizeTotal;
long oneMB = 1024 * 1024;
if (originalFilesSize <= 1024) {
originalSizeTotal = originalFilesSize + " Bytes";
} else if (originalFilesSize <= oneMB) {
originalSizeTotal = round((double) originalFilesSize / 1024, 3) + " KB";
} else {
originalSizeTotal = round((double) originalFilesSize / oneMB, 3) + " MB";
}
if (optimizedFilesSize <= 1024) {
optimizedSizeTotal = optimizedFilesSize + " Bytes";
} else if (optimizedFilesSize <= oneMB) {
optimizedSizeTotal = round((double) optimizedFilesSize / 1024, 3) + " KB";
} else {
optimizedSizeTotal = round((double) optimizedFilesSize / oneMB, 3) + " MB";
}
if (originalFilesSize > 0) {
getLog().info("=== Statistic ===========================================");
getLog().info("Size of original resources = " + originalSizeTotal);
getLog().info("Size of optimized resources = " + optimizedSizeTotal);
getLog().info("Optimized resources have " + round(((optimizedFilesSize * 100.0) / originalFilesSize), 2)
+ "% of original size");
getLog().info("=========================================================");
}
}
private double round(double value, int places) {
double roundedValue = 0.0;
double factor = Math.pow(10.0, places);
double temp = Math.round(value * factor * factor) / factor;
roundedValue = Math.round(temp) / factor;
return roundedValue;
}
}