com.log4ic.compressor.utils.Compressor.java Source code

Java tutorial

Introduction

Here is the source code for com.log4ic.compressor.utils.Compressor.java

Source

/*
 * Dynamic Compressor - Java Library
 * Copyright (c) 2011-2012, IntelligentCode ZhangLixin.
 * All rights reserved.
 * intelligentcodemail@gmail.com
 *
 * GUN GPL 3.0 License
 *
 * http://www.gnu.org/licenses/gpl.html
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.log4ic.compressor.utils;

import com.google.common.collect.Lists;
import com.google.common.css.JobDescription;
import com.google.common.css.JobDescriptionBuilder;
import com.google.common.css.SourceCode;
import com.google.common.css.compiler.ast.CssTree;
import com.google.common.css.compiler.ast.GssParser;
import com.google.common.css.compiler.ast.GssParserException;
import com.google.common.css.compiler.passes.CompactPrinter;
import com.google.common.css.compiler.passes.PassRunner;
import com.google.common.css.compiler.passes.PrettyPrinter;
import com.google.javascript.jscomp.*;
import com.google.javascript.jscomp.Compiler;
import com.log4ic.compressor.cache.Cache;
import com.log4ic.compressor.cache.CacheManager;
import com.log4ic.compressor.exception.CompressionException;
import com.log4ic.compressor.exception.QueryStringEmptyException;
import com.log4ic.compressor.exception.UnsupportedFileTypeException;
import com.log4ic.compressor.servlet.http.ContentResponseWrapper;
import com.log4ic.compressor.servlet.http.stream.ContentResponseStream;
import com.log4ic.compressor.utils.gss.passes.ExtendedPassRunner;
import com.log4ic.compressor.utils.less.LessEngine;
import com.log4ic.compressor.utils.less.exception.LessException;
import com.log4ic.compressor.utils.template.JavascriptTemplateEngine;
import javolution.util.FastList;
import javolution.util.FastMap;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.mozilla.javascript.Parser;
import org.mozilla.javascript.ast.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.util.List;
import java.util.Map;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * JS CSS 
 *
 * @author  IntelligentCode
 */
public class Compressor {
    private Compressor() {
    }

    private static final Logger logger = LoggerFactory.getLogger(Compressor.class);

    private static final Map<String, byte[]> progressCacheLock = new FastMap<String, byte[]>();

    /**
     * LESS
     *
     * @param codeList
     * @param conditions
     * @return
     */
    public static String compressLess(List<SourceCode> codeList, JobDescription.OutputFormat format,
            List<String> conditions, JobDescription.OptimizeStrategy level)
            throws GssParserException, LessException, CompressionException {
        return compressGss(LessEngine.parseLess(codeList, conditions), format, conditions, level);
    }

    /**
     * JS
     *
     * @param code
     * @param options
     * @param jsOutputFile
     * @return
     */
    public static String compressJS(String code, CompilerOptions options, String jsOutputFile) {
        return compressJS(Lists.<SourceFile>newArrayList(SourceFile.fromCode("all.js", code)), options,
                jsOutputFile);
    }

    /**
     * JS
     *
     * @param jsFiles
     * @param options
     * @param jsOutputFile
     * @return
     */
    public static String compressJS(List<SourceFile> jsFiles, CompilerOptions options, String jsOutputFile) {
        return compressJS(Lists.<SourceFile>newArrayList(SourceFile.fromCode("lib.js", "")), jsFiles, options,
                jsOutputFile);
    }

    /**
     * JS
     *
     * @param externFiles
     * @param jsFiles
     * @param options
     * @param jsOutputFile
     * @return
     */
    public static String compressJS(List<SourceFile> externFiles, List<SourceFile> jsFiles, CompilerOptions options,
            String jsOutputFile) {
        String compressed = null;
        Compiler compiler = new Compiler();
        logger.debug("JS...");
        compiler.compile(externFiles, jsFiles, options);
        compressed = compiler.toSource();
        logger.debug("JS...");
        if (StringUtils.isNotBlank(jsOutputFile)) {
            logger.debug("JS...");
            sourceToFile(compressed, new File(jsOutputFile));
            logger.debug("...");
        }
        return compressed;
    }

    /**
     * JS
     *
     * @param code
     * @param level
     * @param outPath
     * @param isDebug
     * @return
     */
    public static String compressJS(String code, CompilationLevel level, String outPath, Boolean isDebug) {

        return compressJS(code, buildJSCompilerOptions(level, isDebug), outPath);
    }

    private static CompilerOptions buildJSCompilerOptions(CompilationLevel level, Boolean isDebug) {
        CompilerOptions options = new CompilerOptions();
        options.setCodingConvention(new ClosureCodingConvention());
        options.setLanguageIn(CompilerOptions.LanguageMode.ECMASCRIPT5);
        level.setOptionsForCompilationLevel(options);
        if (isDebug) {
            level.setDebugOptionsForCompilationLevel(options);
        }
        return options;
    }

    /**
     * JS
     *
     * @param SourceFiles
     * @param level
     * @param isDebug
     * @return
     */
    public static String compressJS(List<SourceFile> SourceFiles, CompilationLevel level, Boolean isDebug) {
        return compressJS(SourceFiles, buildJSCompilerOptions(level, isDebug), null);
    }

    /**
     * JS
     *
     * @param code
     * @param level
     * @param isDebug
     * @return
     */
    public static String compressJS(String code, CompilationLevel level, Boolean isDebug) {
        return compressJS(code, level, null, isDebug);
    }

    /**
     * ?
     *
     * @param code
     * @param file
     */
    public static void sourceToFile(String code, File file) {
        FileUtils.writeFile(code, file.getPath());
    }

    public static String compressGss(SourceCode sourceCode) throws GssParserException {
        return compressGss(Lists.<SourceCode>newArrayList(sourceCode), null, null, null);
    }

    public static String compressGss(List<SourceCode> codeList) throws GssParserException {
        return compressGss(codeList, null, null, null);
    }

    public static String compressGss(List<SourceCode> codeList, JobDescription.OutputFormat format,
            JobDescription.OptimizeStrategy level) throws GssParserException {
        return compressGss(codeList, format, null, level);
    }

    /**
     * css
     *
     * @param codeList
     * @param format
     * @param conditions
     * @return
     * @throws GssParserException
     */
    public static String compressGss(List<SourceCode> codeList, JobDescription.OutputFormat format,
            List<String> conditions) throws GssParserException {
        return compressGss(buildJobDesBuilder(codeList, format, conditions).getJobDescription());
    }

    public static String compressGss(List<SourceCode> codeList, JobDescription.OutputFormat format,
            List<String> conditions, JobDescription.OptimizeStrategy level) throws GssParserException {
        return compressGss(buildJobDesBuilder(codeList, format, conditions, level).getJobDescription());
    }

    private static JobDescriptionBuilder buildJobDesBuilder(List<SourceCode> codeList,
            JobDescription.OutputFormat format, List<String> conditions) {
        return buildJobDesBuilder(codeList, format, conditions, null);
    }

    private static JobDescriptionBuilder buildJobDesBuilder(List<SourceCode> codeList,
            JobDescription.OutputFormat format, List<String> conditions, JobDescription.OptimizeStrategy level) {
        JobDescriptionBuilder builder = new JobDescriptionBuilder();
        builder.setAllowWebkitKeyframes(true);
        builder.setAllowKeyframes(true);
        builder.setAllowUnrecognizedFunctions(true);
        builder.setAllowUnrecognizedProperties(true);
        builder.setProcessDependencies(true);
        builder.setSimplifyCss(true);
        builder.setEliminateDeadStyles(true);
        builder.setOptimizeStrategy(level == null ? JobDescription.OptimizeStrategy.SAFE : level);
        for (SourceCode code : codeList) {
            builder.addInput(new SourceCode(code.getFileName(), fixIE9Hack(code.getFileContents())));
        }
        if (format != null) {
            builder.setOutputFormat(format);
        }
        //
        //builder.setGssFunctionMapProvider(gssFunctionMapProvider);
        //?
        if (conditions != null && conditions.size() > 0) {
            for (String con : conditions) {
                builder.addTrueConditionName(con);
            }
        }
        return builder;
    }

    public static String parseGss(List<SourceCode> codeList, List<String> conditions)
            throws GssParserException, CompressionException {
        List<SourceCode> codes = Lists.newArrayList();
        for (SourceCode s : codeList) {
            if (getFileType(s.getFileName()) == FileType.GSS) {
                codes.add(s);
            }
        }
        JobDescriptionBuilder builder = buildJobDesBuilder(codes, null, conditions);
        builder.setProcessDependencies(false);
        builder.setSimplifyCss(false);
        builder.setEliminateDeadStyles(false);
        builder.setOptimizeStrategy(JobDescription.OptimizeStrategy.NONE);
        return parseGss(builder.getJobDescription());
    }

    public static String parseGss(JobDescription job) throws GssParserException {
        logger.debug("?GSS...");
        try {
            GssParser parser = new GssParser(job.inputs);
            CssTree cssTree = parser.parse();
            CompilerErrorManager errorManager = new CompilerErrorManager();
            PassRunner passRunner = new ExtendedPassRunner(job, errorManager);
            passRunner.runPasses(cssTree);
            PrettyPrinter prettyPrinterPass = new PrettyPrinter(cssTree.getVisitController());
            prettyPrinterPass.runPass();
            return prettyPrinterPass.getPrettyPrintedString();
        } finally {
            logger.debug("?GSS...");
        }
    }

    /**
     * css
     *
     * @param job
     * @return
     * @throws GssParserException
     */
    public static String compressGss(JobDescription job) throws GssParserException {
        logger.debug("CSS...");
        try {
            GssParser parser = new GssParser(job.inputs);
            CssTree cssTree = parser.parse();
            if (job.outputFormat != JobDescription.OutputFormat.DEBUG) {
                CompilerErrorManager errorManager = new CompilerErrorManager();
                PassRunner passRunner = new ExtendedPassRunner(job, errorManager);
                passRunner.runPasses(cssTree);
            }

            if (job.outputFormat == JobDescription.OutputFormat.COMPRESSED) {
                CompactPrinter compactPrinterPass = new CompactPrinter(cssTree);
                compactPrinterPass.runPass();
                return compactPrinterPass.getCompactPrintedString();
            } else {
                PrettyPrinter prettyPrinterPass = new PrettyPrinter(cssTree.getVisitController());
                prettyPrinterPass.runPass();
                return prettyPrinterPass.getPrettyPrintedString();
            }
        } finally {
            logger.debug("CSS...");
        }
    }

    /**
     * URL?
     *
     * @param code
     * @param fileUrl
     * @param type
     * @return
     */
    public static String fixUrlPath(HttpServletRequest requst, String code, String fileUrl, FileType type) {
        return fixUrlPath(requst, code, fileUrl, type, null);
    }

    /**
     * IE9 \9 HACK\9??
     *
     * @param fragment
     * @return
     */
    public static String fixIE9Hack(String fragment) {
        //IE9 \9 HACK\9??
        return fragment.replaceAll("\\s+\\\\", "\\9");
    }

    private static final String importPatternStr = "@import\\s+(?:url\\()?[\\s\\'\\\"]?([^\\'\\\";\\s\\n]+)[\\s\\'\\\"]?(?:\\))?;?";
    private static final Pattern importPattern = Pattern.compile(importPatternStr, Pattern.CASE_INSENSITIVE);

    public static String importCode(String code, String fileUrl, FileType type, HttpServletRequest request,
            HttpServletResponse response)
            throws CompressionException, LessException, GssParserException, IOException {
        StringBuilder codeBuilder = new StringBuilder();
        switch (type) {
        case GSS:
        case CSS:
        case LESS:
        case MSS:
            Matcher matcher = importPattern.matcher(code);
            String[] codeFragments = importPattern.split(code);
            String filePath = fileUrl.substring(0, fileUrl.lastIndexOf("/") + 1);
            FileType fileType = getFileType(fileUrl);
            int i = 0;
            while (matcher.find()) {

                String cssPath;
                String cssFile = matcher.group(1);
                if (!HttpUtils.isHttpProtocol(cssFile) && !cssFile.startsWith("/")) {
                    cssPath = filePath + cssFile;
                } else {
                    cssPath = cssFile;
                }

                if (StringUtils.isNotBlank(codeFragments[i])) {
                    if (codeFragments[i].lastIndexOf("/*") > codeFragments[i].lastIndexOf("*/")
                            || codeFragments[i].lastIndexOf("//") > codeFragments[i].lastIndexOf("\n")) {
                        if (codeFragments.length > i + 1) {
                            codeFragments[i + 1] = codeFragments[i] + codeFragments[i + 1];
                            codeFragments[i] = null;
                            i++;
                        }
                        continue;
                    } else {
                        codeBuilder.append(codeFragments[i]);
                    }
                }

                if (request.getAttribute(cssPath) == null) {
                    if (fileType == FileType.LESS) {
                        try {
                            if (!type.contains(getFileType(cssPath))) {
                                cssPath += ".less";
                            }
                        } catch (Exception e) {
                            cssPath += ".less";
                        }
                    }
                    logger.debug("[{}]", cssPath);
                    request.setAttribute(cssPath, true);
                    List<SourceCode> sourceCodes = mergeCode(new String[] { cssPath }, request, response, type);
                    for (SourceCode s : sourceCodes) {
                        FileType t = getFileType(s.getFileName());
                        if (fileType.equals(t)) {
                            codeBuilder.append(s.getFileContents());
                        } else {
                            codeBuilder
                                    .append(fixIE9Hack(mergeCode(Lists.<SourceCode>newArrayList(s), request, t)));
                        }
                    }
                }
                i++;
            }
            if (i != 0) {
                if (codeFragments.length > i && StringUtils.isNotBlank(codeFragments[i])) {
                    codeBuilder.append(codeFragments[i]);
                }
                break;
            }
        default:
            return code;
        }
        return codeBuilder.toString();
    }

    /**
     * URL?
     *
     * @param code
     * @param fileUrl
     * @param type
     * @param fileDomain
     * @return
     */
    public static String fixUrlPath(HttpServletRequest req, String code, String fileUrl, FileType type,
            String fileDomain) {

        StringBuilder codeBuffer = new StringBuilder();
        switch (type) {
        case GSS:
        case CSS:
        case LESS:
        case MSS:
            logger.debug("URL?...");
            Pattern pattern = Pattern.compile("url\\(\\s*(?!['\"]?(?:data:|about:|#|@))([^)]+)\\)",
                    Pattern.CASE_INSENSITIVE);

            Matcher matcher = pattern.matcher(code);

            String[] codeFragments = pattern.split(code);

            fileUrl = fileUrl.substring(0, fileUrl.lastIndexOf("/") + 1);

            int i = 0;
            while (matcher.find()) {
                codeBuffer.append(codeFragments[i]);
                codeBuffer.append("url(");
                MatchResult result = matcher.toMatchResult();
                String url = result.group(1).replaceAll("'|\"", "");
                //???
                if (!HttpUtils.isHttpProtocol(url) && !url.startsWith("/")) {
                    url = URI.create(fileUrl + url).normalize().toASCIIString();//?URL
                }

                //??url?http?)??????
                if (StringUtils.isNotBlank(fileDomain) && !HttpUtils.isHttpProtocol(url)) {
                    if (!fileDomain.endsWith("/") && !url.startsWith("/")) {
                        fileDomain = fileDomain + "/";
                    } else if (fileDomain.endsWith("/") && url.startsWith("/")) {
                        url = url.substring(1);
                    }
                    if (!HttpUtils.isHttpProtocol(fileDomain)) {
                        fileDomain = "http://" + fileDomain;
                    }
                    url = fileDomain + url;
                } else {
                    url = req.getContextPath() + (url.startsWith("/") ? url : "/" + url);
                }
                codeBuffer.append(url);
                codeBuffer.append(")");
                i++;
            }
            if (i == 0) {
                return code;
            } else {
                if (codeFragments.length > i && StringUtils.isNotBlank(codeFragments[i])) {
                    codeBuffer.append(codeFragments[i]);
                }
            }
            logger.debug("URL?...");
            break;
        default:
            return code;
        }
        return codeBuffer.toString();
    }

    private static class CompilerErrorManager extends com.google.common.css.compiler.ast.BasicErrorManager {
        public void print(String msg) {
            System.err.println(msg);
        }
    }

    public enum FileType {
        JS {
            @Override
            boolean contains(FileType type) {
                return equals(type) || TPL.contains(type);
            }

            @Override
            public boolean contains(String type) {
                return name().equals(type.toUpperCase()) || TPL.contains(type);
            }
        },
        TPL {
            @Override
            boolean contains(FileType type) {
                return equals(type) || ArrayUtils.indexOf(FileType.values(), type) == -1;
            }

            @Override
            public boolean contains(String type) {
                boolean is = name().equals(type.toUpperCase());
                if (!is) {
                    try {
                        FileType.valueOf(type.toUpperCase());
                    } catch (Exception e) {
                        is = true;
                    }
                }
                return is;
            }
        },
        CSS {
            @Override
            boolean contains(FileType type) {
                return equals(type);
            }

            @Override
            public boolean contains(String type) {
                return name().equals(type.toUpperCase());
            }
        },
        GSS {
            @Override
            boolean contains(FileType type) {
                return equals(type) || CSS.contains(type);
            }

            @Override
            public boolean contains(String type) {
                return CSS.contains(type) || name().equals(type.toUpperCase());
            }
        },
        LESS {
            @Override
            boolean contains(FileType type) {
                return equals(type) || CSS.contains(type);
            }

            @Override
            public boolean contains(String type) {
                return CSS.contains(type) || name().equals(type.toUpperCase());
            }
        },
        MSS {
            @Override
            boolean contains(FileType type) {
                return equals(type) || CSS.contains(type) || GSS.contains(type) || LESS.contains(type);
            }

            @Override
            public boolean contains(String type) {
                return name().equals(type.toUpperCase()) || CSS.contains(type) || GSS.contains(type)
                        || LESS.contains(type);
            }
        };

        abstract boolean contains(String type);

        abstract boolean contains(FileType type);
    }

    /**
     * ???
     *
     * @param fileUrlList
     * @param request
     * @param response
     * @param type
     * @return
     * @throws com.log4ic.compressor.exception.CompressionException
     *
     */
    public static List<SourceCode> mergeCode(String[] fileUrlList, HttpServletRequest request,
            HttpServletResponse response, FileType type) throws CompressionException {
        List<SourceCode> codeList = new FastList<SourceCode>();
        ContentResponseWrapper wrapperResponse = null;
        //???
        for (String url : fileUrlList) {
            int index = url.lastIndexOf(".");
            if (index < 0) {
                continue;
            }
            if (type.contains(url.substring(index + 1))) {
                String fragment;
                try {
                    url = URLDecoder.decode(url, "utf8");
                } catch (UnsupportedEncodingException e) {
                    throw new CompressionException(e);
                }
                try {
                    //http/https ??
                    if (HttpUtils.isHttpProtocol(url)) {
                        fragment = importCode(HttpUtils.requestFile(url), url, type, request, response);
                    } else {
                        //??
                        if (wrapperResponse == null) {
                            wrapperResponse = new ContentResponseWrapper(response);
                        }
                        request.getRequestDispatcher(url).include(request, wrapperResponse);
                        wrapperResponse.flushBuffer();
                        fragment = wrapperResponse.getContent();
                        fragment = importCode(fragment, url, type, request, response);
                        ((ContentResponseStream) wrapperResponse.getOutputStream()).reset();
                    }
                } catch (ServletException e) {
                    throw new CompressionException("ServletException", e);
                } catch (IOException e) {
                    throw new CompressionException(e);
                } catch (GssParserException e) {
                    throw new CompressionException(e);
                } catch (LessException e) {
                    throw new CompressionException(e);
                }
                if (StringUtils.isNotBlank(fragment)) {
                    codeList.add(new SourceCode(url, fragment));
                }
            }
        }

        if (wrapperResponse != null) {
            try {
                wrapperResponse.close();
            } catch (IOException e) {
                throw new CompressionException(e);
            } catch (Throwable throwable) {
                throw new CompressionException("Close Response error", throwable);
            }
        }
        return codeList;
    }

    private static JobDescription.OptimizeStrategy getCompressGssOptimizeStrategy(String levelStr)
            throws CompressionException {
        JobDescription.OptimizeStrategy level = JobDescription.OptimizeStrategy.SAFE;
        if (StringUtils.isNotBlank(levelStr)) {
            try {
                level = JobDescription.OptimizeStrategy.values()[Integer.parseInt(levelStr)];
            } catch (Exception e) {
                try {
                    level = JobDescription.OptimizeStrategy.valueOf(levelStr);
                } catch (IllegalArgumentException ae) {
                    //
                }
            }
        }
        return level;
    }

    private static SourceFile processAMDDefine(final SourceFile source) throws IOException {
        //?AMD????
        Map<String, String> params = HttpUtils.getParameterMap(URI.create(source.getName()));
        final String moduleName = params.get("amd");
        if (StringUtils.isNotBlank(moduleName)) {
            //JSdefine???

            Parser jsParser = new Parser();
            AstRoot jsRoot = jsParser.parse(source.getCode(), source.getName(), 1);

            //define?????
            jsRoot.visit(new NodeVisitor() {
                @Override
                public boolean visit(AstNode nodes) {
                    //define?
                    if (nodes instanceof ExpressionStatement) {
                        ExpressionStatement es = (ExpressionStatement) nodes;
                        AstNode ex = es.getExpression();
                        if (ex instanceof FunctionCall) {
                            FunctionCall fc = (FunctionCall) ex;
                            if (fc.getTarget().toSource().equals("define")) {
                                List<AstNode> args = fc.getArguments();
                                //????
                                StringLiteral moduleNameSl;
                                if (args.size() > 0 && args.get(0) instanceof StringLiteral) {
                                    moduleNameSl = ((StringLiteral) args.get(0));
                                } else {
                                    moduleNameSl = new StringLiteral();
                                    args.add(0, moduleNameSl);
                                    moduleNameSl.setParent(fc);
                                    moduleNameSl.setQuoteCharacter('\'');
                                }
                                moduleNameSl.setValue(moduleName);
                                return false;
                            }
                        }
                    }
                    return true;
                }
            });

            return SourceFile.fromCode(source.getName(), jsRoot.toSource());
        }
        return source;
    }

    private static URI getTemplateUri(String fileName) {
        URI uri = URI.create(fileName);
        Map<String, String> params = HttpUtils.getParameterMap(uri);
        if (StringUtils.isNotBlank(params.get("amd"))
                || (("amd".equals(params.get("mode")) || "1".equals(params.get("mode")))
                        && StringUtils.isNotBlank(params.get("name")))) {
            StringBuilder path = new StringBuilder(uri.getRawPath());
            path.append("?");
            for (String name : params.keySet()) {
                if (!name.equals("amd") && !name.equals("name")) {
                    path.append(name).append("=").append(params.get(name)).append("&");
                }
            }
            if (params.containsKey("amd")) {
                path.append("amd");
            } else {
                path.deleteCharAt(path.length() - 1);
            }
            uri = URI.create(path.toString());
        }
        return uri;
    }

    /**
     * 
     *
     * @param fileSourceList
     * @param request
     * @param type
     * @return
     * @throws com.google.common.css.compiler.ast.GssParserException
     *
     * @throws com.log4ic.compressor.exception.CompressionException
     *
     */
    public static String compressCode(List<SourceCode> fileSourceList, HttpServletRequest request, FileType type)
            throws GssParserException, CompressionException, LessException, IOException {
        //
        boolean isDebug = HttpUtils.getBooleanParam(request, "debug");
        String code = "";
        if (fileSourceList.size() > 0) {
            String levelParam = request.getParameter("level");
            switch (type) {
            case JS:
                CompilationLevel level = CompilationLevel.SIMPLE_OPTIMIZATIONS;
                if (StringUtils.isNotBlank(levelParam)) {
                    try {
                        level = CompilationLevel.values()[Integer.parseInt(levelParam)];
                    } catch (Exception e) {
                        try {
                            level = CompilationLevel.valueOf(levelParam);
                        } catch (IllegalArgumentException ae) {
                            //
                        }
                    }
                }
                List<SourceFile> sourceFiles = Lists.newArrayList();
                for (SourceCode source : fileSourceList) {
                    //??
                    SourceFile jsSource = SourceFile.fromCode(source.getFileName(), source.getFileContents());
                    if (FileType.TPL.contains(getFileTypeString(source.getFileName()))) {
                        URI uri;
                        //                            if (fileSourceList.size() <= 1) {
                        //                                uri = getTemplateUri(source.getFileName());
                        //                            } else {
                        uri = URI.create(source.getFileName());
                        //                            }
                        String tpl = JavascriptTemplateEngine.compress(uri, source.getFileContents());
                        jsSource = SourceFile.fromCode(source.getFileName(), tpl);
                    } else if (fileSourceList.size() > 1) {
                        jsSource = processAMDDefine(jsSource);
                    }
                    sourceFiles.add(jsSource);
                }
                code = Compressor.compressJS(sourceFiles, level, isDebug);
                break;
            case CSS:
                code = Compressor.compressGss(fileSourceList, getGssFormat(isDebug, request),
                        getCompressGssOptimizeStrategy(levelParam));
                break;
            case GSS:
                code = Compressor.compressGss(fileSourceList, getGssFormat(isDebug, request),
                        buildTrueConditions(request), getCompressGssOptimizeStrategy(levelParam));
                break;
            case LESS:
            case MSS:
                //??
                code = Compressor.compressLess(fileSourceList, getGssFormat(isDebug, request),
                        buildTrueConditions(request), getCompressGssOptimizeStrategy(levelParam));
                break;
            }
        }
        return code;
    }

    private static JobDescription.OutputFormat getGssFormat(boolean isDebug, HttpServletRequest request) {
        JobDescription.OutputFormat format = JobDescription.OutputFormat.COMPRESSED;
        if (isDebug) {
            format = JobDescription.OutputFormat.DEBUG;
        } else if (HttpUtils.getBooleanParam(request, "pretty")) {
            format = JobDescription.OutputFormat.PRETTY_PRINTED;
        }
        return format;
    }

    private static List<String> buildTrueConditions(HttpServletRequest request) {
        List<String> conditions = new FastList<String>();
        if (!HttpUtils.getBooleanParam(request, "condition")) {
            return conditions;
        }
        //???
        List<BrowserInfo> browserInfoList = HttpUtils.getRequestBrowserInfo(request);
        String prefix = "browser_";
        //?
        for (BrowserInfo info : browserInfoList) {
            //?  BROWSER_IE
            conditions.add((prefix + info.getBrowserType()).toUpperCase());
            Double version = info.getBrowserVersion();
            if (version != null) {
                String versionStr = version.toString();
                //???  BROWSER_IE6.2
                conditions.add((prefix + info.getBrowserType() + versionStr.replace(".", "_")).toUpperCase());
                versionStr = versionStr.substring(0, versionStr.indexOf("."));
                //??  BROWSER_IE6
                conditions.add((prefix + info.getBrowserType() + versionStr).toUpperCase());
            }
        }

        List<String> platformList = HttpUtils.getRequestPlatform(request);

        for (String platform : platformList) {
            conditions.add(("platform_" + platform).toUpperCase());
        }

        return conditions;
    }

    /**
     * 
     *
     * @param request
     * @param response
     * @return
     */
    public static void compress(HttpServletRequest request, HttpServletResponse response)
            throws CompressionException, GssParserException, LessException {
        compress(request, response, null, null);
    }

    public static void compress(HttpServletRequest request, HttpServletResponse response, CacheManager cacheManager)
            throws CompressionException, GssParserException, LessException {
        compress(request, response, cacheManager, null);
    }

    /**
     * ???
     *
     * @param queryString
     * @return
     * @throws com.log4ic.compressor.exception.CompressionException
     *
     */
    private static String removeDuplicateParameters(String queryString) throws CompressionException {
        //        try {
        //            queryString = URLDecoder.decode(queryString, "utf8");
        //        } catch (UnsupportedEncodingException e) {
        //            throw new CompressionException(e);
        //        }

        String root = null;
        int rootIndex = queryString.indexOf("root=");
        if (rootIndex > -1) {
            root = queryString.substring(rootIndex + 5);
            int rootEnd = root.indexOf("&");
            root = root.substring(0, rootEnd);
            queryString = queryString.substring(0, rootIndex) + queryString.substring(rootEnd + 1);
        }

        String[] params = queryString.split("&");

        List<String> noRepeatParams = Lists.newArrayList();
        //???
        for (String param : params) {
            String[] p = param.split("=");
            if (StringUtils.isNotBlank(p[0])) {
                if (StringUtils.isNotBlank(root)) {
                    p[0] = root + (root.endsWith("/") || p[0].startsWith("/") ? "" : "/") + p[0];
                }
                if (!noRepeatParams.contains(p[0])) {
                    if (p.length > 1) {
                        StringBuffer buffer = new StringBuffer();
                        noRepeatParams.add(buffer.append(p[0]).append("=").append(p[1]).toString());
                    } else {
                        noRepeatParams.add(p[0]);
                    }
                }
            }
        }

        return StringUtils.join(noRepeatParams, "&");
    }

    /**
     * ??
     *
     * @param code
     * @param type
     * @param response
     * @throws com.log4ic.compressor.exception.CompressionException
     *
     */
    private static void writeOutCode(String code, FileType type, HttpServletResponse response)
            throws CompressionException {
        //mine type
        try {
            switch (type) {
            case JS:
                response.setContentType("text/javascript");
                break;
            case CSS:
            case GSS:
            case LESS:
            case MSS:
                response.setContentType("text/css");
                break;
            default:
                response.setContentType("text/html");
            }
            PrintWriter writer = response.getWriter();
            writer.write(code);
            writer.flush();
            response.flushBuffer();
        } catch (IOException e) {
            throw new CompressionException("Write code to client error.", e);
        }
    }

    public static FileType getFileType(HttpServletRequest request) throws CompressionException {
        return getFileType(HttpUtils.getRequestUri(request));
    }

    public static String getFileTypeString(String uri) throws CompressionException {
        //?
        String[] uris = uri.split("\\.");
        if (uris.length == 0) {
            throw new UnsupportedFileTypeException();
        }
        FileType type;
        //???
        String typeStr = uris[uris.length - 1].toUpperCase();
        int paramIndexTag = typeStr.indexOf("?");
        return typeStr.substring(0, paramIndexTag == -1 ? typeStr.length() : paramIndexTag);
    }

    public static FileType getFileType(String uri) throws CompressionException {
        FileType type;
        //???
        try {
            type = FileType.valueOf(getFileTypeString(uri));
        } catch (Exception e) {
            throw new UnsupportedFileTypeException();
        }
        return type;
    }

    private static String mergeCode(List<SourceCode> codeList, HttpServletRequest request, FileType type)
            throws LessException, GssParserException, CompressionException, IOException {
        StringBuilder builder = new StringBuilder();
        List<String> con = buildTrueConditions(request);

        if (type == FileType.LESS || type == FileType.MSS) {
            codeList = LessEngine.parseLess(codeList, con);
            for (SourceCode code : codeList) {
                if (getFileType(code.getFileName()) == FileType.LESS) {
                    builder.append(code.getFileContents()).append("\n");
                }
            }
        }

        if (type == FileType.GSS || type == FileType.MSS) {
            builder.append(parseGss(codeList, con));
        }

        if (type == FileType.JS || type == FileType.CSS) {
            for (SourceCode code : codeList) {
                if (FileType.TPL.contains(getFileTypeString(code.getFileName()))) {
                    URI uri;
                    //                    if (codeList.size() <= 1) {
                    //                        uri = getTemplateUri(code.getFileName());
                    //                    } else {
                    uri = URI.create(code.getFileName());
                    //                    }
                    String tpl = JavascriptTemplateEngine.parse(uri, code.getFileContents());
                    builder.append(tpl).append("\n");
                } else {
                    if (type == FileType.JS && codeList.size() > 1) {
                        builder.append(
                                processAMDDefine(SourceFile.fromCode(code.getFileName(), code.getFileContents()))
                                        .getCode());
                    } else {
                        builder.append(code.getFileContents());
                    }
                    builder.append("\n");
                }
            }
        }

        return builder.toString();
    }

    /**
     * ??
     *
     * @param type
     * @param queryString
     * @param cacheManager
     * @param request
     * @param response
     * @param fileDomain
     * @return
     * @throws com.log4ic.compressor.exception.CompressionException
     *
     * @throws com.google.common.css.compiler.ast.GssParserException
     *
     */
    private static String buildCode(final FileType type, final String queryString, final CacheManager cacheManager,
            HttpServletRequest request, HttpServletResponse response, String fileDomain)
            throws CompressionException, GssParserException, LessException {
        Cache cache = null;
        try {
            //???
            if (cacheManager != null) {
                byte[] cacheLock;
                //??
                synchronized (progressCacheLock) {
                    cacheLock = progressCacheLock.get(queryString);
                }
                if (cacheLock != null) {
                    //??
                    synchronized (cacheLock) {
                        logger.debug("?");
                        cacheLock.wait();
                    }
                    //
                    cache = cacheManager.get(queryString);
                } else {
                    //?queryString?
                    cacheLock = new byte[0];
                    synchronized (progressCacheLock) {
                        progressCacheLock.put(queryString, cacheLock);
                    }
                }
            }
            String code;
            //??cache??
            if (cache == null || cache.isExpired()) {
                //???
                List<SourceCode> codeList = mergeCode(queryString.split("&"), request, response, type);
                List<SourceCode> sourceCodeList = Lists.newArrayList();
                //css?
                for (SourceCode source : codeList) {
                    SourceCode s = new SourceCode(source.getFileName(),
                            fixUrlPath(request, source.getFileContents(), source.getFileName(), type, fileDomain));
                    sourceCodeList.add(s);
                }
                //
                code = HttpUtils.getBooleanParam(request, "nocompress") ? mergeCode(sourceCodeList, request, type)
                        : compressCode(sourceCodeList, request, type);
                if (cacheManager != null) {
                    final String finalCode = code;
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                cacheManager.put(queryString, finalCode, type);
                            } catch (Exception e) {
                                logger.error("", e);
                            } finally {
                                byte[] cacheLock;
                                synchronized (progressCacheLock) {
                                    cacheLock = progressCacheLock.remove(queryString);
                                }
                                synchronized (cacheLock) {
                                    cacheLock.notifyAll();
                                }
                            }
                        }
                    }).start();
                }
                return code;
            } else {
                return cache.getContent();
            }
        } catch (Exception e) {
            if (cacheManager != null) {
                byte[] cacheLock;
                synchronized (progressCacheLock) {
                    cacheLock = progressCacheLock.remove(queryString);
                }
                synchronized (cacheLock) {
                    cacheLock.notifyAll();
                }
            }
            throw new CompressionException(e);
        }
    }

    /**
     * 
     *
     * @param request
     * @param response
     * @param cacheManager
     * @param fileDomain
     * @return
     * @throws com.log4ic.compressor.exception.CompressionException
     *
     * @throws GssParserException
     */
    public static void compress(HttpServletRequest request, HttpServletResponse response, CacheManager cacheManager,
            String fileDomain) throws CompressionException, GssParserException, LessException {

        String queryString = HttpUtils.getQueryString(request);

        if (StringUtils.isBlank(queryString)) {
            throw new QueryStringEmptyException();
        }

        //???
        queryString = removeDuplicateParameters(queryString);

        FileType type = getFileType(request);

        if ((type == FileType.GSS || type == FileType.MSS || type == FileType.LESS)
                && HttpUtils.getBooleanParam(request, "condition")) {
            List<BrowserInfo> browserInfoList = HttpUtils.getRequestBrowserInfo(request);
            List<String> platformList = HttpUtils.getRequestPlatform(request);
            if (browserInfoList.size() > 0) {
                BrowserInfo info = browserInfoList.get(0);
                String versionStr = info.getBrowserVersion().toString();
                queryString += "&" + info.getBrowserType() + versionStr.replace(".", "_");
            }
            if (platformList.size() > 0) {
                String platform = platformList.get(0);
                queryString += "&" + platform;
            }
        }

        String code;

        Cache cache = null;
        if (cacheManager != null) {
            //????
            byte[] lock = progressCacheLock.get(queryString);
            if (lock != null) {
                try {
                    synchronized (progressCacheLock) {
                        lock = progressCacheLock.get(queryString);
                    }
                    if (lock != null) {
                        logger.debug("");
                        synchronized (lock) {
                            lock.wait();
                        }
                    }
                } catch (InterruptedException e) {
                    logger.error("", e);
                }
            }
            cache = cacheManager.get(queryString);
        }

        //???????
        if (cache == null || cache.isExpired()) {
            //?
            if (cacheManager != null) {
                logger.debug("?");
            } else {
                logger.debug("?");
            }
            code = buildCode(type, queryString, cacheManager, request, response, fileDomain);
            logger.debug("?");
        } else {
            logger.debug("??");
            code = cache.getContent();
        }

        logger.debug("?...");
        writeOutCode(code, type, response);
    }

}