You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2013/08/26 08:57:52 UTC
[18/21] git commit: Complete the rename of the module and the packages
Complete the rename of the module and the packages
Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/6e42b37c
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/6e42b37c
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/6e42b37c
Branch: refs/heads/master
Commit: 6e42b37cf25536a16f8e9a1c483296e98a38b6b8
Parents: 31fedfe
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Sat Aug 24 21:51:46 2013 -0700
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Sat Aug 24 21:51:46 2013 -0700
----------------------------------------------------------------------
tapestry-webresources/LICENSE-YUICompressor.txt | 3 +
tapestry-webresources/LICENSE.txt | 202 ++++++
tapestry-webresources/NOTICE.txt | 5 +
tapestry-webresources/build.gradle | 32 +
.../platform/yui/compressor/CssCompressor.java | 490 +++++++++++++
.../webresources/AbstractMinimizer.java | 107 +++
.../internal/webresources/CSSMinimizer.java | 58 ++
.../internal/webresources/CacheMode.java | 40 +
.../webresources/CoffeeScriptCompiler.java | 82 +++
.../webresources/ContentChangeTracker.java | 61 ++
.../webresources/GoogleClosureMinimizer.java | 79 ++
.../webresources/LessResourceTransformer.java | 120 +++
.../ResourceDependenciesSplitter.java | 40 +
.../webresources/ResourceTransformUtils.java | 68 ++
.../ResourceTransformerFactory.java | 45 ++
.../ResourceTransformerFactoryImpl.java | 269 +++++++
.../internal/webresources/RhinoExecutor.java | 24 +
.../webresources/RhinoExecutorPool.java | 147 ++++
.../webresources/WebResourcesSymbols.java | 25 +
.../modules/WebResourcesModule.java | 126 ++++
.../webresources/internal/coffeescript-1.6.3.js | 12 +
.../internal/invoke-coffeescript.js | 14 +
.../t5/webresources/components/Layout.groovy | 20 +
.../groovy/t5/webresources/pages/Index.groovy | 9 +
.../t5/webresources/pages/MultiLess.groovy | 7 +
.../webresources/tests/WebResourcesSpec.groovy | 61 ++
.../t5/webresources/services/AppModule.java | 50 ++
.../src/test/resources/GebConfig.groovy | 8 +
.../test/resources/META-INF/assets/colors.less | 2 +
.../test/resources/META-INF/assets/index.less | 5 +
.../test/resources/META-INF/assets/multi.less | 11 +
.../resources/META-INF/modules/index.coffee | 3 +
.../src/test/resources/log4j.properties | 12 +
.../t5/webresources/components/Layout.tml | 42 ++
.../resources/t5/webresources/pages/Index.tml | 7 +
.../t5/webresources/pages/MultiLess.tml | 16 +
.../src/test/webapp/WEB-INF/web.xml | 19 +
.../fonts/glyphicons-halflings-regular.eot | Bin 0 -> 14079 bytes
.../fonts/glyphicons-halflings-regular.svg | 228 ++++++
.../fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 29512 bytes
.../fonts/glyphicons-halflings-regular.woff | Bin 0 -> 16448 bytes
.../src/test/webapp/bootstrap/js/affix.js | 126 ++++
.../src/test/webapp/bootstrap/js/alert.js | 98 +++
.../src/test/webapp/bootstrap/js/button.js | 109 +++
.../src/test/webapp/bootstrap/js/carousel.js | 217 ++++++
.../src/test/webapp/bootstrap/js/collapse.js | 179 +++++
.../src/test/webapp/bootstrap/js/dropdown.js | 154 ++++
.../src/test/webapp/bootstrap/js/modal.js | 246 +++++++
.../src/test/webapp/bootstrap/js/popover.js | 117 +++
.../src/test/webapp/bootstrap/js/scrollspy.js | 158 ++++
.../src/test/webapp/bootstrap/js/tab.js | 135 ++++
.../src/test/webapp/bootstrap/js/tooltip.js | 386 ++++++++++
.../src/test/webapp/bootstrap/js/transition.js | 56 ++
.../src/test/webapp/bootstrap/less/alerts.less | 67 ++
.../src/test/webapp/bootstrap/less/badges.less | 51 ++
.../test/webapp/bootstrap/less/bootstrap.less | 59 ++
.../test/webapp/bootstrap/less/breadcrumbs.less | 23 +
.../webapp/bootstrap/less/button-groups.less | 248 +++++++
.../src/test/webapp/bootstrap/less/buttons.less | 160 ++++
.../test/webapp/bootstrap/less/carousel.less | 209 ++++++
.../src/test/webapp/bootstrap/less/close.less | 33 +
.../src/test/webapp/bootstrap/less/code.less | 56 ++
.../bootstrap/less/component-animations.less | 29 +
.../test/webapp/bootstrap/less/dropdowns.less | 193 +++++
.../src/test/webapp/bootstrap/less/forms.less | 353 +++++++++
.../test/webapp/bootstrap/less/glyphicons.less | 232 ++++++
.../src/test/webapp/bootstrap/less/grid.less | 346 +++++++++
.../webapp/bootstrap/less/input-groups.less | 127 ++++
.../test/webapp/bootstrap/less/jumbotron.less | 40 +
.../src/test/webapp/bootstrap/less/labels.less | 58 ++
.../test/webapp/bootstrap/less/list-group.less | 88 +++
.../src/test/webapp/bootstrap/less/media.less | 56 ++
.../src/test/webapp/bootstrap/less/mixins.less | 723 +++++++++++++++++++
.../src/test/webapp/bootstrap/less/modals.less | 141 ++++
.../src/test/webapp/bootstrap/less/navbar.less | 621 ++++++++++++++++
.../src/test/webapp/bootstrap/less/navs.less | 229 ++++++
.../test/webapp/bootstrap/less/normalize.less | 396 ++++++++++
.../src/test/webapp/bootstrap/less/pager.less | 55 ++
.../test/webapp/bootstrap/less/pagination.less | 83 +++
.../src/test/webapp/bootstrap/less/panels.less | 148 ++++
.../test/webapp/bootstrap/less/popovers.less | 133 ++++
.../src/test/webapp/bootstrap/less/print.less | 100 +++
.../webapp/bootstrap/less/progress-bars.less | 95 +++
.../bootstrap/less/responsive-utilities.less | 220 ++++++
.../test/webapp/bootstrap/less/scaffolding.less | 130 ++++
.../src/test/webapp/bootstrap/less/tables.less | 236 ++++++
.../src/test/webapp/bootstrap/less/theme.less | 232 ++++++
.../test/webapp/bootstrap/less/thumbnails.less | 31 +
.../src/test/webapp/bootstrap/less/tooltip.less | 95 +++
.../src/test/webapp/bootstrap/less/type.less | 238 ++++++
.../test/webapp/bootstrap/less/utilities.less | 42 ++
.../test/webapp/bootstrap/less/variables.less | 620 ++++++++++++++++
.../src/test/webapp/bootstrap/less/wells.less | 29 +
93 files changed, 11555 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6e42b37c/tapestry-webresources/LICENSE-YUICompressor.txt
----------------------------------------------------------------------
diff --git a/tapestry-webresources/LICENSE-YUICompressor.txt b/tapestry-webresources/LICENSE-YUICompressor.txt
new file mode 100644
index 0000000..9d17737
--- /dev/null
+++ b/tapestry-webresources/LICENSE-YUICompressor.txt
@@ -0,0 +1,3 @@
+BSD LICENSE
+
+(Need to provide the real text; but I'm on a plane!)
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6e42b37c/tapestry-webresources/LICENSE.txt
----------------------------------------------------------------------
diff --git a/tapestry-webresources/LICENSE.txt b/tapestry-webresources/LICENSE.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/tapestry-webresources/LICENSE.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6e42b37c/tapestry-webresources/NOTICE.txt
----------------------------------------------------------------------
diff --git a/tapestry-webresources/NOTICE.txt b/tapestry-webresources/NOTICE.txt
new file mode 100644
index 0000000..a5155e9
--- /dev/null
+++ b/tapestry-webresources/NOTICE.txt
@@ -0,0 +1,5 @@
+This product includes software developed by
+The Apache Software Foundation (http://www.apache.org/).
+
+This product includes source from the YUI Compressor library, available under a BSD License.
+http://yui.github.io/yuicompressor/
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6e42b37c/tapestry-webresources/build.gradle
----------------------------------------------------------------------
diff --git a/tapestry-webresources/build.gradle b/tapestry-webresources/build.gradle
new file mode 100644
index 0000000..0d9813e
--- /dev/null
+++ b/tapestry-webresources/build.gradle
@@ -0,0 +1,32 @@
+description = "Integration with WRO4J to perform runtime CoffeeScript compilation, JavaScript minimization, and more."
+
+dependencies {
+ compile project(":tapestry-core")
+ compile "com.github.sommeri:less4j:1.1.1"
+ compile "com.google.javascript:closure-compiler:v20130722"
+ compile "org.mozilla:rhino:1.7R4"
+
+ testCompile project(":tapestry-runner")
+ testCompile "org.gebish:geb-spock:${versions.geb}"
+ testCompile "org.spockframework:spock-core:${versions.spock}"
+
+ testCompile "org.seleniumhq.selenium:selenium-java:${versions.selenium}", {
+ exclude group: "org.eclipse.jetty"
+ }
+ testCompile "org.seleniumhq.selenium:selenium-server:${versions.selenium}", {
+ exclude group: "org.eclipse.jetty"
+ }
+}
+
+jar.manifest {
+ attributes 'Tapestry-Module-Classes': 'org.apache.tapestry5.webresources.modules.WebResourcesModule'
+}
+
+
+test {
+ useJUnit()
+
+ systemProperties("geb.build.reportsDir": "$reporting.baseDir/geb",
+ "tapestry.compiled-asset-cache-dir": "$buildDir/compiled-asset-cache",
+ "tapestry.production-mode": "false")
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6e42b37c/tapestry-webresources/src/main/java/com/yahoo/platform/yui/compressor/CssCompressor.java
----------------------------------------------------------------------
diff --git a/tapestry-webresources/src/main/java/com/yahoo/platform/yui/compressor/CssCompressor.java b/tapestry-webresources/src/main/java/com/yahoo/platform/yui/compressor/CssCompressor.java
new file mode 100644
index 0000000..6db09d7
--- /dev/null
+++ b/tapestry-webresources/src/main/java/com/yahoo/platform/yui/compressor/CssCompressor.java
@@ -0,0 +1,490 @@
+/*
+ * YUI Compressor
+ * http://developer.yahoo.com/yui/compressor/
+ * Author: Julien Lecomte - http://www.julienlecomte.net/
+ * Author: Isaac Schlueter - http://foohack.com/
+ * Author: Stoyan Stefanov - http://phpied.com/
+ * Contributor: Dan Beam - http://danbeam.org/
+ * Copyright (c) 2013 Yahoo! Inc. All rights reserved.
+ * The copyrights embodied in the content of this file are licensed
+ * by Yahoo! Inc. under the BSD (revised) open source license.
+ */
+package com.yahoo.platform.yui.compressor;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class CssCompressor {
+
+ private StringBuffer srcsb = new StringBuffer();
+
+ public CssCompressor(Reader in) throws IOException {
+ // Read the stream...
+ int c;
+ while ((c = in.read()) != -1) {
+ srcsb.append((char) c);
+ }
+ }
+
+ // Leave data urls alone to increase parse performance.
+ protected String extractDataUrls(String css, ArrayList preservedTokens) {
+
+ int maxIndex = css.length() - 1;
+ int appendIndex = 0;
+
+ StringBuffer sb = new StringBuffer();
+
+ Pattern p = Pattern.compile("(?i)url\\(\\s*([\"']?)data\\:");
+ Matcher m = p.matcher(css);
+
+ /*
+ * Since we need to account for non-base64 data urls, we need to handle
+ * ' and ) being part of the data string. Hence switching to indexOf,
+ * to determine whether or not we have matching string terminators and
+ * handling sb appends directly, instead of using matcher.append* methods.
+ */
+
+ while (m.find()) {
+
+ int startIndex = m.start() + 4; // "url(".length()
+ String terminator = m.group(1); // ', " or empty (not quoted)
+
+ if (terminator.length() == 0) {
+ terminator = ")";
+ }
+
+ boolean foundTerminator = false;
+
+ int endIndex = m.end() - 1;
+ while(foundTerminator == false && endIndex+1 <= maxIndex) {
+ endIndex = css.indexOf(terminator, endIndex+1);
+
+ if ((endIndex > 0) && (css.charAt(endIndex-1) != '\\')) {
+ foundTerminator = true;
+ if (!")".equals(terminator)) {
+ endIndex = css.indexOf(")", endIndex);
+ }
+ }
+ }
+
+ // Enough searching, start moving stuff over to the buffer
+ sb.append(css.substring(appendIndex, m.start()));
+
+ if (foundTerminator) {
+ String token = css.substring(startIndex, endIndex);
+ token = token.replaceAll("\\s+", "");
+ preservedTokens.add(token);
+
+ String preserver = "url(___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___)";
+ sb.append(preserver);
+
+ appendIndex = endIndex + 1;
+ } else {
+ // No end terminator found, re-add the whole match. Should we throw/warn here?
+ sb.append(css.substring(m.start(), m.end()));
+ appendIndex = m.end();
+ }
+ }
+
+ sb.append(css.substring(appendIndex));
+
+ return sb.toString();
+ }
+
+ private String preserveOldIESpecificMatrixDefinition(String css, ArrayList preservedTokens) {
+ StringBuffer sb = new StringBuffer();
+ Pattern p = Pattern.compile("\\s*filter:\\s*progid:DXImageTransform.Microsoft.Matrix\\(([^\\)]+)\\);");
+ Matcher m = p.matcher(css);
+ while (m.find()) {
+ String token = m.group(1);
+ preservedTokens.add(token);
+ String preserver = "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___";
+ m.appendReplacement(sb, "filter:progid:DXImageTransform.Microsoft.Matrix(" + preserver + ");");
+ }
+ m.appendTail(sb);
+ return sb.toString();
+ }
+
+ public void compress(Writer out, int linebreakpos)
+ throws IOException {
+
+ Pattern p;
+ Matcher m;
+ String css = srcsb.toString();
+
+ int startIndex = 0;
+ int endIndex = 0;
+ int i = 0;
+ int max = 0;
+ ArrayList preservedTokens = new ArrayList(0);
+ ArrayList comments = new ArrayList(0);
+ String token;
+ int totallen = css.length();
+ String placeholder;
+
+ css = this.extractDataUrls(css, preservedTokens);
+
+ StringBuffer sb = new StringBuffer(css);
+
+ // collect all comment blocks...
+ while ((startIndex = sb.indexOf("/*", startIndex)) >= 0) {
+ endIndex = sb.indexOf("*/", startIndex + 2);
+ if (endIndex < 0) {
+ endIndex = totallen;
+ }
+
+ token = sb.substring(startIndex + 2, endIndex);
+ comments.add(token);
+ sb.replace(startIndex + 2, endIndex, "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + (comments.size() - 1) + "___");
+ startIndex += 2;
+ }
+ css = sb.toString();
+
+ // preserve strings so their content doesn't get accidentally minified
+ sb = new StringBuffer();
+ p = Pattern.compile("(\"([^\\\\\"]|\\\\.|\\\\)*\")|(\'([^\\\\\']|\\\\.|\\\\)*\')");
+ m = p.matcher(css);
+ while (m.find()) {
+ token = m.group();
+ char quote = token.charAt(0);
+ token = token.substring(1, token.length() - 1);
+
+ // maybe the string contains a comment-like substring?
+ // one, maybe more? put'em back then
+ if (token.indexOf("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_") >= 0) {
+ for (i = 0, max = comments.size(); i < max; i += 1) {
+ token = token.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", comments.get(i).toString());
+ }
+ }
+
+ // minify alpha opacity in filter strings
+ token = token.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity=");
+
+ preservedTokens.add(token);
+ String preserver = quote + "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___" + quote;
+ m.appendReplacement(sb, preserver);
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+
+ // strings are safe, now wrestle the comments
+ for (i = 0, max = comments.size(); i < max; i += 1) {
+
+ token = comments.get(i).toString();
+ placeholder = "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___";
+
+ // ! in the first position of the comment means preserve
+ // so push to the preserved tokens while stripping the !
+ if (token.startsWith("!")) {
+ preservedTokens.add(token);
+ css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
+ continue;
+ }
+
+ // \ in the last position looks like hack for Mac/IE5
+ // shorten that to /*\*/ and the next one to /**/
+ if (token.endsWith("\\")) {
+ preservedTokens.add("\\");
+ css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
+ i = i + 1; // attn: advancing the loop
+ preservedTokens.add("");
+ css = css.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
+ continue;
+ }
+
+ // keep empty comments after child selectors (IE7 hack)
+ // e.g. html >/**/ body
+ if (token.length() == 0) {
+ startIndex = css.indexOf(placeholder);
+ if (startIndex > 2) {
+ if (css.charAt(startIndex - 3) == '>') {
+ preservedTokens.add("");
+ css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
+ }
+ }
+ }
+
+ // in all other cases kill the comment
+ css = css.replace("/*" + placeholder + "*/", "");
+ }
+
+
+ // Normalize all whitespace strings to single spaces. Easier to work with that way.
+ css = css.replaceAll("\\s+", " ");
+
+ css = this.preserveOldIESpecificMatrixDefinition(css, preservedTokens);
+
+ // Remove the spaces before the things that should not have spaces before them.
+ // But, be careful not to turn "p :link {...}" into "p:link{...}"
+ // Swap out any pseudo-class colons with the token, and then swap back.
+ sb = new StringBuffer();
+ p = Pattern.compile("(^|\\})(([^\\{:])+:)+([^\\{]*\\{)");
+ m = p.matcher(css);
+ while (m.find()) {
+ String s = m.group();
+ s = s.replaceAll(":", "___YUICSSMIN_PSEUDOCLASSCOLON___");
+ s = s.replaceAll( "\\\\", "\\\\\\\\" ).replaceAll( "\\$", "\\\\\\$" );
+ m.appendReplacement(sb, s);
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+ // Remove spaces before the things that should not have spaces before them.
+ css = css.replaceAll("\\s+([!{};:>+\\(\\)\\],])", "$1");
+ // Restore spaces for !important
+ css = css.replaceAll("!important", " !important");
+ // bring back the colon
+ css = css.replaceAll("___YUICSSMIN_PSEUDOCLASSCOLON___", ":");
+
+ // retain space for special IE6 cases
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i):first\\-(line|letter)(\\{|,)");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, ":first-" + m.group(1).toLowerCase() + " " + m.group(2));
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // no space after the end of a preserved comment
+ css = css.replaceAll("\\*/ ", "*/");
+
+ // If there are multiple @charset directives, push them to the top of the file.
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i)^(.*)(@charset)( \"[^\"]*\";)");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, m.group(2).toLowerCase() + m.group(3) + m.group(1));
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // When all @charset are at the top, remove the second and after (as they are completely ignored).
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i)^((\\s*)(@charset)( [^;]+;\\s*))+");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, m.group(2) + m.group(3).toLowerCase() + m.group(4));
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // lowercase some popular @directives (@charset is done right above)
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i)@(font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframe|media|page|namespace)");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, '@' + m.group(1).toLowerCase());
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // lowercase some more common pseudo-elements
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i):(active|after|before|checked|disabled|empty|enabled|first-(?:child|of-type)|focus|hover|last-(?:child|of-type)|link|only-(?:child|of-type)|root|:selection|target|visited)");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, ':' + m.group(1).toLowerCase());
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // lowercase some more common functions
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i):(lang|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|(?:-(?:moz|webkit)-)?any)\\(");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, ':' + m.group(1).toLowerCase() + '(');
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // lower case some common function that can be values
+ // NOTE: rgb() isn't useful as we replace with #hex later, as well as and() is already done for us right after this
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i)([:,\\( ]\\s*)(attr|color-stop|from|rgba|to|url|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|max|min|(?:repeating-)?(?:linear|radial)-gradient)|-webkit-gradient)");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, m.group(1) + m.group(2).toLowerCase());
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // Put the space back in some cases, to support stuff like
+ // @media screen and (-webkit-min-device-pixel-ratio:0){
+ css = css.replaceAll("(?i)\\band\\(", "and (");
+
+ // Remove the spaces after the things that should not have spaces after them.
+ css = css.replaceAll("([!{}:;>+\\(\\[,])\\s+", "$1");
+
+ // remove unnecessary semicolons
+ css = css.replaceAll(";+}", "}");
+
+ // Replace 0(px,em,%) with 0.
+ css = css.replaceAll("(?i)(^|[^0-9])(?:0?\\.)?0(?:px|em|%|in|cm|mm|pc|pt|ex|deg|g?rad|m?s|k?hz)", "$10");
+
+ // Replace 0 0 0 0; with 0.
+ css = css.replaceAll(":0 0 0 0(;|})", ":0$1");
+ css = css.replaceAll(":0 0 0(;|})", ":0$1");
+ css = css.replaceAll(":0 0(;|})", ":0$1");
+
+
+ // Replace background-position:0; with background-position:0 0;
+ // same for transform-origin
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i)(background-position|webkit-mask-position|transform-origin|webkit-transform-origin|moz-transform-origin|o-transform-origin|ms-transform-origin):0(;|})");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, m.group(1).toLowerCase() + ":0 0" + m.group(2));
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // Replace 0.6 to .6, but only when preceded by : or a white-space
+ css = css.replaceAll("(:|\\s)0+\\.(\\d+)", "$1.$2");
+
+ // Shorten colors from rgb(51,102,153) to #336699
+ // This makes it more likely that it'll get further compressed in the next step.
+ p = Pattern.compile("rgb\\s*\\(\\s*([0-9,\\s]+)\\s*\\)");
+ m = p.matcher(css);
+ sb = new StringBuffer();
+ while (m.find()) {
+ String[] rgbcolors = m.group(1).split(",");
+ StringBuffer hexcolor = new StringBuffer("#");
+ for (i = 0; i < rgbcolors.length; i++) {
+ int val = Integer.parseInt(rgbcolors[i]);
+ if (val < 16) {
+ hexcolor.append("0");
+ }
+
+ // If someone passes an RGB value that's too big to express in two characters, round down.
+ // Probably should throw out a warning here, but generating valid CSS is a bigger concern.
+ if (val > 255) {
+ val = 255;
+ }
+ hexcolor.append(Integer.toHexString(val));
+ }
+ m.appendReplacement(sb, hexcolor.toString());
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // Shorten colors from #AABBCC to #ABC. Note that we want to make sure
+ // the color is not preceded by either ", " or =. Indeed, the property
+ // filter: chroma(color="#FFFFFF");
+ // would become
+ // filter: chroma(color="#FFF");
+ // which makes the filter break in IE.
+ // We also want to make sure we're only compressing #AABBCC patterns inside { }, not id selectors ( #FAABAC {} )
+ // We also want to avoid compressing invalid values (e.g. #AABBCCD to #ABCD)
+ p = Pattern.compile("(\\=\\s*?[\"']?)?" + "#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])" + "(:?\\}|[^0-9a-fA-F{][^{]*?\\})");
+
+ m = p.matcher(css);
+ sb = new StringBuffer();
+ int index = 0;
+
+ while (m.find(index)) {
+
+ sb.append(css.substring(index, m.start()));
+
+ boolean isFilter = (m.group(1) != null && !"".equals(m.group(1)));
+
+ if (isFilter) {
+ // Restore, as is. Compression will break filters
+ sb.append(m.group(1) + "#" + m.group(2) + m.group(3) + m.group(4) + m.group(5) + m.group(6) + m.group(7));
+ } else {
+ if( m.group(2).equalsIgnoreCase(m.group(3)) &&
+ m.group(4).equalsIgnoreCase(m.group(5)) &&
+ m.group(6).equalsIgnoreCase(m.group(7))) {
+
+ // #AABBCC pattern
+ sb.append("#" + (m.group(3) + m.group(5) + m.group(7)).toLowerCase());
+
+ } else {
+
+ // Non-compressible color, restore, but lower case.
+ sb.append("#" + (m.group(2) + m.group(3) + m.group(4) + m.group(5) + m.group(6) + m.group(7)).toLowerCase());
+ }
+ }
+
+ index = m.end(7);
+ }
+
+ sb.append(css.substring(index));
+ css = sb.toString();
+
+ // Replace #f00 -> red
+ css = css.replaceAll("(:|\\s)(#f00)(;|})", "$1red$3");
+ // Replace other short color keywords
+ css = css.replaceAll("(:|\\s)(#000080)(;|})", "$1navy$3");
+ css = css.replaceAll("(:|\\s)(#808080)(;|})", "$1gray$3");
+ css = css.replaceAll("(:|\\s)(#808000)(;|})", "$1olive$3");
+ css = css.replaceAll("(:|\\s)(#800080)(;|})", "$1purple$3");
+ css = css.replaceAll("(:|\\s)(#c0c0c0)(;|})", "$1silver$3");
+ css = css.replaceAll("(:|\\s)(#008080)(;|})", "$1teal$3");
+ css = css.replaceAll("(:|\\s)(#ffa500)(;|})", "$1orange$3");
+ css = css.replaceAll("(:|\\s)(#800000)(;|})", "$1maroon$3");
+
+ // border: none -> border:0
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i)(border|border-top|border-right|border-bottom|border-left|outline|background):none(;|})");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, m.group(1).toLowerCase() + ":0" + m.group(2));
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // shorter opacity IE filter
+ css = css.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity=");
+
+ // Find a fraction that is used for Opera's -o-device-pixel-ratio query
+ // Add token to add the "\" back in later
+ css = css.replaceAll("\\(([\\-A-Za-z]+):([0-9]+)\\/([0-9]+)\\)", "($1:$2___YUI_QUERY_FRACTION___$3)");
+
+ // Remove empty rules.
+ css = css.replaceAll("[^\\}\\{/;]+\\{\\}", "");
+
+ // Add "\" back to fix Opera -o-device-pixel-ratio query
+ css = css.replaceAll("___YUI_QUERY_FRACTION___", "/");
+
+ // TODO: Should this be after we re-insert tokens. These could alter the break points. However then
+ // we'd need to make sure we don't break in the middle of a string etc.
+ if (linebreakpos >= 0) {
+ // Some source control tools don't like it when files containing lines longer
+ // than, say 8000 characters, are checked in. The linebreak option is used in
+ // that case to split long lines after a specific column.
+ i = 0;
+ int linestartpos = 0;
+ sb = new StringBuffer(css);
+ while (i < sb.length()) {
+ char c = sb.charAt(i++);
+ if (c == '}' && i - linestartpos > linebreakpos) {
+ sb.insert(i, '\n');
+ linestartpos = i;
+ }
+ }
+
+ css = sb.toString();
+ }
+
+ // Replace multiple semi-colons in a row by a single one
+ // See SF bug #1980989
+ css = css.replaceAll(";;+", ";");
+
+ // restore preserved comments and strings
+ for(i = preservedTokens.size() - 1; i >= 0 ; i--) {
+ css = css.replace("___YUICSSMIN_PRESERVED_TOKEN_" + i + "___", preservedTokens.get(i).toString());
+ }
+
+ // Trim the final string (for any leading or trailing white spaces)
+ css = css.trim();
+
+ // Write the output...
+ out.write(css);
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6e42b37c/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/AbstractMinimizer.java
----------------------------------------------------------------------
diff --git a/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/AbstractMinimizer.java b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/AbstractMinimizer.java
new file mode 100644
index 0000000..7548bed
--- /dev/null
+++ b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/AbstractMinimizer.java
@@ -0,0 +1,107 @@
+// Copyright 2011-2013 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.webresources;
+
+import org.apache.tapestry5.internal.TapestryInternalUtils;
+import org.apache.tapestry5.internal.services.assets.BytestreamCache;
+import org.apache.tapestry5.internal.services.assets.StreamableResourceImpl;
+import org.apache.tapestry5.ioc.IOOperation;
+import org.apache.tapestry5.ioc.OperationTracker;
+import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
+import org.apache.tapestry5.services.assets.CompressionStatus;
+import org.apache.tapestry5.services.assets.ResourceMinimizer;
+import org.apache.tapestry5.services.assets.StreamableResource;
+import org.slf4j.Logger;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Base class for resource minimizers.
+ *
+ * @since 5.3
+ */
+public abstract class AbstractMinimizer implements ResourceMinimizer
+{
+ private static final double NANOS_TO_MILLIS = 1.0d / 1000000.0d;
+
+ protected final Logger logger;
+
+ protected final OperationTracker tracker;
+
+ private final AssetChecksumGenerator checksumGenerator;
+
+ private final String resourceType;
+
+ public AbstractMinimizer(Logger logger, OperationTracker tracker, AssetChecksumGenerator checksumGenerator, String resourceType)
+ {
+ this.logger = logger;
+ this.tracker = tracker;
+ this.resourceType = resourceType;
+ this.checksumGenerator = checksumGenerator;
+ }
+
+ public StreamableResource minimize(final StreamableResource input) throws IOException
+ {
+ long startNanos = System.nanoTime();
+
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
+
+ tracker.perform("Minimizing " + input, new IOOperation<Void>()
+ {
+ public Void perform() throws IOException
+ {
+ InputStream in = doMinimize(input);
+
+ TapestryInternalUtils.copy(in, bos);
+
+ return null;
+ }
+ });
+
+ // The content is minimized, but can still be (GZip) compressed.
+
+ StreamableResource output = new StreamableResourceImpl("minimized " + input.getDescription(),
+ input.getContentType(), CompressionStatus.COMPRESSABLE,
+ input.getLastModified(), new BytestreamCache(bos), checksumGenerator);
+
+ if (logger.isInfoEnabled())
+ {
+ long elapsedNanos = System.nanoTime() - startNanos;
+
+ int inputSize = input.getSize();
+ int outputSize = output.getSize();
+
+ double elapsedMillis = ((double) elapsedNanos) * NANOS_TO_MILLIS;
+ // e.g., reducing 100 bytes to 25 would be a (100-25)/100 reduction, or 75%
+ double reduction = 100d * ((double) (inputSize - outputSize)) / ((double) inputSize);
+
+ logger.info(String.format("Minimized %s (%,d input bytes of %s to %,d output bytes in %.2f ms, %.2f%% reduction)",
+ input.getDescription(), inputSize, resourceType, outputSize, elapsedMillis, reduction));
+ }
+
+ return output;
+ }
+
+ /**
+ * Implemented in subclasses to do the actual work.
+ *
+ * @param resource
+ * content to minimize
+ * @return stream of minimized content
+ */
+ protected abstract InputStream doMinimize(StreamableResource resource) throws IOException;
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6e42b37c/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/CSSMinimizer.java
----------------------------------------------------------------------
diff --git a/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/CSSMinimizer.java b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/CSSMinimizer.java
new file mode 100644
index 0000000..a33c6e8
--- /dev/null
+++ b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/CSSMinimizer.java
@@ -0,0 +1,58 @@
+// Copyright 2013 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.webresources;
+
+import com.yahoo.platform.yui.compressor.CssCompressor;
+import org.apache.commons.io.IOUtils;
+import org.apache.tapestry5.ioc.OperationTracker;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
+import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
+import org.apache.tapestry5.services.assets.StreamableResource;
+import org.slf4j.Logger;
+
+import java.io.*;
+
+/**
+ * A wrapper around YUI Compressor. This module does not have a dependency on YUICompressor;
+ * isntead a local copy of the YUICompressor CSS minimizer is kept (because the reset of YUICompressor
+ * is painful to mix due to how it attempts to patch Rhino).
+ */
+public class CSSMinimizer extends AbstractMinimizer
+{
+ public CSSMinimizer(Logger logger, OperationTracker tracker, AssetChecksumGenerator checksumGenerator)
+ {
+ super(logger, tracker, checksumGenerator, "text/css");
+ }
+
+ @Override
+ protected InputStream doMinimize(StreamableResource resource) throws IOException
+ {
+ StringWriter writer = new StringWriter(1000);
+ Reader reader = new InputStreamReader(resource.openStream());
+
+ try
+ {
+ new CssCompressor(reader).compress(writer, -1);
+
+ writer.flush();
+
+ return IOUtils.toInputStream(writer.getBuffer());
+ } finally
+ {
+ InternalUtils.close(reader);
+ InternalUtils.close(writer);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6e42b37c/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/CacheMode.java
----------------------------------------------------------------------
diff --git a/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/CacheMode.java b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/CacheMode.java
new file mode 100644
index 0000000..b2dfee6
--- /dev/null
+++ b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/CacheMode.java
@@ -0,0 +1,40 @@
+// Copyright 2013 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.webresources;
+
+/**
+ * Controls caching for {@link ResourceTransformerFactory} in <em>development mode</em>. In production mode, caching at this
+ * level is not needed, because artifacts are also cached later in the pipeline. This caching is all about avoid unwanted
+ */
+public enum CacheMode
+{
+ /**
+ * Cache the content on the file system, in the directory defined by {@link org.apache.tapestry5.webresources.WebResourcesSymbols#CACHE_DIR}.
+ * This allows compilation to be avoided even after a restart, as long as the source file has not changed. This only works
+ * for compilations that operate on a single file (such as CoffeeScript, but not Less, which has an {@code @import} statement).
+ */
+ SINGLE_FILE,
+
+ /**
+ * The source may be multiple files (e.g., Less). Cache in memory, and invalidate the cache if any of the multiple
+ * file's content changes.
+ */
+ MULTIPLE_FILE,
+
+ /**
+ * Do no caching. This is appropriate for extremely cheap compilers.
+ */
+ NONE;
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6e42b37c/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/CoffeeScriptCompiler.java
----------------------------------------------------------------------
diff --git a/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/CoffeeScriptCompiler.java b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/CoffeeScriptCompiler.java
new file mode 100644
index 0000000..849fc22
--- /dev/null
+++ b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/CoffeeScriptCompiler.java
@@ -0,0 +1,82 @@
+package org.apache.tapestry5.internal.webresources;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.tapestry5.annotations.Path;
+import org.apache.tapestry5.ioc.OperationTracker;
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.services.assets.ResourceDependencies;
+import org.apache.tapestry5.services.assets.ResourceTransformer;
+import org.mozilla.javascript.NativeObject;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.util.List;
+
+public class CoffeeScriptCompiler implements ResourceTransformer
+{
+ private final static Charset UTF8 = Charset.forName("utf-8");
+
+ private final RhinoExecutorPool executorPool;
+
+ public String getTransformedContentType()
+ {
+ return "text/javascript";
+ }
+
+ public CoffeeScriptCompiler(@Path("classpath:org/apache/tapestry5/webresources/internal/coffeescript-1.6.3.js")
+ Resource mainCompiler,
+ @Path("classpath:org/apache/tapestry5/webresources/internal/invoke-coffeescript.js")
+ Resource shim,
+ OperationTracker tracker)
+ {
+
+ executorPool = new RhinoExecutorPool(tracker, toList(mainCompiler, shim));
+ }
+
+ private List<Resource> toList(Resource... resources)
+ {
+ List<Resource> list = CollectionFactory.newList();
+
+ for (Resource r : resources)
+ {
+ list.add(r);
+ }
+
+ return list;
+ }
+
+
+ private String getString(NativeObject object, String key)
+ {
+ return object.get(key).toString();
+ }
+
+
+ public InputStream transform(Resource source, ResourceDependencies dependencies) throws IOException
+ {
+ String content = IOUtils.toString(source.openStream(), UTF8);
+
+ RhinoExecutor executor = executorPool.get();
+
+ try
+ {
+
+ NativeObject result = (NativeObject) executor.invokeFunction("compileCoffeeScriptSource", content, source.toString());
+
+ if (result.containsKey("exception"))
+ {
+ throw new RuntimeException(getString(result, "exception"));
+ }
+
+ return IOUtils.toInputStream(getString(result, "output"), UTF8);
+
+ } finally
+ {
+ executor.discard();
+ }
+
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6e42b37c/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ContentChangeTracker.java
----------------------------------------------------------------------
diff --git a/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ContentChangeTracker.java b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ContentChangeTracker.java
new file mode 100644
index 0000000..cff26f8
--- /dev/null
+++ b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ContentChangeTracker.java
@@ -0,0 +1,61 @@
+// Copyright 2013 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.webresources;
+
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.services.assets.ResourceDependencies;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Manages a collection of Resources and can check to see if any resource's actual content has changed.
+ *
+ * @since 5.4
+ */
+public class ContentChangeTracker implements ResourceDependencies
+{
+ private final Map<Resource, Long> checksums = CollectionFactory.newMap();
+
+ public void addDependency(Resource dependency)
+ {
+ long checksum = ResourceTransformUtils.toChecksum(dependency);
+
+ checksums.put(dependency, checksum);
+ }
+
+ /**
+ * Checks all resources tracked by this instance and returns true if any resource's content has changed.
+ *
+ * @return true if a change has occurred
+ */
+ public boolean dirty() throws IOException
+ {
+ for (Map.Entry<Resource, Long> e : checksums.entrySet())
+ {
+ long current = ResourceTransformUtils.toChecksum(e.getKey());
+
+ if (current != e.getValue().longValue())
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6e42b37c/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/GoogleClosureMinimizer.java
----------------------------------------------------------------------
diff --git a/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/GoogleClosureMinimizer.java b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/GoogleClosureMinimizer.java
new file mode 100644
index 0000000..edd484f
--- /dev/null
+++ b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/GoogleClosureMinimizer.java
@@ -0,0 +1,79 @@
+// Copyright 2013 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.webresources;
+
+import com.google.javascript.jscomp.*;
+import com.google.javascript.jscomp.Compiler;
+import org.apache.commons.io.IOUtils;
+import org.apache.tapestry5.ioc.OperationTracker;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
+import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
+import org.apache.tapestry5.services.assets.StreamableResource;
+import org.slf4j.Logger;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Level;
+
+/**
+ * A wrapper around the Google Closure {@link Compiler} used to minimize
+ * a JavaScript resource.
+ */
+public class GoogleClosureMinimizer extends AbstractMinimizer
+{
+ private final List<SourceFile> EXTERNS = Collections.emptyList();
+
+ static
+ {
+ Compiler.setLoggingLevel(Level.SEVERE);
+ }
+
+ public GoogleClosureMinimizer(Logger logger, OperationTracker tracker, AssetChecksumGenerator checksumGenerator)
+ {
+ super(logger, tracker, checksumGenerator, "text/javascript");
+ }
+
+ @Override
+ protected InputStream doMinimize(StreamableResource resource) throws IOException
+ {
+ // Don't bother to pool the Compiler
+
+ CompilerOptions options = new CompilerOptions();
+ options.setCodingConvention(new ClosureCodingConvention());
+ options.setOutputCharset("utf-8");
+ options.setWarningLevel(DiagnosticGroups.CHECK_VARIABLES, CheckLevel.WARNING);
+
+ Compiler compiler = new Compiler();
+
+ compiler.disableThreads();
+
+ SourceFile input = SourceFile.fromInputStream(resource.toString(), resource.openStream());
+
+ List<SourceFile> inputs = Collections.singletonList(input);
+
+ Result result = compiler.compile(EXTERNS, inputs, options);
+
+ if (result.success)
+ {
+ return IOUtils.toInputStream(compiler.toSource());
+ }
+
+ throw new RuntimeException(String.format("Compilation failed: %s.",
+ InternalUtils.join(CollectionFactory.newList(result.errors), ";")));
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6e42b37c/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/LessResourceTransformer.java
----------------------------------------------------------------------
diff --git a/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/LessResourceTransformer.java b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/LessResourceTransformer.java
new file mode 100644
index 0000000..5bd80af
--- /dev/null
+++ b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/LessResourceTransformer.java
@@ -0,0 +1,120 @@
+// Copyright 2013 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.webresources;
+
+import com.github.sommeri.less4j.Less4jException;
+import com.github.sommeri.less4j.LessCompiler;
+import com.github.sommeri.less4j.LessSource;
+import com.github.sommeri.less4j.core.DefaultLessCompiler;
+import org.apache.commons.io.IOUtils;
+import org.apache.tapestry5.internal.services.assets.BytestreamCache;
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.services.assets.ResourceDependencies;
+import org.apache.tapestry5.services.assets.ResourceTransformer;
+
+import java.io.*;
+
+/**
+ * Direct wrapper around the LessCompiler, so that Less source files may use {@code @import}, which isn't
+ * supported by the normal WRO4J processor.
+ */
+public class LessResourceTransformer implements ResourceTransformer
+{
+ private final LessCompiler compiler = new DefaultLessCompiler();
+
+ public String getTransformedContentType()
+ {
+ return "text/css";
+ }
+
+ class ResourceLessSource extends LessSource
+ {
+ private final Resource resource;
+
+ private final ResourceDependencies dependencies;
+
+
+ ResourceLessSource(Resource resource, ResourceDependencies dependencies)
+ {
+ this.resource = resource;
+ this.dependencies = dependencies;
+ }
+
+ @Override
+ public LessSource relativeSource(String filename) throws FileNotFound, CannotReadFile, StringSourceException
+ {
+ Resource relative = resource.forFile(filename);
+
+ if (!relative.exists())
+ {
+ throw new FileNotFound();
+ }
+
+ dependencies.addDependency(relative);
+
+ return new ResourceLessSource(relative, dependencies);
+ }
+
+ @Override
+ public String getContent() throws FileNotFound, CannotReadFile
+ {
+ // Adapted from Less's URLSource
+ try
+ {
+ Reader input = new InputStreamReader(resource.openStream());
+ String content = IOUtils.toString(input).replace("\r\n", "\n");
+
+ input.close();
+
+ return content;
+ } catch (FileNotFoundException ex)
+ {
+ throw new FileNotFound();
+ } catch (IOException ex)
+ {
+ throw new CannotReadFile();
+ }
+ }
+ }
+
+
+ public InputStream transform(Resource source, ResourceDependencies dependencies) throws IOException
+ {
+ BytestreamCache compiled = invokeLessCompiler(source, dependencies);
+
+ return compiled.openStream();
+ }
+
+ private BytestreamCache invokeLessCompiler(Resource source, ResourceDependencies dependencies) throws IOException
+ {
+ try
+ {
+ LessSource lessSource = new ResourceLessSource(source, dependencies);
+
+ LessCompiler.CompilationResult compilationResult = compiler.compile(lessSource);
+
+ // Currently, ignoring any warnings.
+
+ return new BytestreamCache(compilationResult.getCss().getBytes("utf-8"));
+
+ } catch (Less4jException ex)
+ {
+ throw new IOException(ex);
+ } catch (UnsupportedEncodingException ex)
+ {
+ throw new IOException(ex);
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6e42b37c/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ResourceDependenciesSplitter.java
----------------------------------------------------------------------
diff --git a/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ResourceDependenciesSplitter.java b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ResourceDependenciesSplitter.java
new file mode 100644
index 0000000..6363a7a
--- /dev/null
+++ b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ResourceDependenciesSplitter.java
@@ -0,0 +1,40 @@
+// Copyright 2013 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.webresources;
+
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.services.assets.ResourceDependencies;
+
+/**
+ * A wrapper around two ResourceDependencies.
+ *
+ * @since 5.4
+ */
+public class ResourceDependenciesSplitter implements ResourceDependencies
+{
+ private final ResourceDependencies left, right;
+
+ public ResourceDependenciesSplitter(ResourceDependencies left, ResourceDependencies right)
+ {
+ this.left = left;
+ this.right = right;
+ }
+
+ public void addDependency(Resource dependency)
+ {
+ left.addDependency(dependency);
+ right.addDependency(dependency);
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6e42b37c/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ResourceTransformUtils.java
----------------------------------------------------------------------
diff --git a/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ResourceTransformUtils.java b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ResourceTransformUtils.java
new file mode 100644
index 0000000..aeb9c27
--- /dev/null
+++ b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ResourceTransformUtils.java
@@ -0,0 +1,68 @@
+// Copyright 2013 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.webresources;
+
+import org.apache.tapestry5.ioc.Resource;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.Adler32;
+
+/**
+ * @since 5.4
+ */
+public class ResourceTransformUtils
+{
+ private static final double NANOS_TO_MILLIS = 1.0d / 1000000.0d;
+
+ public static double nanosToMillis(long nanos)
+ {
+ return ((double) nanos) * NANOS_TO_MILLIS;
+ }
+
+ public static long toChecksum(Resource resource)
+ {
+ Adler32 checksum = new Adler32();
+
+ byte[] buffer = new byte[1024];
+
+ InputStream is = null;
+
+ try
+ {
+ is = resource.openStream();
+
+ while (true)
+ {
+ int length = is.read(buffer);
+
+ if (length < 0)
+ {
+ break;
+ }
+
+ checksum.update(buffer, 0, length);
+ }
+
+ is.close();
+
+ // Reduces it down to just 32 bits which we express in hex.'
+ return checksum.getValue();
+ } catch (IOException ex)
+ {
+ throw new RuntimeException(ex);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6e42b37c/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ResourceTransformerFactory.java
----------------------------------------------------------------------
diff --git a/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ResourceTransformerFactory.java b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ResourceTransformerFactory.java
new file mode 100644
index 0000000..c509cce
--- /dev/null
+++ b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ResourceTransformerFactory.java
@@ -0,0 +1,45 @@
+// Copyright 2013 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.webresources;
+
+import org.apache.tapestry5.services.assets.ResourceTransformer;
+
+/**
+ * Creates ResourceTransformer around a named {@link org.apache.tapestry5.webresources.services.ResourceProcessor}.
+ *
+ * @see org.apache.tapestry5.services.assets.StreamableResourceSource
+ * @since 5.4
+ */
+public interface ResourceTransformerFactory
+{
+
+ /**
+ * Constructs a compiler around a another ResourceTransformer implementation. In development mode, the wrapped version
+ * will handle caching, as well as logging output of timing for the real implementation.
+ *
+ * @param sourceName
+ * for debugging: source name, e.g., "Less"
+ * @param targetName
+ * for debugging: target name, e.g., "CSS"
+ * @param transformer
+ * performs the actual work
+ * @param cacheMode
+ * Indicates if and how the compiled content should be cached (in development mode only)
+ * @return transformer
+ * @see org.apache.tapestry5.webresources.services.ResourceProcessorSource
+ */
+ ResourceTransformer createCompiler(String contentType, String sourceName, String targetName, ResourceTransformer transformer, CacheMode cacheMode);
+
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6e42b37c/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ResourceTransformerFactoryImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ResourceTransformerFactoryImpl.java b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ResourceTransformerFactoryImpl.java
new file mode 100644
index 0000000..f02d94d
--- /dev/null
+++ b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ResourceTransformerFactoryImpl.java
@@ -0,0 +1,269 @@
+// Copyright 2013 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.webresources;
+
+import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.internal.TapestryInternalUtils;
+import org.apache.tapestry5.internal.services.assets.BytestreamCache;
+import org.apache.tapestry5.ioc.IOOperation;
+import org.apache.tapestry5.ioc.OperationTracker;
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.ioc.annotations.PostInjection;
+import org.apache.tapestry5.ioc.annotations.Symbol;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.services.assets.ResourceDependencies;
+import org.apache.tapestry5.services.assets.ResourceTransformer;
+import org.apache.tapestry5.webresources.WebResourcesSymbols;
+import org.slf4j.Logger;
+
+import java.io.*;
+import java.util.Map;
+
+public class ResourceTransformerFactoryImpl implements ResourceTransformerFactory
+{
+ private final Logger logger;
+
+ private final OperationTracker tracker;
+
+ private final boolean productionMode;
+
+ private final File cacheDir;
+
+ public ResourceTransformerFactoryImpl(Logger logger, OperationTracker tracker,
+ @Symbol(SymbolConstants.PRODUCTION_MODE)
+ boolean productionMode,
+ @Symbol(WebResourcesSymbols.CACHE_DIR)
+ String cacheDir)
+ {
+ this.logger = logger;
+ this.tracker = tracker;
+ this.productionMode = productionMode;
+
+ this.cacheDir = new File(cacheDir);
+
+ if (!productionMode)
+ {
+ logger.info(String.format("Using %s to store compiled assets (development mode only).", cacheDir));
+ }
+ }
+
+ @PostInjection
+ public void createCacheDir()
+ {
+ cacheDir.mkdirs();
+ }
+
+ static class Compiled extends ContentChangeTracker
+ {
+ private BytestreamCache bytestreamCache;
+
+ Compiled(Resource root)
+ {
+ addDependency(root);
+ }
+
+ void store(InputStream stream) throws IOException
+ {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+ TapestryInternalUtils.copy(stream, bos);
+
+ stream.close();
+ bos.close();
+
+ this.bytestreamCache = new BytestreamCache(bos);
+ }
+
+ InputStream openStream()
+ {
+ return bytestreamCache.openStream();
+ }
+ }
+
+
+ public ResourceTransformer createCompiler(String contentType, String sourceName, String targetName, ResourceTransformer transformer, CacheMode cacheMode)
+ {
+ ResourceTransformer trackingCompiler = wrapWithTracking(sourceName, targetName, transformer);
+
+ if (productionMode)
+ {
+ return trackingCompiler;
+ }
+
+ ResourceTransformer timingCompiler = wrapWithTiming(targetName, trackingCompiler);
+
+ switch (cacheMode)
+ {
+ case NONE:
+
+ return timingCompiler;
+
+ case SINGLE_FILE:
+
+ return wrapWithFileSystemCaching(timingCompiler, targetName);
+
+ case MULTIPLE_FILE:
+
+ return wrapWithInMemoryCaching(timingCompiler, targetName);
+
+ default:
+
+ throw new IllegalStateException();
+ }
+ }
+
+ private ResourceTransformer wrapWithTracking(final String sourceName, final String targetName, final ResourceTransformer core)
+ {
+ return new ResourceTransformer()
+ {
+ public String getTransformedContentType()
+ {
+ return core.getTransformedContentType();
+ }
+
+ public InputStream transform(final Resource source, final ResourceDependencies dependencies) throws IOException
+ {
+ final String description = String.format("Compiling %s from %s to %s", source, sourceName, targetName);
+
+ return tracker.perform(description, new IOOperation<InputStream>()
+ {
+ public InputStream perform() throws IOException
+ {
+ return core.transform(source, dependencies);
+ }
+ });
+ }
+ };
+ }
+
+ private ResourceTransformer wrapWithTiming(final String targetName, final ResourceTransformer coreCompiler)
+ {
+ return new ResourceTransformer()
+ {
+ public String getTransformedContentType()
+ {
+ return coreCompiler.getTransformedContentType();
+ }
+
+ public InputStream transform(final Resource source, final ResourceDependencies dependencies) throws IOException
+ {
+ final long startTime = System.nanoTime();
+
+ InputStream result = coreCompiler.transform(source, dependencies);
+
+ final long elapsedTime = System.nanoTime() - startTime;
+
+ logger.info(String.format("Compiled %s to %s in %.2f ms",
+ source, targetName,
+ ResourceTransformUtils.nanosToMillis(elapsedTime)));
+
+ return result;
+ }
+ };
+ }
+
+ /**
+ * Caching is not needed in production, because caching of streamable resources occurs at a higher level
+ * (possibly after sources have been aggregated and minimized and gzipped). However, in development, it is
+ * very important to avoid costly CoffeeScript compilation (or similar operations); Tapestry's caching is
+ * somewhat primitive: a change to *any* resource in a given domain results in the cache of all of those resources
+ * being discarded.
+ */
+ private ResourceTransformer wrapWithInMemoryCaching(final ResourceTransformer core, final String targetName)
+ {
+ return new ResourceTransformer()
+ {
+ final Map<Resource, Compiled> cache = CollectionFactory.newConcurrentMap();
+
+ public String getTransformedContentType()
+ {
+ return core.getTransformedContentType();
+ }
+
+ public InputStream transform(Resource source, ResourceDependencies dependencies) throws IOException
+ {
+ Compiled compiled = cache.get(source);
+
+ if (compiled != null && !compiled.dirty())
+ {
+ logger.info(String.format("Resource %s and dependencies are unchanged; serving compiled %s content from in-memory cache",
+ source, targetName));
+
+ return compiled.openStream();
+ }
+
+ compiled = new Compiled(source);
+
+ InputStream is = core.transform(source, new ResourceDependenciesSplitter(dependencies, compiled));
+
+ compiled.store(is);
+
+ cache.put(source, compiled);
+
+ return compiled.openStream();
+ }
+ };
+ }
+
+ private ResourceTransformer wrapWithFileSystemCaching(final ResourceTransformer core, final String targetName)
+ {
+ return new ResourceTransformer()
+ {
+ public String getTransformedContentType()
+ {
+ return core.getTransformedContentType();
+ }
+
+ public InputStream transform(Resource source, ResourceDependencies dependencies) throws IOException
+ {
+ long checksum = ResourceTransformUtils.toChecksum(source);
+
+ String fileName = Long.toHexString(checksum) + "-" + source.getFile();
+
+ File cacheFile = new File(cacheDir, fileName);
+
+ if (cacheFile.exists())
+ {
+ logger.debug(String.format("Serving up compiled %s content for %s from file system cache", targetName, source));
+
+ return new BufferedInputStream(new FileInputStream(cacheFile));
+ }
+
+ InputStream compiled = core.transform(source, dependencies);
+
+ // We need the InputStream twice; once to return, and once to write out to the cache file for later.
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+ TapestryInternalUtils.copy(compiled, bos);
+
+ BytestreamCache cache = new BytestreamCache(bos);
+
+ writeToCacheFile(cacheFile, cache.openStream());
+
+ return cache.openStream();
+ }
+ };
+ }
+
+ private void writeToCacheFile(File file, InputStream stream) throws IOException
+ {
+ OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file));
+
+ TapestryInternalUtils.copy(stream, outputStream);
+
+ outputStream.close();
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6e42b37c/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/RhinoExecutor.java
----------------------------------------------------------------------
diff --git a/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/RhinoExecutor.java b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/RhinoExecutor.java
new file mode 100644
index 0000000..7f884fa
--- /dev/null
+++ b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/RhinoExecutor.java
@@ -0,0 +1,24 @@
+package org.apache.tapestry5.internal.webresources;
+
+
+import org.mozilla.javascript.ScriptableObject;
+
+public interface RhinoExecutor
+{
+ /**
+ * Invokes the named function, which must return a scriptable object (typically, a JavaScript Object).
+ *
+ * @param functionName
+ * name of function visible to the executor's scope (e.g., loaded from the scripts associated
+ * with the executor).
+ * @param arguments
+ * Arguments to pass to the object which must be convertable to JavaScript types; Strings work well here.
+ * @return result of invoking the function.
+ */
+ ScriptableObject invokeFunction(String functionName, Object... arguments);
+
+ /**
+ * Discards the executor, returning it to the pool for reuse.
+ */
+ void discard();
+}