001/*
002 * Minify Maven Plugin
003 * https://github.com/samaxes/minify-maven-plugin
004 *
005 * Copyright (c) 2009 samaxes.com
006 *
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019package com.samaxes.maven.minify.plugin;
020
021import com.google.common.base.Strings;
022import com.google.gson.Gson;
023import com.google.javascript.jscomp.*;
024import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
025import com.samaxes.maven.minify.common.Aggregation;
026import com.samaxes.maven.minify.common.AggregationConfiguration;
027import com.samaxes.maven.minify.common.ClosureConfig;
028import com.samaxes.maven.minify.common.YuiConfig;
029import org.apache.maven.plugin.AbstractMojo;
030import org.apache.maven.plugin.MojoExecutionException;
031import org.apache.maven.plugin.MojoFailureException;
032import org.apache.maven.plugins.annotations.LifecyclePhase;
033import org.apache.maven.plugins.annotations.Mojo;
034import org.apache.maven.plugins.annotations.Parameter;
035
036import java.io.*;
037import java.nio.charset.Charset;
038import java.util.*;
039import java.util.concurrent.ExecutionException;
040import java.util.concurrent.ExecutorService;
041import java.util.concurrent.Executors;
042import java.util.concurrent.Future;
043
044import static com.google.common.collect.Lists.newArrayList;
045
046/**
047 * Goal for combining and minifying CSS and JavaScript files.
048 */
049@Mojo(name = "minify", defaultPhase = LifecyclePhase.PROCESS_RESOURCES, threadSafe = true)
050public class MinifyMojo extends AbstractMojo {
051
052    /**
053     * Engine used for minification.
054     */
055    public enum Engine {
056        /**
057         * YUI Compressor
058         */
059        YUI,
060        /**
061         * Google Closure Compiler
062         */
063        CLOSURE
064    }
065
066    /* ************** */
067    /* Global Options */
068    /* ************** */
069
070    /**
071     * Show source file paths in log output.
072     *
073     * @since 1.5.2
074     * @deprecated Use {@link #verbose} instead.
075     */
076    @Deprecated
077    @Parameter(property = "debug")
078    private Boolean debug;
079
080    /**
081     * Display additional informational messages and warnings.
082     */
083    @Parameter(property = "verbose", defaultValue = "false")
084    private boolean verbose;
085
086    /**
087     * Size of the buffer used to read source files.
088     */
089    @Parameter(property = "bufferSize", defaultValue = "4096")
090    private int bufferSize;
091
092    /**
093     * If a supported character set is specified, it will be used to read the input file. Otherwise, it will assume that
094     * the platform's default character set is being used. The output file is encoded using the same character set.<br/>
095     * See the <a href="http://www.iana.org/assignments/character-sets">IANA Charset Registry</a> for a list of valid
096     * encoding types.
097     *
098     * @since 1.3.2
099     */
100    @Parameter(property = "charset", defaultValue = "${project.build.sourceEncoding}")
101    private String charset;
102
103    /**
104     * The output file name suffix.
105     *
106     * @since 1.3.2
107     */
108    @Parameter(property = "suffix", defaultValue = ".min")
109    private String suffix;
110
111    /**
112     * Do not append a suffix to the minified output file name, independently of the value in the {@code suffix}
113     * parameter.<br/>
114     * <strong>Warning:</strong> when both the options {@code nosuffix} and {@code skipMerge} are set to {@code true},
115     * the plugin execution phase needs to be set to {@code package}, otherwise the output files will be overridden by
116     * the source files during the packaging.
117     *
118     * @since 1.7
119     */
120    @Parameter(property = "nosuffix", defaultValue = "false")
121    private boolean nosuffix;
122
123    /**
124     * Skip the merge step. Minification will be applied to each source file individually.
125     *
126     * @since 1.5.2
127     */
128    @Parameter(property = "skipMerge", defaultValue = "false")
129    private boolean skipMerge;
130
131    /**
132     * Skip the minify step. Useful when merging files that are already minified.
133     *
134     * @since 1.5.2
135     */
136    @Parameter(property = "skipMinify", defaultValue = "false")
137    private boolean skipMinify;
138
139    /**
140     * Webapp source directory.
141     */
142    @Parameter(property = "webappSourceDir", defaultValue = "${basedir}/src/main/webapp")
143    private String webappSourceDir;
144
145    /**
146     * Webapp target directory.
147     */
148    @Parameter(property = "webappTargetDir", defaultValue = "${project.build.directory}/${project.build.finalName}")
149    private String webappTargetDir;
150
151    /**
152     * Specify aggregations in an external JSON formatted config file.
153     *
154     * @since 1.7.5
155     */
156    @Parameter(property = "bundleConfiguration")
157    private String bundleConfiguration;
158
159    /* *********** */
160    /* CSS Options */
161    /* *********** */
162
163    /**
164     * CSS source directory.
165     */
166    @Parameter(property = "cssSourceDir", defaultValue = "css")
167    private String cssSourceDir;
168
169    /**
170     * CSS source file names list.
171     */
172    @Parameter(property = "cssSourceFiles", alias = "cssFiles")
173    private ArrayList<String> cssSourceFiles;
174
175    /**
176     * CSS files to include. Specified as fileset patterns which are relative to the CSS source directory.
177     *
178     * @since 1.2
179     */
180    @Parameter(property = "cssSourceIncludes", alias = "cssIncludes")
181    private ArrayList<String> cssSourceIncludes;
182
183    /**
184     * CSS files to exclude. Specified as fileset patterns which are relative to the CSS source directory.
185     *
186     * @since 1.2
187     */
188    @Parameter(property = "cssSourceExcludes", alias = "cssExcludes")
189    private ArrayList<String> cssSourceExcludes;
190
191    /**
192     * CSS target directory. Takes the same value as {@code cssSourceDir} when empty.
193     *
194     * @since 1.3.2
195     */
196    @Parameter(property = "cssTargetDir")
197    private String cssTargetDir;
198
199    /**
200     * CSS output file name.
201     */
202    @Parameter(property = "cssFinalFile", defaultValue = "style.css")
203    private String cssFinalFile;
204
205    /**
206     * Define the CSS compressor engine to use.<br/>
207     * Possible values are:
208     * <ul>
209     * <li>{@code YUI}: <a href="http://yui.github.io/yuicompressor/">YUI Compressor</a></li>
210     * </ul>
211     *
212     * @since 1.7.1
213     */
214    @Parameter(property = "cssEngine", defaultValue = "YUI")
215    private Engine cssEngine;
216
217    /* ****************** */
218    /* JavaScript Options */
219    /* ****************** */
220
221    /**
222     * JavaScript source directory.
223     */
224    @Parameter(property = "jsSourceDir", defaultValue = "js")
225    private String jsSourceDir;
226
227    /**
228     * JavaScript source file names list.
229     */
230    @Parameter(property = "jsSourceFiles", alias = "jsFiles")
231    private ArrayList<String> jsSourceFiles;
232
233    /**
234     * JavaScript files to include. Specified as fileset patterns which are relative to the JavaScript source directory.
235     *
236     * @since 1.2
237     */
238    @Parameter(property = "jsSourceIncludes", alias = "jsIncludes")
239    private ArrayList<String> jsSourceIncludes;
240
241    /**
242     * JavaScript files to exclude. Specified as fileset patterns which are relative to the JavaScript source directory.
243     *
244     * @since 1.2
245     */
246    @Parameter(property = "jsSourceExcludes", alias = "jsExcludes")
247    private ArrayList<String> jsSourceExcludes;
248
249    /**
250     * JavaScript target directory. Takes the same value as {@code jsSourceDir} when empty.
251     *
252     * @since 1.3.2
253     */
254    @Parameter(property = "jsTargetDir")
255    private String jsTargetDir;
256
257    /**
258     * JavaScript output file name.
259     */
260    @Parameter(property = "jsFinalFile", defaultValue = "script.js")
261    private String jsFinalFile;
262
263    /**
264     * Define the JavaScript compressor engine to use.<br/>
265     * Possible values are:
266     * <ul>
267     * <li>{@code YUI}: <a href="http://yui.github.io/yuicompressor/">YUI Compressor</a></li>
268     * <li>{@code CLOSURE}: <a href="https://developers.google.com/closure/compiler/">Google Closure Compiler</a></li>
269     * </ul>
270     *
271     * @since 1.6
272     */
273    @Parameter(property = "jsEngine", defaultValue = "YUI")
274    private Engine jsEngine;
275
276    /* *************************** */
277    /* YUI Compressor Only Options */
278    /* *************************** */
279
280    /**
281     * Some source control tools don't like files containing lines longer than, say 8000 characters. The line-break
282     * option is used in that case to split long lines after a specific column. It can also be used to make the code
283     * more readable and easier to debug. Specify {@code 0} to get a line break after each semi-colon in JavaScript, and
284     * after each rule in CSS. Specify {@code -1} to disallow line breaks.
285     *
286     * @deprecated Use {@link #yuiLineBreak} instead.
287     */
288    @Deprecated
289    @Parameter(property = "linebreak")
290    private Integer linebreak;
291
292    /**
293     * Some source control tools don't like files containing lines longer than, say 8000 characters. The line-break
294     * option is used in that case to split long lines after a specific column. It can also be used to make the code
295     * more readable and easier to debug. Specify {@code 0} to get a line break after each semi-colon in JavaScript, and
296     * after each rule in CSS. Specify {@code -1} to disallow line breaks.
297     */
298    @Parameter(property = "yuiLineBreak", defaultValue = "-1")
299    private int yuiLineBreak;
300
301    /**
302     * Obfuscate local symbols in addition to minification.
303     *
304     * @deprecated Use {@link #yuiNoMunge} instead.
305     */
306    @Deprecated
307    @Parameter(property = "munge")
308    private Boolean munge;
309
310    /**
311     * Minify only. Do not obfuscate local symbols.
312     */
313    @Parameter(property = "yuiNoMunge", defaultValue = "false")
314    private boolean yuiNoMunge;
315
316    /**
317     * Preserve unnecessary semicolons (such as right before a '}'). This option is useful when compressed code has to
318     * be run through JSLint.
319     *
320     * @deprecated Use {@link #yuiPreserveSemicolons} instead.
321     */
322    @Deprecated
323    @Parameter(property = "preserveAllSemiColons")
324    private Boolean preserveAllSemiColons;
325
326    /**
327     * Preserve unnecessary semicolons (such as right before a '}'). This option is useful when compressed code has to
328     * be run through JSLint.
329     */
330    @Parameter(property = "yuiPreserveSemicolons", defaultValue = "false")
331    private boolean yuiPreserveSemicolons;
332
333    /**
334     * Disable all the built-in micro-optimizations.
335     *
336     * @deprecated Use {@link #yuiDisableOptimizations} instead.
337     */
338    @Deprecated
339    @Parameter(property = "disableOptimizations")
340    private Boolean disableOptimizations;
341
342    /**
343     * Disable all the built-in micro-optimizations.
344     */
345    @Parameter(property = "yuiDisableOptimizations", defaultValue = "false")
346    private boolean yuiDisableOptimizations;
347
348    /* ************************************ */
349    /* Google Closure Compiler Only Options */
350    /* ************************************ */
351
352    /**
353     * Refers to which version of ECMAScript to assume when checking for errors in your code.<br/>
354     * Possible values are:
355     * <ul>
356     * <li>{@code ECMASCRIPT3}: Checks code assuming ECMAScript 3 compliance, and gives errors for code using features only present in later versions of ECMAScript.</li>
357     * <li>{@code ECMASCRIPT5}: Checks code assuming ECMAScript 5 compliance, allowing new features not present in ECMAScript 3, and gives errors for code using features only present in later versions of ECMAScript.</li>
358     * <li>{@code ECMASCRIPT5_STRICT}: Like {@code ECMASCRIPT5} but assumes compliance with strict mode ({@code 'use strict';}).</li>
359     * <li>{@code ECMASCRIPT6}: Checks code assuming ECMAScript 6 compliance, allowing new features not present in ECMAScript 5.</li>
360     * <li>{@code ECMASCRIPT6_STRICT}: Like {@code ECMASCRIPT6} but assumes compliance with strict mode ({@code 'use strict';}).</li>
361     * </ul>
362     *
363     * @since 1.7.2
364     */
365    @Parameter(property = "closureLanguageIn", defaultValue = "ECMASCRIPT6")
366    private LanguageMode closureLanguageIn;
367
368    /**
369     * Refers to which version of ECMAScript your code will be returned in.<br/>
370     * It accepts the same options as {@code closureLanguageIn} and is used to transpile between different levels of ECMAScript.
371     *
372     * @since 1.7.5
373     */
374    @Parameter(property = "closureLanguageOut", defaultValue = "ECMASCRIPT5")
375    private LanguageMode closureLanguageOut;
376
377    /**
378     * Determines the set of builtin externs to load.<br/>
379     * Options: BROWSER, CUSTOM.
380     *
381     * @since 1.7.5
382     */
383    @Parameter(property = "closureEnvironment", defaultValue = "BROWSER")
384    private CompilerOptions.Environment closureEnvironment;
385
386    /**
387     * The degree of compression and optimization to apply to your JavaScript.<br/>
388     * There are three possible compilation levels:
389     * <ul>
390     * <li>{@code WHITESPACE_ONLY}: Just removes whitespace and comments from your JavaScript.</li>
391     * <li>{@code SIMPLE_OPTIMIZATIONS}: Performs compression and optimization that does not interfere with the
392     * interaction between the compiled JavaScript and other JavaScript. This level renames only local variables.</li>
393     * <li>{@code ADVANCED_OPTIMIZATIONS}: Achieves the highest level of compression by renaming symbols in your
394     * JavaScript. When using {@code ADVANCED_OPTIMIZATIONS} compilation you must perform extra steps to preserve
395     * references to external symbols. See <a href="/closure/compiler/docs/api-tutorial3">Advanced Compilation and
396     * Externs</a> for more information about {@code ADVANCED_OPTIMIZATIONS}.</li>
397     * </ul>
398     *
399     * @since 1.7.2
400     */
401    @Parameter(property = "closureCompilationLevel", defaultValue = "SIMPLE_OPTIMIZATIONS")
402    private CompilationLevel closureCompilationLevel;
403
404    /**
405     * List of JavaScript files containing code that declares function names or other symbols. Use
406     * {@code closureExterns} to preserve symbols that are defined outside of the code you are compiling. The
407     * {@code closureExterns} parameter only has an effect if you are using a {@code CompilationLevel} of
408     * {@code ADVANCED_OPTIMIZATIONS}.<br/>
409     * These file names are relative to {@link #webappSourceDir} directory.
410     *
411     * @since 1.7.2
412     */
413    @Parameter(property = "closureExterns")
414    private ArrayList<String> closureExterns;
415
416    /**
417     * Collects information mapping the generated (compiled) source back to its original source for debugging purposes.<br/>
418     * Please visit <a href="https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit">Source Map Revision 3 Proposal</a> for more information.
419     *
420     * @since 1.7.3
421     */
422    @Parameter(property = "closureCreateSourceMap", defaultValue = "false")
423    private boolean closureCreateSourceMap;
424
425    /**
426     * Enables or disables sorting mode for Closure Library dependencies.<br/>
427     * If {@code true}, automatically sort dependencies so that a file that {@code goog.provides} symbol X will always come
428     * before a file that {@code goog.requires} symbol X.
429     *
430     * @since 1.7.4
431     */
432    @Parameter(property = "closureSortDependencies", defaultValue = "false")
433    private boolean closureSortDependencies;
434
435    /**
436     * Treat certain warnings as the specified CheckLevel:
437     * <ul>
438     * <li>{@code ERROR}: Makes all warnings of the given group to build-breaking error.</li>
439     * <li>{@code WARNING}: Makes all warnings of the given group a non-breaking warning.</li>
440     * <li>{@code OFF}: Silences all warnings of the given group.</li>
441     * </ul>
442     * Example:
443     * <pre><code class="language-java">
444     * &lt;closureWarningLevels>
445     *     &lt;nonStandardJsDocs>OFF&lt;/nonStandardJsDocs>
446     * &lt;/closureWarningLevels>
447     * </code></pre>
448     * For the complete list of diagnostic groups please visit <a href="https://github.com/google/closure-compiler/wiki/Warnings">https://github.com/google/closure-compiler/wiki/Warnings</a>.
449     *
450     * @since 1.7.5
451     */
452    @Parameter(property = "closureWarningLevels")
453    private HashMap<String, String> closureWarningLevels;
454
455    /**
456     * Generate {@code $inject} properties for AngularJS for functions annotated with {@code @ngInject}.
457     *
458     * @since 1.7.3
459     */
460    @Parameter(property = "closureAngularPass", defaultValue = "false")
461    private boolean closureAngularPass;
462
463    /**
464     * A whitelist of tag names in JSDoc. Needed to support JSDoc extensions like ngdoc.
465     *
466     * @since 1.7.5
467     */
468    @Parameter(property = "closureExtraAnnotations")
469    private ArrayList<String> closureExtraAnnotations;
470
471    /**
472     * Override the value of variables annotated with {@code @define}.<br/>
473     * The format is:
474     * <pre><code class="language-java">
475     * &lt;define>
476     *     &lt;name>value&lt;/name>
477     * &lt;/define>
478     * </code></pre>
479     * where {@code <name>} is the name of a {@code @define} variable and {@code value} is a boolean, number or string.
480     *
481     * @since 1.7.5
482     */
483    @Parameter(property = "closureDefine")
484    private HashMap<String, String> closureDefine;
485
486    /**
487     * Executed when the goal is invoked, it will first invoke a parallel lifecycle, ending at the given phase.
488     */
489    @Override
490    public void execute() throws MojoExecutionException, MojoFailureException {
491        checkDeprecatedOptions();
492
493        if (skipMerge && skipMinify) {
494            getLog().warn("Both merge and minify steps are configured to be skipped.");
495            return;
496        }
497
498        fillOptionalValues();
499
500        YuiConfig yuiConfig = fillYuiConfig();
501        ClosureConfig closureConfig = fillClosureConfig();
502        Collection<ProcessFilesTask> processFilesTasks;
503        try {
504            processFilesTasks = createTasks(yuiConfig, closureConfig);
505        } catch (FileNotFoundException e) {
506            throw new MojoFailureException(e.getMessage(), e);
507        }
508
509        ExecutorService executor = Executors.newFixedThreadPool(processFilesTasks.size());
510        try {
511            List<Future<Object>> futures = executor.invokeAll(processFilesTasks);
512            for (Future<Object> future : futures) {
513                try {
514                    future.get();
515                } catch (ExecutionException e) {
516                    throw new MojoExecutionException(e.getMessage(), e);
517                }
518            }
519            executor.shutdown();
520        } catch (InterruptedException e) {
521            executor.shutdownNow();
522            throw new MojoExecutionException(e.getMessage(), e);
523        }
524    }
525
526    private void checkDeprecatedOptions() {
527        if (debug == null) {
528            debug = verbose;
529        } else {
530            getLog().warn(
531                    "The option 'debug' is deprecated and will be removed on the next version. Use 'verbose' instead.");
532        }
533        if (linebreak == null) {
534            linebreak = yuiLineBreak;
535        } else {
536            getLog().warn(
537                    "The option 'linebreak' is deprecated and will be removed on the next version. Use 'yuiLineBreak' instead.");
538        }
539        if (munge == null) {
540            munge = !yuiNoMunge;
541        } else {
542            getLog().warn(
543                    "The option 'munge' is deprecated and will be removed on the next version. Use 'yuiNoMunge' instead.");
544        }
545        if (preserveAllSemiColons == null) {
546            preserveAllSemiColons = yuiPreserveSemicolons;
547        } else {
548            getLog().warn(
549                    "The option 'preserveAllSemiColons' is deprecated and will be removed on the next version. Use 'yuiPreserveSemicolons' instead.");
550        }
551        if (disableOptimizations == null) {
552            disableOptimizations = yuiDisableOptimizations;
553        } else {
554            getLog().warn(
555                    "The option 'disableOptimizations' is deprecated and will be removed on the next version. Use 'yuiDisableOptimizations' instead.");
556        }
557    }
558
559    private void fillOptionalValues() {
560        if (Strings.isNullOrEmpty(cssTargetDir)) {
561            cssTargetDir = cssSourceDir;
562        }
563        if (Strings.isNullOrEmpty(jsTargetDir)) {
564            jsTargetDir = jsSourceDir;
565        }
566        if (Strings.isNullOrEmpty(charset)) {
567            charset = Charset.defaultCharset().name();
568        }
569    }
570
571    private YuiConfig fillYuiConfig() {
572        return new YuiConfig(linebreak, munge, preserveAllSemiColons, disableOptimizations);
573    }
574
575    private ClosureConfig fillClosureConfig() throws MojoFailureException {
576        DependencyOptions dependencyOptions = new DependencyOptions();
577        dependencyOptions.setDependencySorting(closureSortDependencies);
578
579        List<SourceFile> externs = new ArrayList<>();
580        for (String extern : closureExterns) {
581            externs.add(SourceFile.fromFile(webappSourceDir + File.separator + extern, Charset.forName(charset)));
582        }
583
584        Map<DiagnosticGroup, CheckLevel> warningLevels = new HashMap<>();
585        DiagnosticGroups diagnosticGroups = new DiagnosticGroups();
586        for (Map.Entry<String, String> warningLevel : closureWarningLevels.entrySet()) {
587            DiagnosticGroup diagnosticGroup = diagnosticGroups.forName(warningLevel.getKey());
588            if (diagnosticGroup == null) {
589                throw new MojoFailureException("Failed to process closureWarningLevels: " + warningLevel.getKey() + " is an invalid DiagnosticGroup");
590            }
591
592            try {
593                CheckLevel checkLevel = CheckLevel.valueOf(warningLevel.getValue());
594                warningLevels.put(diagnosticGroup, checkLevel);
595            } catch (IllegalArgumentException e) {
596                throw new MojoFailureException("Failed to process closureWarningLevels: " + warningLevel.getKey() + " is an invalid CheckLevel");
597            }
598        }
599
600        return new ClosureConfig(closureLanguageIn, closureLanguageOut, closureEnvironment, closureCompilationLevel,
601                dependencyOptions, externs, closureCreateSourceMap, warningLevels, closureAngularPass,
602                closureExtraAnnotations, closureDefine);
603    }
604
605    private Collection<ProcessFilesTask> createTasks(YuiConfig yuiConfig, ClosureConfig closureConfig)
606            throws MojoFailureException, FileNotFoundException {
607        List<ProcessFilesTask> tasks = newArrayList();
608
609        if (!Strings.isNullOrEmpty(bundleConfiguration)) { // If a bundleConfiguration is defined, attempt to use that
610            AggregationConfiguration aggregationConfiguration;
611            try (Reader bundleConfigurationReader = new FileReader(bundleConfiguration)) {
612                aggregationConfiguration = new Gson().fromJson(bundleConfigurationReader,
613                        AggregationConfiguration.class);
614            } catch (IOException e) {
615                throw new MojoFailureException("Failed to open the bundle configuration file [" + bundleConfiguration
616                        + "].", e);
617            }
618
619            for (Aggregation aggregation : aggregationConfiguration.getBundles()) {
620                if (Aggregation.AggregationType.css.equals(aggregation.getType())) {
621                    tasks.add(createCSSTask(yuiConfig, closureConfig, aggregation.getFiles(),
622                            Collections.<String>emptyList(), Collections.<String>emptyList(), aggregation.getName()));
623                } else if (Aggregation.AggregationType.js.equals(aggregation.getType())) {
624                    tasks.add(createJSTask(yuiConfig, closureConfig, aggregation.getFiles(),
625                            Collections.<String>emptyList(), Collections.<String>emptyList(), aggregation.getName()));
626                }
627            }
628        } else { // Otherwise, fallback to the default behavior
629            tasks.add(createCSSTask(yuiConfig, closureConfig, cssSourceFiles, cssSourceIncludes, cssSourceExcludes,
630                    cssFinalFile));
631            tasks.add(createJSTask(yuiConfig, closureConfig, jsSourceFiles, jsSourceIncludes, jsSourceExcludes,
632                    jsFinalFile));
633        }
634
635        return tasks;
636    }
637
638    private ProcessFilesTask createCSSTask(YuiConfig yuiConfig, ClosureConfig closureConfig,
639                                           List<String> cssSourceFiles, List<String> cssSourceIncludes, List<String> cssSourceExcludes,
640                                           String cssFinalFile) throws FileNotFoundException {
641        return new ProcessCSSFilesTask(getLog(), debug, bufferSize, Charset.forName(charset), suffix, nosuffix,
642                skipMerge, skipMinify, webappSourceDir, webappTargetDir, cssSourceDir, cssSourceFiles,
643                cssSourceIncludes, cssSourceExcludes, cssTargetDir, cssFinalFile, cssEngine, yuiConfig);
644    }
645
646    private ProcessFilesTask createJSTask(YuiConfig yuiConfig, ClosureConfig closureConfig, List<String> jsSourceFiles,
647                                          List<String> jsSourceIncludes, List<String> jsSourceExcludes, String jsFinalFile)
648            throws FileNotFoundException {
649        return new ProcessJSFilesTask(getLog(), debug, bufferSize, Charset.forName(charset), suffix, nosuffix,
650                skipMerge, skipMinify, webappSourceDir, webappTargetDir, jsSourceDir, jsSourceFiles, jsSourceIncludes,
651                jsSourceExcludes, jsTargetDir, jsFinalFile, jsEngine, yuiConfig, closureConfig);
652    }
653}