001 // Copyright 2011 The Apache Software Foundation 002 // 003 // Licensed under the Apache License, Version 2.0 (the "License"); 004 // you may not use this file except in compliance with the License. 005 // You may obtain a copy of the License at 006 // 007 // http://www.apache.org/licenses/LICENSE-2.0 008 // 009 // Unless required by applicable law or agreed to in writing, software 010 // distributed under the License is distributed on an "AS IS" BASIS, 011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 // See the License for the specific language governing permissions and 013 // limitations under the License. 014 015 package org.apache.tapestry5.internal.yuicompressor; 016 017 import com.yahoo.platform.yui.compressor.JavaScriptCompressor; 018 import org.apache.tapestry5.ioc.OperationTracker; 019 import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 020 import org.apache.tapestry5.ioc.internal.util.InternalUtils; 021 import org.apache.tapestry5.services.assets.StreamableResource; 022 import org.mozilla.javascript.ErrorReporter; 023 import org.mozilla.javascript.EvaluatorException; 024 import org.slf4j.Logger; 025 026 import java.io.IOException; 027 import java.io.LineNumberReader; 028 import java.io.Reader; 029 import java.io.Writer; 030 import java.util.Set; 031 032 /** 033 * JavaScript resource minimizer based on the YUI {@link JavaScriptCompressor}. 034 * 035 * @since 5.3 036 */ 037 public class JavaScriptResourceMinimizer extends AbstractMinimizer 038 { 039 private final Logger logger; 040 041 private final static int RANGE = 5; 042 043 private enum Where 044 { 045 EXACT, NEAR, FAR 046 } 047 048 public JavaScriptResourceMinimizer(final Logger logger, OperationTracker tracker) 049 { 050 super(logger, tracker, "JavaScript"); 051 052 this.logger = logger; 053 } 054 055 protected void doMinimize(StreamableResource resource, Writer output) throws IOException 056 { 057 final Set<Integer> errorLines = CollectionFactory.newSet(); 058 059 ErrorReporter errorReporter = new ErrorReporter() 060 { 061 private String format(String message, int line, int lineOffset) 062 { 063 if (line < 0) 064 return message; 065 066 return String.format("(%d:%d): %s", line, lineOffset, message); 067 } 068 069 public void warning(String message, String sourceName, int line, String lineSource, int lineOffset) 070 { 071 errorLines.add(line); 072 073 logger.warn(format(message, line, lineOffset)); 074 } 075 076 public EvaluatorException runtimeError(String message, String sourceName, int line, String lineSource, 077 int lineOffset) 078 { 079 error(message, sourceName, line, lineSource, lineOffset); 080 081 return new EvaluatorException(message); 082 } 083 084 public void error(String message, String sourceName, int line, String lineSource, int lineOffset) 085 { 086 errorLines.add(line); 087 088 logger.error(format(message, line, lineOffset)); 089 } 090 091 }; 092 093 094 Reader reader = toReader(resource); 095 096 try 097 { 098 JavaScriptCompressor compressor = new JavaScriptCompressor(reader, errorReporter); 099 compressor.compress(output, -1, true, false, false, false); 100 } catch (EvaluatorException ex) 101 { 102 logInputLines(resource, errorLines); 103 104 recoverFromException(ex, resource, output); 105 106 } catch (Exception ex) 107 { 108 recoverFromException(ex, resource, output); 109 } 110 111 reader.close(); 112 } 113 114 private void recoverFromException(Exception ex, StreamableResource resource, Writer output) throws IOException 115 { 116 logger.error(String.format("Exception minimizing %s: %s", resource.getDescription(), InternalUtils.toMessage(ex)), ex); 117 118 streamUnminimized(resource, output); 119 } 120 121 private void streamUnminimized(StreamableResource resource, Writer output) throws IOException 122 { 123 Reader reader = toReader(resource); 124 125 char[] buffer = new char[5000]; 126 127 try 128 { 129 130 while (true) 131 { 132 int length = reader.read(buffer); 133 134 if (length < 0) 135 { 136 break; 137 } 138 139 output.write(buffer, 0, length); 140 } 141 } finally 142 { 143 reader.close(); 144 } 145 } 146 147 private void logInputLines(StreamableResource resource, Set<Integer> lines) 148 { 149 logger.error(String.format("Errors in resource %s:", resource.getDescription())); 150 151 int last = -1; 152 153 try 154 { 155 LineNumberReader lnr = new LineNumberReader(toReader(resource)); 156 157 while (true) 158 { 159 String line = lnr.readLine(); 160 161 if (line == null) break; 162 163 int lineNumber = lnr.getLineNumber(); 164 165 Where where = where(lineNumber, lines); 166 167 if (where == Where.FAR) 168 { 169 continue; 170 } 171 172 // Add a blank line to separate non-consecutive parts of the content. 173 if (last > 0 && last + 1 != lineNumber) 174 { 175 logger.error(""); 176 } 177 178 String formatted = String.format("%s%6d %s", 179 where == Where.EXACT ? "*" : " ", 180 lineNumber, 181 line); 182 183 logger.error(formatted); 184 185 last = lineNumber; 186 } 187 188 lnr.close(); 189 190 } catch (IOException ex) 191 { // Ignore. 192 } 193 194 } 195 196 private Where where(int lineNumber, Set<Integer> lines) 197 { 198 if (lines.contains(lineNumber)) 199 { 200 return Where.EXACT; 201 } 202 203 for (int line : lines) 204 { 205 if (Math.abs(lineNumber - line) < RANGE) 206 { 207 return Where.NEAR; 208 } 209 } 210 211 return Where.FAR; 212 } 213 }