Java tutorial
/* * 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); } }