001: /*
002: * Copyright 2005 Joe Walker
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.directwebremoting.impl;
017:
018: import java.io.BufferedReader;
019: import java.io.IOException;
020: import java.io.StringReader;
021:
022: import org.apache.commons.logging.Log;
023: import org.apache.commons.logging.LogFactory;
024: import org.directwebremoting.extend.Compressor;
025: import org.directwebremoting.util.LocalUtil;
026:
027: /**
028: * An implementation of {@link Compressor} that does nothing.
029: * @author Joe Walker [joe at getahead dot ltd dot uk]
030: */
031: public class LegacyCompressor implements Compressor {
032: /* (non-Javadoc)
033: * @see org.directwebremoting.extend.Compressor#compressJavaScript(java.lang.String, java.lang.String)
034: */
035: public String compressJavaScript(String script) throws IOException {
036: return compress(script, compressionLevel);
037: }
038:
039: /**
040: * Compress the source code by removing java style comments and removing
041: * leading and trailing spaces.
042: * @param text The javascript (or java) program to compress
043: * @param level The compression level - see LEVEL_* and COMPRESS_* constants.
044: * @return The compressed version
045: */
046: public static String compress(String text, int level) {
047: String reply = text;
048:
049: // First we strip multi line comments. I think this is important:
050: if ((level & COMPRESS_STRIP_ML_COMMENTS) != 0) {
051: reply = stripMultiLineComments(text);
052: }
053:
054: if ((level & COMPRESS_STRIP_SL_COMMENTS) != 0) {
055: reply = stripSingleLineComments(reply);
056: }
057:
058: if ((level & COMPRESS_TRIM_LINES) != 0) {
059: reply = trimLines(reply);
060: }
061:
062: if ((level & COMPRESS_STRIP_BLANKLINES) != 0) {
063: reply = stripBlankLines(reply);
064: }
065:
066: if ((level & COMPRESS_SHRINK_VARS) != 0) {
067: reply = shrinkVariableNames(reply);
068: }
069:
070: if ((level & COMPRESS_REMOVE_NEWLINES) != 0) {
071: reply = stripNewlines(reply);
072: }
073:
074: return reply;
075: }
076:
077: /**
078: * Remove any leading or trailing spaces from a line of code.
079: * This function could be improved by making it strip unnecessary double
080: * spaces, but since we would need to leave double spaces inside strings
081: * this is not simple and since the benefit is small, we'll leave it for now
082: * @param text The javascript program to strip spaces from.
083: * @return The stripped program
084: */
085: public static String trimLines(String text) {
086: if (text == null) {
087: return null;
088: }
089:
090: BufferedReader in = null;
091: try {
092: StringBuffer output = new StringBuffer();
093:
094: // First we strip multi line comments. I think this is important:
095: in = new BufferedReader(new StringReader(text));
096: while (true) {
097: String line = in.readLine();
098: if (line == null) {
099: break;
100: }
101:
102: output.append(line.trim());
103: output.append('\n');
104: }
105:
106: return output.toString();
107: } catch (IOException ex) {
108: log.error("IOExecption unexpected.", ex);
109: throw new IllegalArgumentException(
110: "IOExecption unexpected.");
111: } finally {
112: LocalUtil.close(in);
113: }
114: }
115:
116: /**
117: * Remove all the single-line comments from a block of text
118: * @param text The text to remove single-line comments from
119: * @return The single-line comment free text
120: */
121: public static String stripSingleLineComments(String text) {
122: if (text == null) {
123: return null;
124: }
125:
126: BufferedReader in = null;
127: try {
128: StringBuffer output = new StringBuffer();
129:
130: in = new BufferedReader(new StringReader(text));
131: while (true) {
132: String line = in.readLine();
133: if (line == null) {
134: break;
135: }
136:
137: // Skip @DWR comments
138: if (!line.contains(COMMENT_RETAIN)) {
139: int cstart = line.indexOf(COMMENT_SL_START);
140: if (cstart >= 0) {
141: line = line.substring(0, cstart);
142: }
143: }
144:
145: output.append(line);
146: output.append('\n');
147: }
148:
149: return output.toString();
150: } catch (IOException ex) {
151: log.error("IOExecption unexpected.", ex);
152: throw new IllegalArgumentException(
153: "IOExecption unexpected.");
154: } finally {
155: LocalUtil.close(in);
156: }
157: }
158:
159: /**
160: * Remove all the multi-line comments from a block of text
161: * @param text The text to remove multi-line comments from
162: * @return The multi-line comment free text
163: */
164: public static String stripMultiLineComments(String text) {
165: if (text == null) {
166: return null;
167: }
168:
169: BufferedReader in = null;
170: try {
171: StringBuffer output = new StringBuffer();
172:
173: // Comment rules:
174: /*/ This is still a comment
175: /* /* */// Comments do not nest
176: // /* */ This is in a comment
177: /* // */// The second // is needed to make this a comment.
178: // First we strip multi line comments. I think this is important:
179: boolean inMultiLine = false;
180: in = new BufferedReader(new StringReader(text));
181: while (true) {
182: String line = in.readLine();
183: if (line == null) {
184: break;
185: }
186:
187: if (!inMultiLine) {
188: // We are not in a multi-line comment, check for a start
189: int cstart = line.indexOf(COMMENT_ML_START);
190: if (cstart >= 0) {
191: // This could be a MLC on one line ...
192: int cend = line.indexOf(COMMENT_ML_END, cstart
193: + COMMENT_ML_START.length());
194: if (cend >= 0) {
195: // A comment that starts and ends on one line
196: // BUG: you can have more than 1 multi-line comment on a line
197: line = line.substring(0, cstart)
198: + " "
199: + line.substring(cend
200: + COMMENT_ML_END.length());
201: } else {
202: // A real multi-line comment
203: inMultiLine = true;
204: line = line.substring(0, cstart) + " ";
205: }
206: } else {
207: // We are not in a multi line comment and we havn't
208: // started one so we are going to ignore closing
209: // comments even if they exist.
210: }
211: } else {
212: // We are in a multi-line comment, check for the end
213: int cend = line.indexOf(COMMENT_ML_END);
214: if (cend >= 0) {
215: // End of comment
216: line = line.substring(cend
217: + COMMENT_ML_END.length());
218: inMultiLine = false;
219: } else {
220: // The comment continues
221: line = " ";
222: }
223: }
224:
225: output.append(line);
226: output.append('\n');
227: }
228:
229: return output.toString();
230: } catch (IOException ex) {
231: log.error("IOExecption unexpected.", ex);
232: throw new IllegalArgumentException(
233: "IOExecption unexpected.");
234: } finally {
235: LocalUtil.close(in);
236: }
237: }
238:
239: /**
240: * Remove all blank lines from a string.
241: * A blank line is defined to be a line where the only characters are whitespace.
242: * We always ensure that the line contains a newline at the end.
243: * @param text The string to strip blank lines from
244: * @return The blank line stripped reply
245: */
246: public static String stripBlankLines(String text) {
247: if (text == null) {
248: return null;
249: }
250:
251: BufferedReader in = null;
252: try {
253: StringBuffer output = new StringBuffer();
254:
255: in = new BufferedReader(new StringReader(text));
256: boolean doneOneLine = false;
257: while (true) {
258: String line = in.readLine();
259: if (line == null) {
260: break;
261: }
262:
263: if (line.trim().length() > 0) {
264: output.append(line);
265: output.append('\n');
266: doneOneLine = true;
267: }
268: }
269:
270: if (!doneOneLine) {
271: output.append('\n');
272: }
273:
274: return output.toString();
275: } catch (IOException ex) {
276: log.error("IOExecption unexpected.", ex);
277: throw new IllegalArgumentException(
278: "IOExecption unexpected.");
279: } finally {
280: LocalUtil.close(in);
281: }
282: }
283:
284: /**
285: * Remove all newline characters from a string.
286: * @param text The string to strip newline characters from
287: * @return The stripped reply
288: */
289: public static String stripNewlines(String text) {
290: if (text == null) {
291: return null;
292: }
293:
294: BufferedReader in = null;
295: try {
296: StringBuffer output = new StringBuffer();
297:
298: in = new BufferedReader(new StringReader(text));
299: while (true) {
300: String line = in.readLine();
301: if (line == null) {
302: break;
303: }
304:
305: output.append(line);
306: output.append(" ");
307: }
308: output.append('\n');
309:
310: return output.toString();
311: } catch (IOException ex) {
312: log.error("IOExecption unexpected.", ex);
313: throw new IllegalArgumentException(
314: "IOExecption unexpected.");
315: } finally {
316: LocalUtil.close(in);
317: }
318: }
319:
320: /**
321: * Shrink variable names to a minimum.
322: * @param text The javascript program to shrink the variable names in.
323: * @return The shrunk version of the javascript program.
324: */
325: public static String shrinkVariableNames(String text) {
326: if (text == null) {
327: return null;
328: }
329:
330: throw new UnsupportedOperationException(
331: "Variable name shrinking is not supported");
332: }
333:
334: /**
335: * @param compressionLevel The compressionLevel to set.
336: */
337: public void setCompressionLevel(int compressionLevel) {
338: this .compressionLevel = compressionLevel;
339: }
340:
341: /**
342: * How much do we compression javascript by?
343: */
344: protected int compressionLevel = LEVEL_DEBUGGABLE;
345:
346: /**
347: * Flag for use in javascript compression: Remove single line comments.
348: * For ease of use you may wish to use one of the LEVEL_* compression levels.
349: * @noinspection PointlessBitwiseExpression
350: */
351: public static final int COMPRESS_STRIP_SL_COMMENTS = 1 << 0;
352:
353: /**
354: * Flag for use in javascript compression: Remove multi line comments.
355: * For ease of use you may wish to use one of the LEVEL_* compression levels.
356: */
357: public static final int COMPRESS_STRIP_ML_COMMENTS = 1 << 1;
358:
359: /**
360: * Flag for use in javascript compression: Remove whitespace at the start and end of a line.
361: * For ease of use you may wish to use one of the LEVEL_* compression levels.
362: */
363: public static final int COMPRESS_TRIM_LINES = 1 << 2;
364:
365: /**
366: * Flag for use in javascript compression: Remove blank lines.
367: * This option will make the javascript harder to debug because line number references
368: * are likely be altered.
369: * For ease of use you may wish to use one of the LEVEL_* compression levels.
370: */
371: public static final int COMPRESS_STRIP_BLANKLINES = 1 << 3;
372:
373: /**
374: * Flag for use in javascript compression: Shrink variable names.
375: * This option is currently un-implemented.
376: * For ease of use you may wish to use one of the LEVEL_* compression levels.
377: */
378: public static final int COMPRESS_SHRINK_VARS = 1 << 4;
379:
380: /**
381: * Flag for use in javascript compression: Remove all lines endings.
382: * Warning: Javascript can add semi-colons in for you. If you make use of this feature
383: * then removing newlines may well break.
384: * For ease of use you may wish to use one of the LEVEL_* compression levels.
385: */
386: public static final int COMPRESS_REMOVE_NEWLINES = 1 << 5;
387:
388: /**
389: * Compression level that leaves the source un-touched.
390: */
391: public static final int LEVEL_NONE = 0;
392:
393: /**
394: * Basic compression that leaves the source fully debuggable.
395: * This includes removing all comments and extraneous whitespace.
396: */
397: public static final int LEVEL_DEBUGGABLE = COMPRESS_STRIP_SL_COMMENTS
398: | COMPRESS_STRIP_ML_COMMENTS | COMPRESS_TRIM_LINES;
399:
400: /**
401: * Normal compression makes all changes that will work for generic javascript.
402: * This adds variable name compression and blank line removal in addition to the
403: * compressions done by LEVEL_DEBUGGABLE.
404: */
405: public static final int LEVEL_NORMAL = LEVEL_DEBUGGABLE
406: | COMPRESS_STRIP_BLANKLINES | COMPRESS_SHRINK_VARS;
407:
408: /**
409: * LEVEL_ULTRA performs additional compression that makes some assumptions about the
410: * style of javascript.
411: * Specifically it assumes that you are not using JavaScript's ability to infer where the ;
412: * should go.
413: */
414: public static final int LEVEL_ULTRA = LEVEL_NORMAL
415: | COMPRESS_REMOVE_NEWLINES;
416:
417: /**
418: * How does a multi line comment start?
419: */
420: private static final String COMMENT_ML_START = "/*";
421:
422: /**
423: * How does a multi line comment end?
424: */
425: private static final String COMMENT_ML_END = "*/";
426:
427: /**
428: * How does a single line comment start?
429: */
430: private static final String COMMENT_SL_START = "//";
431:
432: /**
433: * Sometimes we need to retain the comment because it has special meaning
434: */
435: private static final String COMMENT_RETAIN = "#DWR";
436:
437: /**
438: * The log stream
439: */
440: private static final Log log = LogFactory
441: .getLog(LegacyCompressor.class);
442: }
|