1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package org.dojotoolkit.shrinksafe;
28
29 import java.io.IOException;
30 import java.io.Reader;
31 import java.util.HashMap;
32 import java.util.Iterator;
33 import java.util.Map;
34 import java.util.Stack;
35 import java.util.regex.Matcher;
36 import java.util.regex.Pattern;
37
38 import org.mozilla.javascript.CompilerEnvirons;
39 import org.mozilla.javascript.Decompiler;
40 import org.mozilla.javascript.FunctionNode;
41 import org.mozilla.javascript.Interpreter;
42 import org.mozilla.javascript.Kit;
43 import org.mozilla.javascript.Parser;
44 import org.mozilla.javascript.ScriptOrFnNode;
45 import org.mozilla.javascript.ScriptRuntime;
46 import org.mozilla.javascript.Token;
47 import org.mozilla.javascript.UintMap;
48
49
50
51
52
53 public class Compressor {
54 private static final int FUNCTION_END = Token.LAST_TOKEN + 1;
55
56
57
58
59
60
61
62
63
64
65
66 private static String compress(String encodedSource,
67 int flags,
68 UintMap properties,
69 ScriptOrFnNode parseTree,
70 boolean escapeUnicode,
71 String stripConsole,
72 TokenMapper tm,
73 Map replacedTokensLookup){
74 int indent = properties.getInt(Decompiler.INITIAL_INDENT_PROP, 0);
75 if (indent < 0) throw new IllegalArgumentException();
76 int indentGap = properties.getInt(Decompiler.INDENT_GAP_PROP, 4);
77 if (indentGap < 0) throw new IllegalArgumentException();
78 int caseGap = properties.getInt(Decompiler.CASE_GAP_PROP, 2);
79 if (caseGap < 0) throw new IllegalArgumentException();
80
81 String stripConsoleRegex = "assert|count|debug|dir|dirxml|group|groupEnd|info|profile|profileEnd|time|timeEnd|trace|log";
82 if (stripConsole == null) {
83
84 stripConsoleRegex = null;
85 } else if (stripConsole.equals("normal")) {
86
87 } else if (stripConsole.equals("warn")) {
88 stripConsoleRegex += "|warn";
89 } else if (stripConsole.equals("all")) {
90 stripConsoleRegex += "|warn|error";
91 } else {
92 throw new IllegalArgumentException("unrecognised value for stripConsole: " + stripConsole + "!");
93 }
94
95 Pattern stripConsolePattern = null;
96 if (stripConsoleRegex != null) {
97 stripConsolePattern = Pattern.compile(stripConsoleRegex);
98 }
99
100 StringBuffer result = new StringBuffer();
101 boolean justFunctionBody = (0 != (flags & Decompiler.ONLY_BODY_FLAG));
102 boolean toSource = (0 != (flags & Decompiler.TO_SOURCE_FLAG));
103 int braceNesting = 0;
104 boolean afterFirstEOL = false;
105 int i = 0;
106 int prevToken = 0;
107 boolean primeFunctionNesting = false;
108 boolean inArgsList = false;
109 boolean primeInArgsList = false;
110
111 boolean discardingConsole = false;
112 int consoleParenCount = 0;
113 StringBuffer discardMe = new StringBuffer();
114 ReplacedTokens dummyTokens = new ReplacedTokens(new HashMap(), new int[]{}, replacedTokensLookup, null);
115 int lastMeaningfulToken = Token.SEMI;
116 int lastMeaningfulTokenBeforeConsole = Token.SEMI;
117
118 int topFunctionType;
119 if (encodedSource.charAt(i) == Token.SCRIPT) {
120 ++i;
121 topFunctionType = -1;
122 } else {
123 topFunctionType = encodedSource.charAt(i + 1);
124 }
125 if (!toSource) {
126
127
128 for (int j = 0; j < indent; j++){
129
130 result.append("");
131 }
132 } else {
133 if (topFunctionType == FunctionNode.FUNCTION_EXPRESSION) {
134 result.append('(');
135 }
136 }
137
138 Stack positionStack = new Stack();
139 Stack functionPositionStack = new Stack();
140
141 int length = encodedSource.length();
142 int lineCount = 1;
143
144 while (i < length) {
145 if(i>0){
146 prevToken = encodedSource.charAt(i-1);
147 }
148 if (discardingConsole) {
149
150 int thisToken = encodedSource.charAt(i);
151
152 switch (thisToken) {
153 case Token.LP:
154 consoleParenCount++;
155 break;
156 case Token.RP:
157 consoleParenCount--;
158 if (consoleParenCount == 0) {
159
160 discardingConsole = false;
161
162 if (i < (length - 1)) {
163 int nextToken = getNext(encodedSource, length, i);
164
165 if ((lastMeaningfulTokenBeforeConsole != Token.SEMI &&
166 lastMeaningfulTokenBeforeConsole != Token.LC &&
167 lastMeaningfulTokenBeforeConsole != Token.RC) ||
168 nextToken != Token.SEMI) {
169
170
171
172 result.append("undefined");
173 } else {
174 if (Token.SEMI == nextToken) {
175
176 i++;
177 }
178 }
179 }
180 if ((i < (length - 1))
181 && (Token.EOL == getNext(encodedSource, length, i))) {
182
183 i++;
184 }
185 }
186 break;
187 }
188
189
190
191
192
193
194
195 switch (thisToken) {
196 case Token.NAME:
197 case Token.REGEXP:
198 int jumpPos = getSourceStringEnd(encodedSource, i + 1,
199 escapeUnicode);
200 if (Token.OBJECTLIT == encodedSource.charAt(jumpPos)) {
201 i = printSourceString(encodedSource, i + 1, false,
202 discardMe, escapeUnicode);
203 } else {
204 i = tm.sourceCompress(encodedSource, i + 1, false,
205 discardMe, prevToken, inArgsList, braceNesting,
206 dummyTokens);
207 }
208 break;
209 case Token.STRING:
210 i = printSourceString(encodedSource, i + 1, true,
211 discardMe, escapeUnicode);
212 break;
213 case Token.NUMBER:
214 i = printSourceNumber(encodedSource, i + 1, discardMe);
215 break;
216 default:
217
218 i++;
219 }
220
221 continue;
222 }
223
224
225 int thisToken = encodedSource.charAt(i);
226
227 switch(thisToken) {
228 case Token.NAME:
229 case Token.REGEXP:
230 int jumpPos = getSourceStringEnd(encodedSource, i+1, escapeUnicode);
231 if (stripConsolePattern != null && thisToken == Token.NAME) {
232
233
234 int nextTokenAt = tm.sourceCompress(encodedSource, i + 1, false, discardMe, prevToken,
235 inArgsList, braceNesting, dummyTokens);
236 if (encodedSource.substring(i+2, i+2+encodedSource.charAt(i+1)).equals("console") &&
237 (encodedSource.charAt(nextTokenAt) == Token.DOT)) {
238
239 int afterFnName = printSourceString(encodedSource, nextTokenAt+2, false, discardMe, escapeUnicode);
240 Matcher m = stripConsolePattern.matcher(encodedSource.substring(nextTokenAt + 3, afterFnName));
241 if (m.matches()) {
242
243 if (encodedSource.charAt(afterFnName) == Token.LP) {
244 discardingConsole = true;
245 consoleParenCount = 0;
246 lastMeaningfulTokenBeforeConsole = lastMeaningfulToken;
247 continue;
248 }
249 }
250 }
251 }
252 if(Token.OBJECTLIT == encodedSource.charAt(jumpPos)){
253 i = printSourceString(encodedSource, i + 1, false, result, escapeUnicode);
254 }else{
255 ReplacedTokens replacedTokens = null;
256 if (positionStack.size() > 0) {
257 Integer pos = (Integer)positionStack.peek();
258 replacedTokens = (ReplacedTokens)replacedTokensLookup.get(pos);
259 }
260 else {
261 replacedTokens = new ReplacedTokens(new HashMap(), new int[]{}, replacedTokensLookup, null);
262 }
263
264 i = tm.sourceCompress( encodedSource, i + 1, false, result, prevToken,
265 inArgsList, braceNesting, replacedTokens);
266 }
267 continue;
268 case Token.STRING:
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286 i = printSourceString(encodedSource, i + 1, true, result, escapeUnicode);
287
288 continue;
289 case Token.NUMBER:
290 i = printSourceNumber(encodedSource, i + 1, result);
291 continue;
292 case Token.TRUE:
293 result.append("true");
294 break;
295 case Token.FALSE:
296 result.append("false");
297 break;
298 case Token.NULL:
299 result.append("null");
300 break;
301 case Token.THIS:
302 result.append("this");
303 break;
304 case Token.FUNCTION: {
305 ++i;
306 tm.incrementFunctionNumber();
307 primeInArgsList = true;
308 primeFunctionNesting = true;
309 result.append("function");
310 if (Token.LP != getNext(encodedSource, length, i)) {
311 result.append(' ');
312 }
313 Integer functionPos = new Integer(i-1);
314 functionPositionStack.push(functionPos);
315 DebugData debugData = tm.getDebugData(functionPos);
316 debugData.compressedStart = lineCount;
317 break;
318 }
319 case FUNCTION_END: {
320 Integer functionPos = (Integer)functionPositionStack.pop();
321 DebugData debugData = tm.getDebugData(functionPos);
322 debugData.compressedEnd = lineCount;
323 break;
324 }
325 case Token.COMMA:
326 result.append(",");
327 break;
328 case Token.LC:
329 ++braceNesting;
330 if (Token.EOL == getNext(encodedSource, length, i)){
331 indent += indentGap;
332 }
333 result.append('{');
334
335 break;
336 case Token.RC: {
337 if (tm.leaveNestingLevel(braceNesting)) {
338 positionStack.pop();
339 }
340 --braceNesting;
341
342
343
344
345 if(justFunctionBody && braceNesting == 0){
346 break;
347 }
348
349 result.append('}');
350
351 switch (getNext(encodedSource, length, i)) {
352 case Token.EOL:
353 case FUNCTION_END:
354 if (
355 (getNext(encodedSource, length, i+1) != Token.SEMI) &&
356 (getNext(encodedSource, length, i+1) != Token.LP) &&
357 (getNext(encodedSource, length, i+1) != Token.RP) &&
358 (getNext(encodedSource, length, i+1) != Token.RB) &&
359 (getNext(encodedSource, length, i+1) != Token.RC) &&
360 (getNext(encodedSource, length, i+1) != Token.COMMA) &&
361 (getNext(encodedSource, length, i+1) != Token.COLON) &&
362 (getNext(encodedSource, length, i+1) != Token.DOT) &&
363 (getNext(encodedSource, length, i) == FUNCTION_END )
364 ){
365 result.append(';');
366 }
367 indent -= indentGap;
368 break;
369 case Token.WHILE:
370 case Token.ELSE:
371 indent -= indentGap;
372
373 result.append("");
374 break;
375 }
376 break;
377 }
378 case Token.LP:
379 if(primeInArgsList){
380 inArgsList = true;
381 primeInArgsList = false;
382 }
383 if(primeFunctionNesting){
384 positionStack.push(new Integer(i));
385 tm.enterNestingLevel(braceNesting);
386 primeFunctionNesting = false;
387 }
388 result.append('(');
389 break;
390 case Token.RP:
391 if(inArgsList){
392 inArgsList = false;
393 }
394 result.append(')');
395
396
397
398
399
400 break;
401 case Token.LB:
402 result.append('[');
403 break;
404 case Token.RB:
405 result.append(']');
406 break;
407 case Token.EOL: {
408 if (toSource) break;
409 boolean newLine = true;
410 if (!afterFirstEOL) {
411 afterFirstEOL = true;
412 if (justFunctionBody) {
413
414
415
416 result.setLength(0);
417 indent -= indentGap;
418 newLine = false;
419 }
420 }
421 if (newLine) {
422 result.append('\n');
423 lineCount++;
424 }
425
426
427
428
429 if (i + 1 < length) {
430 int less = 0;
431 int nextToken = encodedSource.charAt(i + 1);
432 if (nextToken == Token.CASE
433 || nextToken == Token.DEFAULT)
434 {
435 less = indentGap - caseGap;
436 } else if (nextToken == Token.RC) {
437 less = indentGap;
438 }
439
440
441
442 else if (nextToken == Token.NAME) {
443 int afterName = getSourceStringEnd(encodedSource, i + 2, escapeUnicode);
444 if (encodedSource.charAt(afterName) == Token.COLON)
445 less = indentGap;
446 }
447 for (; less < indent; less++){
448
449 result.append("");
450 }
451 }
452 break;
453 }
454 case Token.DOT:
455 result.append('.');
456 break;
457 case Token.NEW:
458 result.append("new ");
459 break;
460 case Token.DELPROP:
461 result.append("delete ");
462 break;
463 case Token.IF:
464 result.append("if");
465 break;
466 case Token.ELSE:
467 result.append("else");
468 break;
469 case Token.FOR:
470 result.append("for");
471 break;
472 case Token.IN:
473 result.append(" in ");
474 break;
475 case Token.WITH:
476 result.append("with");
477 break;
478 case Token.WHILE:
479 result.append("while");
480 break;
481 case Token.DO:
482 result.append("do");
483 break;
484 case Token.TRY:
485 result.append("try");
486 break;
487 case Token.CATCH:
488 result.append("catch");
489 break;
490 case Token.FINALLY:
491 result.append("finally");
492 break;
493 case Token.THROW:
494 result.append("throw ");
495 break;
496 case Token.SWITCH:
497 result.append("switch");
498 break;
499 case Token.BREAK:
500 result.append("break");
501 if(Token.NAME == getNext(encodedSource, length, i)){
502 result.append(' ');
503 }
504 break;
505 case Token.CONTINUE:
506 result.append("continue");
507 if(Token.NAME == getNext(encodedSource, length, i)){
508 result.append(' ');
509 }
510 break;
511 case Token.CASE:
512 result.append("case ");
513 break;
514 case Token.DEFAULT:
515 result.append("default");
516 break;
517 case Token.RETURN:
518 result.append("return");
519 if(Token.SEMI != getNext(encodedSource, length, i)){
520 result.append(' ');
521 }
522 break;
523 case Token.VAR:
524 result.append("var ");
525 break;
526 case Token.SEMI:
527 result.append(';');
528
529
530
531
532
533
534
535 break;
536 case Token.ASSIGN:
537 result.append("=");
538 break;
539 case Token.ASSIGN_ADD:
540 result.append("+=");
541 break;
542 case Token.ASSIGN_SUB:
543 result.append("-=");
544 break;
545 case Token.ASSIGN_MUL:
546 result.append("*=");
547 break;
548 case Token.ASSIGN_DIV:
549 result.append("/=");
550 break;
551 case Token.ASSIGN_MOD:
552 result.append("%=");
553 break;
554 case Token.ASSIGN_BITOR:
555 result.append("|=");
556 break;
557 case Token.ASSIGN_BITXOR:
558 result.append("^=");
559 break;
560 case Token.ASSIGN_BITAND:
561 result.append("&=");
562 break;
563 case Token.ASSIGN_LSH:
564 result.append("<<=");
565 break;
566 case Token.ASSIGN_RSH:
567 result.append(">>=");
568 break;
569 case Token.ASSIGN_URSH:
570 result.append(">>>=");
571 break;
572 case Token.HOOK:
573 result.append("?");
574 break;
575 case Token.OBJECTLIT:
576
577
578
579
580
581 result.append(':');
582 break;
583 case Token.COLON:
584 if (Token.EOL == getNext(encodedSource, length, i))
585
586 result.append(':');
587 else
588
589 result.append(":");
590 break;
591 case Token.OR:
592 result.append("||");
593 break;
594 case Token.AND:
595 result.append("&&");
596 break;
597 case Token.BITOR:
598 result.append("|");
599 break;
600 case Token.BITXOR:
601 result.append("^");
602 break;
603 case Token.BITAND:
604 result.append("&");
605 break;
606 case Token.SHEQ:
607 result.append("===");
608 break;
609 case Token.SHNE:
610 result.append("!==");
611 break;
612 case Token.EQ:
613 result.append("==");
614 break;
615 case Token.NE:
616 result.append("!=");
617 break;
618 case Token.LE:
619 result.append("<=");
620 break;
621 case Token.LT:
622 result.append("<");
623 break;
624 case Token.GE:
625 result.append(">=");
626 break;
627 case Token.GT:
628 result.append(">");
629 break;
630 case Token.INSTANCEOF:
631
632 result.append(" instanceof ");
633 break;
634 case Token.LSH:
635 result.append("<<");
636 break;
637 case Token.RSH:
638 result.append(">>");
639 break;
640 case Token.URSH:
641 result.append(">>>");
642 break;
643 case Token.TYPEOF:
644 result.append("typeof ");
645 break;
646 case Token.VOID:
647 result.append("void ");
648 break;
649 case Token.NOT:
650 result.append('!');
651 break;
652 case Token.BITNOT:
653 result.append('~');
654 break;
655 case Token.POS:
656 result.append('+');
657 break;
658 case Token.NEG:
659 result.append('-');
660 break;
661 case Token.INC:
662 if(Token.ADD == prevToken){
663 result.append(' ');
664 }
665 result.append("++");
666 if(Token.ADD == getNext(encodedSource, length, i)){
667 result.append(' ');
668 }
669 break;
670 case Token.DEC:
671 if(Token.SUB == prevToken){
672 result.append(' ');
673 }
674 result.append("--");
675 if(Token.SUB == getNext(encodedSource, length, i)){
676 result.append(' ');
677 }
678 break;
679 case Token.ADD:
680 result.append("+");
681 int nextToken = encodedSource.charAt(i + 1);
682 if (nextToken == Token.POS) {
683 result.append(' ');
684 }
685 break;
686 case Token.SUB:
687 result.append("-");
688 nextToken = encodedSource.charAt(i + 1);
689 if (nextToken == Token.NEG) {
690 result.append(' ');
691 }
692 break;
693 case Token.MUL:
694 result.append("*");
695 break;
696 case Token.DIV:
697 result.append("/");
698 break;
699 case Token.MOD:
700 result.append("%");
701 break;
702 case Token.COLONCOLON:
703 result.append("::");
704 break;
705 case Token.DOTDOT:
706 result.append("..");
707 break;
708 case Token.XMLATTR:
709 result.append('@');
710 break;
711 case Token.DEBUGGER:
712 System.out.println("WARNING: Found a `debugger;` statement in code being compressed");
713 result.append("debugger");
714 break;
715 default:
716
717 throw new RuntimeException();
718 }
719 if (thisToken != Token.EOL) {
720 lastMeaningfulToken = thisToken;
721 }
722 ++i;
723 }
724 if (!toSource) {
725
726
727
728
729 } else {
730 if (topFunctionType == FunctionNode.FUNCTION_EXPRESSION) {
731 result.append(')');
732 }
733 }
734 return result.toString();
735 }
736
737
738
739
740
741
742
743
744
745
746 private static Map collectReplacedTokens(String encodedSource, boolean escapeUnicode, TokenMapper tm) {
747 int length = encodedSource.length();
748
749 int i = 0;
750 int prevToken = 0;
751 int braceNesting = 0;
752
753 boolean inArgsList = false;
754 boolean primeFunctionNesting = false;
755 boolean primeInArgsList = false;
756
757 if (encodedSource.charAt(i) == Token.SCRIPT) {
758 ++i;
759 }
760
761 Stack positionStack = new Stack();
762 Stack functionPositionStack = new Stack();
763 Map tokenLookup = new HashMap();
764
765 while (i < length) {
766 if (i > 0) {
767 prevToken = encodedSource.charAt(i - 1);
768 }
769 switch (encodedSource.charAt(i)) {
770 case Token.NAME:
771 case Token.REGEXP: {
772 int jumpPos = getSourceStringEnd(encodedSource, i + 1, escapeUnicode);
773 if (Token.OBJECTLIT == encodedSource.charAt(jumpPos)) {
774 i = printSourceString(encodedSource, i + 1, false, null, escapeUnicode);
775 } else {
776 i = tm.sourceCompress(encodedSource, i + 1, false, null, prevToken, inArgsList, braceNesting, null);
777 }
778 continue;
779 }
780 case Token.STRING: {
781 i = printSourceString(encodedSource, i + 1, true, null, escapeUnicode);
782 continue;
783 }
784 case Token.NUMBER: {
785 i = printSourceNumber(encodedSource, i + 1, null);
786 continue;
787 }
788 case Token.FUNCTION: {
789 ++i;
790 tm.incrementFunctionNumber();
791 primeInArgsList = true;
792 primeFunctionNesting = true;
793 functionPositionStack.push(new Integer(i-1));
794 break;
795 }
796 case Token.LC: {
797 ++braceNesting;
798 break;
799 }
800 case Token.RC: {
801 Map m = tm.getCurrentTokens();
802 if (tm.leaveNestingLevel(braceNesting)) {
803 Integer pos = (Integer)positionStack.pop();
804 Integer functionPos = (Integer)functionPositionStack.pop();
805 int[] parents = new int[positionStack.size()];
806 int idx = 0;
807 for (Iterator itr = positionStack.iterator(); itr.hasNext();) {
808 parents[idx++] = ((Integer)itr.next()).intValue();
809 }
810 DebugData debugData = tm.getDebugData(functionPos);
811 ReplacedTokens replacedTokens = new ReplacedTokens(m, parents, tokenLookup, debugData);
812 tokenLookup.put(pos, replacedTokens);
813 }
814 --braceNesting;
815 break;
816 }
817 case Token.LP: {
818 if (primeInArgsList) {
819 inArgsList = true;
820 primeInArgsList = false;
821 }
822 if (primeFunctionNesting) {
823 positionStack.push(new Integer(i));
824 tm.enterNestingLevel(braceNesting);
825 primeFunctionNesting = false;
826 }
827 break;
828 }
829 case Token.RP: {
830 if (inArgsList) {
831 inArgsList = false;
832 }
833 break;
834 }
835 }
836 ++i;
837 }
838 return tokenLookup;
839 }
840
841 private static int getNext(String source, int length, int i) {
842 return (i + 1 < length) ? source.charAt(i + 1) : Token.EOF;
843 }
844
845 private static int getSourceStringEnd(String source, int offset, boolean escapeUnicode) {
846 return printSourceString(source, offset, false, null, escapeUnicode);
847 }
848
849 private static int printSourceString(String source, int offset,
850 boolean asQuotedString,
851 StringBuffer sb,
852 boolean escapeUnicode) {
853 int length = source.charAt(offset);
854 ++offset;
855 if ((0x8000 & length) != 0) {
856 length = ((0x7FFF & length) << 16) | source.charAt(offset);
857 ++offset;
858 }
859 if (sb != null) {
860 String str = source.substring(offset, offset + length);
861 if (!asQuotedString) {
862 sb.append(str);
863 } else {
864 sb.append('"');
865 sb.append(escapeString(str, escapeUnicode));
866 sb.append('"');
867 }
868 }
869 return offset + length;
870 }
871
872 private static int printSourceNumber(String source, int offset, StringBuffer sb) {
873 double number = 0.0;
874 char type = source.charAt(offset);
875 ++offset;
876 if (type == 'S') {
877 if (sb != null) {
878 int ival = source.charAt(offset);
879 number = ival;
880 }
881 ++offset;
882 } else if (type == 'J' || type == 'D') {
883 if (sb != null) {
884 long lbits;
885 lbits = (long) source.charAt(offset) << 48;
886 lbits |= (long) source.charAt(offset + 1) << 32;
887 lbits |= (long) source.charAt(offset + 2) << 16;
888 lbits |= source.charAt(offset + 3);
889 if (type == 'J') {
890 number = lbits;
891 } else {
892 number = Double.longBitsToDouble(lbits);
893 }
894 }
895 offset += 4;
896 } else {
897
898 throw new RuntimeException();
899 }
900 if (sb != null) {
901 sb.append(ScriptRuntime.numberToString(number, 10));
902 }
903 return offset;
904 }
905
906 private static String escapeString(String s, boolean escapeUnicode) {
907 return escapeString(s, '"', escapeUnicode);
908 }
909
910 private static String escapeString(String s, char escapeQuote, boolean escapeUnicode) {
911 if (!(escapeQuote == '"' || escapeQuote == '\'')) Kit.codeBug();
912 StringBuffer sb = null;
913
914 for(int i = 0, L = s.length(); i != L; ++i) {
915 int c = s.charAt(i);
916
917 if (' ' <= c && c <= '~' && c != escapeQuote && c != '\\') {
918
919
920 if (sb != null) {
921 sb.append((char)c);
922 }
923 continue;
924 }
925 if (sb == null) {
926 sb = new StringBuffer(L + 3);
927 sb.append(s);
928 sb.setLength(i);
929 }
930
931 int escape = -1;
932 switch (c) {
933 case '\b': escape = 'b'; break;
934 case '\f': escape = 'f'; break;
935 case '\n': escape = 'n'; break;
936 case '\r': escape = 'r'; break;
937 case '\t': escape = 't'; break;
938 case 0xb: escape = 'v'; break;
939 case ' ': escape = ' '; break;
940 case '\\': escape = '\\'; break;
941 }
942 if (escape >= 0) {
943
944 sb.append('\\');
945 sb.append((char)escape);
946 } else if (c == escapeQuote) {
947 sb.append('\\');
948 sb.append(escapeQuote);
949 } else {
950 if (escapeUnicode || c == 0) {
951 int hexSize;
952 if (c < 256) {
953
954 sb.append("\\x");
955 hexSize = 2;
956 } else {
957
958 sb.append("\\u");
959 hexSize = 4;
960 }
961
962 for (int shift = (hexSize - 1) * 4; shift >= 0; shift -= 4) {
963 int digit = 0xf & (c >> shift);
964 int hc = (digit < 10) ? '0' + digit : 'a' - 10 + digit;
965 sb.append((char)hc);
966 }
967 }
968 else {
969 sb.append((char)c);
970 }
971 }
972 }
973 return (sb == null) ? s : sb.toString();
974 }
975
976 public static final String compressScript(Reader source)
977 throws IOException
978 {
979 return compressScript(source, 0, 1, false, null);
980 }
981
982 public static final String compressScript(Reader source, String stripConsole)
983 throws IOException
984 {
985 return compressScript(source, 0, 1, false, stripConsole);
986 }
987
988 public static final String compressScript(Reader source, int indent, int lineno, String stripConsole)
989 throws IOException
990 {
991 return compressScript(source, indent, lineno, false, stripConsole);
992 }
993
994 public static final String compressScript(Reader source, int indent, int lineno, boolean escapeUnicode, String stripConsole)
995 throws IOException
996 {
997 return compressScript(source, indent, lineno, escapeUnicode, stripConsole, null);
998 }
999
1000 public static final String compressScript(Reader source, int indent, int lineno, boolean escapeUnicode, String stripConsole, StringBuffer debugData)
1001 throws IOException
1002 {
1003 CompilerEnvirons compilerEnv = new CompilerEnvirons();
1004
1005 Parser parser = new Parser(compilerEnv, compilerEnv.getErrorReporter());
1006
1007 ScriptOrFnNode tree = parser.parse(source, null, lineno);
1008 String encodedSource = parser.getEncodedSource();
1009 if (encodedSource.length() == 0) { return ""; }
1010
1011 Interpreter compiler = new Interpreter();
1012 compiler.compile(compilerEnv, tree, encodedSource, false);
1013 UintMap properties = new UintMap(1);
1014 properties.put(Decompiler.INITIAL_INDENT_PROP, indent);
1015
1016 TokenMapper tm = new TokenMapper(tree);
1017 Map replacedTokensLookup = collectReplacedTokens(encodedSource, escapeUnicode, tm);
1018 tm.reset();
1019
1020 String compressedSource = compress(encodedSource, 0, properties, tree, escapeUnicode, stripConsole, tm, replacedTokensLookup);
1021 if (debugData != null) {
1022 debugData.append("[\n");
1023 int count = 1;
1024 for (Iterator itr = replacedTokensLookup.keySet().iterator(); itr.hasNext();) {
1025 Integer pos = (Integer)itr.next();
1026 ReplacedTokens replacedTokens = (ReplacedTokens)replacedTokensLookup.get(pos);
1027 debugData.append(replacedTokens.toJson());
1028 if (count++ < replacedTokensLookup.size()) {
1029 debugData.append(',');
1030 }
1031 debugData.append("\n");
1032 }
1033 debugData.append("]");
1034 }
1035 return compressedSource;
1036 }
1037 }