KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > directwebremoting > util > JavascriptUtil


1 /*
2  * Copyright 2005 Joe Walker
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.directwebremoting.util;
17
18 import java.io.BufferedReader JavaDoc;
19 import java.io.IOException JavaDoc;
20 import java.io.StringReader JavaDoc;
21 import java.util.Arrays JavaDoc;
22 import java.util.Locale JavaDoc;
23 import java.util.SortedSet JavaDoc;
24 import java.util.TreeSet JavaDoc;
25
26 /**
27  * Various Javascript code utilities.
28  * The escape classes were taken from jakarta-commons-lang which in turn borrowed
29  * from Turbine and other projects. The list of authors below is almost certainly
30  * far too long, but I'm not sure who really wrote these methods.
31  * @author Joe Walker [joe at getahead dot ltd dot uk]
32  * @author Apache Jakarta Turbine
33  * @author GenerationJavaCore library
34  * @author Purple Technology
35  * @author <a HREF="mailto:bayard@generationjava.com">Henri Yandell</a>
36  * @author <a HREF="mailto:alex@purpletech.com">Alexander Day Chaffee</a>
37  * @author <a HREF="mailto:cybertiger@cyberiantiger.org">Antony Riley</a>
38  * @author Helge Tesgaard
39  * @author <a HREF="sean@boohai.com">Sean Brown</a>
40  * @author <a HREF="mailto:ggregory@seagullsw.com">Gary Gregory</a>
41  * @author Phil Steitz
42  * @author Pete Gieser
43  */

44 public class JavascriptUtil
45 {
46     /**
47      * Flag for use in javascript compression: Remove single line comments.
48      * For ease of use you may wish to use one of the LEVEL_* compression levels.
49      * @noinspection PointlessBitwiseExpression
50      */

51     public static final int COMPRESS_STRIP_SL_COMMENTS = 1 << 0;
52
53     /**
54      * Flag for use in javascript compression: Remove multi line comments.
55      * For ease of use you may wish to use one of the LEVEL_* compression levels.
56      */

57     public static final int COMPRESS_STRIP_ML_COMMENTS = 1 << 1;
58
59     /**
60      * Flag for use in javascript compression: Remove whitespace at the start and end of a line.
61      * For ease of use you may wish to use one of the LEVEL_* compression levels.
62      */

63     public static final int COMPRESS_TRIM_LINES = 1 << 2;
64
65     /**
66      * Flag for use in javascript compression: Remove blank lines.
67      * This option will make the javascript harder to debug because line number references
68      * are likely be altered.
69      * For ease of use you may wish to use one of the LEVEL_* compression levels.
70      */

71     public static final int COMPRESS_STRIP_BLANKLINES = 1 << 3;
72
73     /**
74      * Flag for use in javascript compression: Shrink variable names.
75      * This option is currently un-implemented.
76      * For ease of use you may wish to use one of the LEVEL_* compression levels.
77      */

78     public static final int COMPRESS_SHRINK_VARS = 1 << 4;
79
80     /**
81      * Flag for use in javascript compression: Remove all lines endings.
82      * Warning: Javascript can add semi-colons in for you. If you make use of this feature
83      * then removing newlines may well break.
84      * For ease of use you may wish to use one of the LEVEL_* compression levels.
85      */

86     public static final int COMPRESS_REMOVE_NEWLINES = 1 << 5;
87
88     /**
89      * Compression level that leaves the source un-touched.
90      */

91     public static final int LEVEL_NONE = 0;
92
93     /**
94      * Basic compression that leaves the source fully debuggable.
95      * This includes removing all comments and extraneous whitespace.
96      */

97     public static final int LEVEL_DEBUGGABLE = COMPRESS_STRIP_SL_COMMENTS | COMPRESS_STRIP_ML_COMMENTS | COMPRESS_TRIM_LINES;
98
99     /**
100      * Normal compression makes all changes that will work for generic javascript.
101      * This adds variable name compression and blank line removal in addition to the
102      * compressions done by LEVEL_DEBUGGABLE.
103      */

104     public static final int LEVEL_NORMAL = LEVEL_DEBUGGABLE | COMPRESS_STRIP_BLANKLINES | COMPRESS_SHRINK_VARS;
105
106     /**
107      * LEVEL_ULTRA performs additional compression that makes some assumptions about the
108      * style of javascript.
109      * Specifically it assumes that you are not using javascripts ability to infer where the ;
110      * should go.
111      */

112     public static final int LEVEL_ULTRA = LEVEL_NORMAL | COMPRESS_REMOVE_NEWLINES;
113
114     /**
115      * Compress the source code by removing java style comments and removing
116      * leading and trailing spaces.
117      * @param text The javascript (or java) program to compress
118      * @param level The compression level - see LEVEL_* and COMPRESS_* constants.
119      * @return The compressed version
120      */

121     public static String JavaDoc compress(String JavaDoc text, int level)
122     {
123         String JavaDoc reply = text;
124
125         // First we strip multi line comments. I think this is important:
126
if ((level & COMPRESS_STRIP_ML_COMMENTS) != 0)
127         {
128             reply = stripMultiLineComments(text);
129         }
130
131         if ((level & COMPRESS_STRIP_SL_COMMENTS) != 0)
132         {
133             reply = stripSingleLineComments(reply);
134         }
135
136         if ((level & COMPRESS_TRIM_LINES) != 0)
137         {
138             reply = trimLines(reply);
139         }
140
141         if ((level & COMPRESS_STRIP_BLANKLINES) != 0)
142         {
143             reply = stripBlankLines(reply);
144         }
145
146         if ((level & COMPRESS_SHRINK_VARS) != 0)
147         {
148             reply = shrinkVariableNames(reply);
149         }
150
151         if ((level & COMPRESS_REMOVE_NEWLINES) != 0)
152         {
153             reply = stripNewlines(reply);
154         }
155
156         return reply;
157     }
158
159     /**
160      * Remove any leading or trailing spaces from a line of code.
161      * This function could be improved by making it strip unnecessary double
162      * spaces, but since we would need to leave double spaces inside strings
163      * this is not simple and since the benefit is small, we'll leave it for now
164      * @param text The javascript program to strip spaces from.
165      * @return The stripped program
166      */

167     public static String JavaDoc trimLines(String JavaDoc text)
168     {
169         if (text == null)
170         {
171             return null;
172         }
173
174         try
175         {
176             StringBuffer JavaDoc output = new StringBuffer JavaDoc();
177
178             // First we strip multi line comments. I think this is important:
179
BufferedReader JavaDoc in = new BufferedReader JavaDoc(new StringReader JavaDoc(text));
180             while (true)
181             {
182                 String JavaDoc line = in.readLine();
183                 if (line == null)
184                 {
185                     break;
186                 }
187
188                 output.append(line.trim());
189                 output.append('\n');
190             }
191
192             return output.toString();
193         }
194         catch (IOException JavaDoc ex)
195         {
196             log.error("IOExecption unexpected.", ex);
197             throw new IllegalArgumentException JavaDoc("IOExecption unexpected.");
198         }
199     }
200
201     /**
202      * Remove all the single-line comments from a block of text
203      * @param text The text to remove single-line comments from
204      * @return The single-line comment free text
205      */

206     public static String JavaDoc stripSingleLineComments(String JavaDoc text)
207     {
208         if (text == null)
209         {
210             return null;
211         }
212
213         try
214         {
215             StringBuffer JavaDoc output = new StringBuffer JavaDoc();
216
217             BufferedReader JavaDoc in = new BufferedReader JavaDoc(new StringReader JavaDoc(text));
218             while (true)
219             {
220                 String JavaDoc line = in.readLine();
221                 if (line == null)
222                 {
223                     break;
224                 }
225
226                 // Skip @DWR comments
227
if (line.indexOf(COMMENT_RETAIN) == -1)
228                 {
229                     int cstart = line.indexOf(COMMENT_SL_START);
230                     if (cstart >= 0)
231                     {
232                         line = line.substring(0, cstart);
233                     }
234                 }
235
236                 output.append(line);
237                 output.append('\n');
238             }
239
240             return output.toString();
241         }
242         catch (IOException JavaDoc ex)
243         {
244             log.error("IOExecption unexpected.", ex);
245             throw new IllegalArgumentException JavaDoc("IOExecption unexpected.");
246         }
247     }
248
249     /**
250      * Remove all the multi-line comments from a block of text
251      * @param text The text to remove multi-line comments from
252      * @return The multi-line comment free text
253      */

254     public static String JavaDoc stripMultiLineComments(String JavaDoc text)
255     {
256         if (text == null)
257         {
258             return null;
259         }
260
261         try
262         {
263             StringBuffer JavaDoc output = new StringBuffer JavaDoc();
264
265             // Comment rules:
266
/*/ This is still a comment
267             /* /* */
// Comments do not nest
268
// /* */ This is in a comment
269
/* // */ // The second // is needed to make this a comment.
270

271             // First we strip multi line comments. I think this is important:
272
boolean inMultiLine = false;
273             BufferedReader JavaDoc in = new BufferedReader JavaDoc(new StringReader JavaDoc(text));
274             while (true)
275             {
276                 String JavaDoc line = in.readLine();
277                 if (line == null)
278                 {
279                     break;
280                 }
281
282                 if (!inMultiLine)
283                 {
284                     // We are not in a multi-line comment, check for a start
285
int cstart = line.indexOf(COMMENT_ML_START);
286                     if (cstart >= 0)
287                     {
288                         // This could be a MLC on one line ...
289
int cend = line.indexOf(COMMENT_ML_END, cstart + COMMENT_ML_START.length());
290                         if (cend >= 0)
291                         {
292                             // A comment that starts and ends on one line
293
// BUG: you can have more than 1 multi-line comment on a line
294
line = line.substring(0, cstart) + SPACE + line.substring(cend + COMMENT_ML_END.length());
295                         }
296                         else
297                         {
298                             // A real multi-line comment
299
inMultiLine = true;
300                             line = line.substring(0, cstart) + SPACE;
301                         }
302                     }
303                     else
304                     {
305                         // We are not in a multi line comment and we havn't
306
// started one so we are going to ignore closing
307
// comments even if they exist.
308
}
309                 }
310                 else
311                 {
312                     // We are in a multi-line comment, check for the end
313
int cend = line.indexOf(COMMENT_ML_END);
314                     if (cend >= 0)
315                     {
316                         // End of comment
317
line = line.substring(cend + COMMENT_ML_END.length());
318                         inMultiLine = false;
319                     }
320                     else
321                     {
322                         // The comment continues
323
line = SPACE;
324                     }
325                 }
326
327                 output.append(line);
328                 output.append('\n');
329             }
330
331             return output.toString();
332         }
333         catch (IOException JavaDoc ex)
334         {
335             log.error("IOExecption unexpected.", ex);
336             throw new IllegalArgumentException JavaDoc("IOExecption unexpected.");
337         }
338     }
339
340     /**
341      * Remove all blank lines from a string.
342      * A blank line is defined to be a line where the only characters are whitespace.
343      * We always ensure that the line contains a newline at the end.
344      * @param text The string to strip blank lines from
345      * @return The blank line stripped reply
346      */

347     public static String JavaDoc stripBlankLines(String JavaDoc text)
348     {
349         if (text == null)
350         {
351             return null;
352         }
353
354         try
355         {
356             StringBuffer JavaDoc output = new StringBuffer JavaDoc();
357
358             BufferedReader JavaDoc in = new BufferedReader JavaDoc(new StringReader JavaDoc(text));
359             boolean doneOneLine = false;
360             while (true)
361             {
362                 String JavaDoc line = in.readLine();
363                 if (line == null)
364                 {
365                     break;
366                 }
367
368                 if (line.trim().length() > 0)
369                 {
370                     output.append(line);
371                     output.append('\n');
372                     doneOneLine = true;
373                 }
374             }
375
376             if (!doneOneLine)
377             {
378                 output.append('\n');
379             }
380
381             return output.toString();
382         }
383         catch (IOException JavaDoc ex)
384         {
385             log.error("IOExecption unexpected.", ex);
386             throw new IllegalArgumentException JavaDoc("IOExecption unexpected.");
387         }
388     }
389
390     /**
391      * Remove all newline characters from a string.
392      * @param text The string to strip newline characters from
393      * @return The stripped reply
394      */

395     public static String JavaDoc stripNewlines(String JavaDoc text)
396     {
397         if (text == null)
398         {
399             return null;
400         }
401
402         try
403         {
404             StringBuffer JavaDoc output = new StringBuffer JavaDoc();
405
406             BufferedReader JavaDoc in = new BufferedReader JavaDoc(new StringReader JavaDoc(text));
407             while (true)
408             {
409                 String JavaDoc line = in.readLine();
410                 if (line == null)
411                 {
412                     break;
413                 }
414
415                 output.append(line);
416                 output.append(SPACE);
417             }
418             output.append('\n');
419
420             return output.toString();
421         }
422         catch (IOException JavaDoc ex)
423         {
424             log.error("IOExecption unexpected.", ex);
425             throw new IllegalArgumentException JavaDoc("IOExecption unexpected.");
426         }
427     }
428
429     /**
430      * Shrink variable names to a minimum.
431      * @param text The javascript program to shrink the variable names in.
432      * @return The shrunk version of the javascript program.
433      */

434     public static String JavaDoc shrinkVariableNames(String JavaDoc text)
435     {
436         if (text == null)
437         {
438             return null;
439         }
440
441         throw new UnsupportedOperationException JavaDoc("Variable name shrinking is not supported");
442     }
443
444     /**
445      * <p>Escapes the characters in a <code>String</code> using JavaScript String rules.</p>
446      * <p>Escapes any values it finds into their JavaScript String form.
447      * Deals correctly with quotes and control-chars (tab, backslash, cr, ff, etc.) </p>
448      *
449      * <p>So a tab becomes the characters <code>'\\'</code> and
450      * <code>'t'</code>.</p>
451      *
452      * <p>The only difference between Java strings and JavaScript strings
453      * is that in JavaScript, a single quote must be escaped.</p>
454      *
455      * <p>Example:
456      * <pre>
457      * input string: He didn't say, "Stop!"
458      * output string: He didn\'t say, \"Stop!\"
459      * </pre>
460      * </p>
461      *
462      * @param str String to escape values in, may be null
463      * @return String with escaped values, <code>null</code> if null string input
464      */

465     public static String JavaDoc escapeJavaScript(String JavaDoc str)
466     {
467         if (str == null)
468         {
469             return null;
470         }
471
472         StringBuffer JavaDoc writer = new StringBuffer JavaDoc(str.length() * 2);
473
474         int sz = str.length();
475         for (int i = 0; i < sz; i++)
476         {
477             char ch = str.charAt(i);
478
479             // handle unicode
480
if (ch > 0xfff)
481             {
482                 writer.append("\\u");
483                 writer.append(hex(ch));
484             }
485             else if (ch > 0xff)
486             {
487                 writer.append("\\u0");
488                 writer.append(hex(ch));
489             }
490             else if (ch > 0x7f)
491             {
492                 writer.append("\\u00");
493                 writer.append(hex(ch));
494             }
495             else if (ch < 32)
496             {
497                 switch (ch)
498                 {
499                 case '\b':
500                     writer.append('\\');
501                     writer.append('b');
502                     break;
503                 case '\n':
504                     writer.append('\\');
505                     writer.append('n');
506                     break;
507                 case '\t':
508                     writer.append('\\');
509                     writer.append('t');
510                     break;
511                 case '\f':
512                     writer.append('\\');
513                     writer.append('f');
514                     break;
515                 case '\r':
516                     writer.append('\\');
517                     writer.append('r');
518                     break;
519                 default:
520                     if (ch > 0xf)
521                     {
522                         writer.append("\\u00");
523                         writer.append(hex(ch));
524                     }
525                     else
526                     {
527                         writer.append("\\u000");
528                         writer.append(hex(ch));
529                     }
530                     break;
531                 }
532             }
533             else
534             {
535                 switch (ch)
536                 {
537                 case '\'':
538                     // If we wanted to escape for Java strings then we would
539
// not need this next line.
540
writer.append('\\');
541                     writer.append('\'');
542                     break;
543                 case '"':
544                     writer.append('\\');
545                     writer.append('"');
546                     break;
547                 case '\\':
548                     writer.append('\\');
549                     writer.append('\\');
550                     break;
551                 default:
552                     writer.append(ch);
553                     break;
554                 }
555             }
556         }
557
558         return writer.toString();
559     }
560
561     /**
562      * <p>Returns an upper case hexadecimal <code>String</code> for the given
563      * character.</p>
564      * @param ch The character to convert.
565      * @return An upper case hexadecimal <code>String</code>
566      */

567     private static String JavaDoc hex(char ch)
568     {
569         return Integer.toHexString(ch).toUpperCase(Locale.ENGLISH);
570     }
571
572     /**
573      * <p>Unescapes any JavaScript literals found in the <code>String</code>.</p>
574      * <p>For example, it will turn a sequence of <code>'\'</code> and <code>'n'</code>
575      * into a newline character, unless the <code>'\'</code> is preceded by another
576      * <code>'\'</code>.</p>
577      * @param str the <code>String</code> to unescape, may be null
578      * @return A new unescaped <code>String</code>, <code>null</code> if null string input
579      */

580     public static String JavaDoc unescapeJavaScript(String JavaDoc str)
581     {
582         if (str == null)
583         {
584             return null;
585         }
586
587         StringBuffer JavaDoc writer = new StringBuffer JavaDoc(str.length());
588         int sz = str.length();
589         StringBuffer JavaDoc unicode = new StringBuffer JavaDoc(4);
590         boolean hadSlash = false;
591         boolean inUnicode = false;
592
593         for (int i = 0; i < sz; i++)
594         {
595             char ch = str.charAt(i);
596             if (inUnicode)
597             {
598                 // if in unicode, then we're reading unicode
599
// values in somehow
600
unicode.append(ch);
601                 if (unicode.length() == 4)
602                 {
603                     // unicode now contains the four hex digits
604
// which represents our unicode chacater
605
try
606                     {
607                         int value = Integer.parseInt(unicode.toString(), 16);
608                         writer.append((char) value);
609                         unicode.setLength(0);
610                         inUnicode = false;
611                         hadSlash = false;
612                     }
613                     catch (NumberFormatException JavaDoc nfe)
614                     {
615                         throw new IllegalArgumentException JavaDoc("Unable to parse unicode value: " + unicode + " cause: " + nfe);
616                     }
617                 }
618                 continue;
619             }
620
621             if (hadSlash)
622             {
623                 // handle an escaped value
624
hadSlash = false;
625                 switch (ch)
626                 {
627                 case '\\':
628                     writer.append('\\');
629                     break;
630                 case '\'':
631                     writer.append('\'');
632                     break;
633                 case '\"':
634                     writer.append('"');
635                     break;
636                 case 'r':
637                     writer.append('\r');
638                     break;
639                 case 'f':
640                     writer.append('\f');
641                     break;
642                 case 't':
643                     writer.append('\t');
644                     break;
645                 case 'n':
646                     writer.append('\n');
647                     break;
648                 case 'b':
649                     writer.append('\b');
650                     break;
651                 case 'u':
652                     // uh-oh, we're in unicode country....
653
inUnicode = true;
654                     break;
655                 default:
656                     writer.append(ch);
657                     break;
658                 }
659                 continue;
660             }
661             else if (ch == '\\')
662             {
663                 hadSlash = true;
664                 continue;
665             }
666             writer.append(ch);
667         }
668
669         if (hadSlash)
670         {
671             // then we're in the weird case of a \ at the end of the
672
// string, let's output it anyway.
673
writer.append('\\');
674         }
675
676         return writer.toString();
677     }
678
679     /**
680      * Check to see if the given word is reserved or a bad idea in any known
681      * version of JavaScript.
682      * @param name The word to check
683      * @return false if the word is not reserved
684      */

685     public static boolean isReservedWord(String JavaDoc name)
686     {
687         return reserved.contains(name);
688     }
689
690     /**
691      * The array of javascript reserved words
692      */

693     private static final String JavaDoc[] RESERVED_ARRAY = new String JavaDoc[]
694     {
695         // Reserved and used at ECMAScript 4
696
"as",
697         "break",
698         "case",
699         "catch",
700         "class",
701         "const",
702         "continue",
703         "default",
704         "delete",
705         "do",
706         "else",
707         "export",
708         "extends",
709         "false",
710         "finally",
711         "for",
712         "function",
713         "if",
714         "import",
715         "in",
716         "instanceof",
717         "is",
718         "namespace",
719         "new",
720         "null",
721         "package",
722         "private",
723         "public",
724         "return",
725         "super",
726         "switch",
727         "this",
728         "throw",
729         "true",
730         "try",
731         "typeof",
732         "use",
733         "var",
734         "void",
735         "while",
736         "with",
737         // Reserved for future use at ECMAScript 4
738
"abstract",
739         "debugger",
740         "enum",
741         "goto",
742         "implements",
743         "interface",
744         "native",
745         "protected",
746         "synchronized",
747         "throws",
748         "transient",
749         "volatile",
750         // Reserved in ECMAScript 3, unreserved at 4 best to avoid anyway
751
"boolean",
752         "byte",
753         "char",
754         "double",
755         "final",
756         "float",
757         "int",
758         "long",
759         "short",
760         "static",
761
762         // I have seen the folowing list as 'best avoided for function names'
763
// but it seems way to all encompassing, so I'm not going to include it
764
/*
765         "alert", "anchor", "area", "arguments", "array", "assign", "blur",
766         "boolean", "button", "callee", "caller", "captureevents", "checkbox",
767         "clearinterval", "cleartimeout", "close", "closed", "confirm",
768         "constructor", "date", "defaultstatus", "document", "element", "escape",
769         "eval", "fileupload", "find", "focus", "form", "frame", "frames",
770         "getclass", "hidden", "history", "home", "image", "infinity",
771         "innerheight", "isfinite", "innerwidth", "isnan", "java", "javaarray",
772         "javaclass", "javaobject", "javapackage", "length", "link", "location",
773         "locationbar", "math", "menubar", "mimetype", "moveby", "moveto",
774         "name", "nan", "navigate", "navigator", "netscape", "number", "object",
775         "onblur", "onerror", "onfocus", "onload", "onunload", "open", "opener",
776         "option", "outerheight", "outerwidth", "packages", "pagexoffset",
777         "pageyoffset", "parent", "parsefloat", "parseint", "password",
778         "personalbar", "plugin", "print", "prompt", "prototype", "radio", "ref",
779         "regexp", "releaseevents", "reset", "resizeby", "resizeto",
780         "routeevent", "scroll", "scrollbars", "scrollby", "scrollto", "select",
781         "self", "setinterval", "settimeout", "status", "statusbar", "stop",
782         "string", "submit", "sun", "taint", "text", "textarea", "toolbar",
783         "top", "tostring", "unescape", "untaint", "unwatch", "valueof", "watch",
784         "window",
785         */

786     };
787
788     private static SortedSet JavaDoc reserved = new TreeSet JavaDoc();
789
790     /**
791      * For easy access ...
792      */

793     static
794     {
795         // The Javascript reserved words array so we don't generate illegal javascript
796
reserved.addAll(Arrays.asList(RESERVED_ARRAY));
797     }
798
799     private static final String JavaDoc SPACE = " ";
800
801     /**
802      * How does a multi line comment start?
803      */

804     private static final String JavaDoc COMMENT_ML_START = "/*";
805
806     /**
807      * How does a multi line comment end?
808      */

809     private static final String JavaDoc COMMENT_ML_END = "*/";
810
811     /**
812      * How does a single line comment start?
813      */

814     private static final String JavaDoc COMMENT_SL_START = "//";
815
816     /**
817      * Sometimes we need to retain the comment because it has special meaning
818      */

819     private static final String JavaDoc COMMENT_RETAIN = "#DWR";
820
821     /**
822      * The log stream
823      */

824     private static final Logger log = Logger.getLogger(JavascriptUtil.class);
825 }
826
Popular Tags