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 }