View Javadoc

1   /*
2    *
3    * JSMin.java 2006-02-13
4    *
5    * Updated 2007-08-20 with updates from jsmin.c (2007-05-22)
6    *
7    * Copyright (c) 2006 John Reilly (www.inconspicuous.org)
8    *
9    * This work is a translation from C to Java of jsmin.c published by
10   * Douglas Crockford.  Permission is hereby granted to use the Java
11   * version under the same conditions as the jsmin.c on which it is
12   * based.
13   *
14   *
15   *
16   *
17   * jsmin.c 2003-04-21
18   *
19   * Copyright (c) 2002 Douglas Crockford (www.crockford.com)
20   *
21   * Permission is hereby granted, free of charge, to any person obtaining a copy
22   * of this software and associated documentation files (the "Software"), to deal
23   * in the Software without restriction, including without limitation the rights
24   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
25   * copies of the Software, and to permit persons to whom the Software is
26   * furnished to do so, subject to the following conditions:
27   *
28   * The above copyright notice and this permission notice shall be included in
29   * all copies or substantial portions of the Software.
30   *
31   * The Software shall be used for Good, not Evil.
32   *
33   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39   * SOFTWARE.
40   */
41  
42  package org.inconspicuous.jsmin;
43  
44  import java.io.FileInputStream;
45  import java.io.FileNotFoundException;
46  import java.io.IOException;
47  import java.io.InputStream;
48  import java.io.OutputStream;
49  import java.io.PushbackInputStream;
50  
51  
52  public class JSMin {
53      private static final int EOF = -1;
54  
55      private PushbackInputStream in;
56      private OutputStream out;
57  
58      private int theA;
59      private int theB;
60  
61      public JSMin(InputStream in, OutputStream out) {
62          this.in = new PushbackInputStream(in);
63          this.out = out;
64      }
65  
66      /**
67       * isAlphanum -- return true if the character is a letter, digit,
68       * underscore, dollar sign, or non-ASCII character.
69       */
70      static boolean isAlphanum(int c) {
71          return ( (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
72                   (c >= 'A' && c <= 'Z') || c == '_' || c == '$' || c == '\\' ||
73                   c > 126);
74      }
75  
76      /**
77       * get -- return the next character from stdin. Watch out for lookahead. If
78       * the character is a control character, translate it to a space or
79       * linefeed.
80       */
81      int get() throws IOException {
82          int c = in.read();
83  
84          if (c >= ' ' || c == '\n' || c == EOF) {
85              return c;
86          }
87  
88          if (c == '\r') {
89              return '\n';
90          }
91  
92          return ' ';
93      }
94  
95  
96  
97      /**
98       * Get the next character without getting it.
99       */
100     int peek() throws IOException {
101         int lookaheadChar = in.read();
102         in.unread(lookaheadChar);
103         return lookaheadChar;
104     }
105 
106     /**
107      * next -- get the next character, excluding comments. peek() is used to see
108      * if a '/' is followed by a '/' or '*'.
109      */
110     int next() throws IOException, UnterminatedCommentException {
111         int c = get();
112         if (c == '/') {
113             switch (peek()) {
114             case '/':
115                 for (;;) {
116                     c = get();
117                     if (c <= '\n') {
118                         return c;
119                     }
120                 }
121 
122             case '*':
123                 get();
124                 for (;;) {
125                     switch (get()) {
126                     case '*':
127                         if (peek() == '/') {
128                             get();
129                             return ' ';
130                         }
131                         break;
132                     case EOF:
133                         throw new UnterminatedCommentException();
134                     }
135                 }
136 
137             default:
138                 return c;
139             }
140 
141         }
142         return c;
143     }
144 
145     /**
146      * action -- do something! What you do is determined by the argument: 1
147      * Output A. Copy B to A. Get the next B. 2 Copy B to A. Get the next B.
148      * (Delete A). 3 Get the next B. (Delete B). action treats a string as a
149      * single character. Wow! action recognizes a regular expression if it is
150      * preceded by ( or , or =.
151      */
152 
153     void action(int d) throws IOException, UnterminatedRegExpLiteralException,
154             UnterminatedCommentException, UnterminatedStringLiteralException {
155         switch (d) {
156         case 1:
157             out.write(theA);
158         case 2:
159             theA = theB;
160 
161             if (theA == '\'' || theA == '"') {
162                 for (;;) {
163                     out.write(theA);
164                     theA = get();
165                     if (theA == theB) {
166                         break;
167                     }
168                     if (theA <= '\n') {
169                         throw new UnterminatedStringLiteralException();
170                     }
171                     if (theA == '\\') {
172                         out.write(theA);
173                         theA = get();
174                     }
175                 }
176             }
177 
178         case 3:
179             theB = next();
180             if (theB == '/' && (theA == '(' || theA == ',' || theA == '=' ||
181                                 theA == ':' || theA == '[' || theA == '!' ||
182                                 theA == '&' || theA == '|' || theA == '?' ||
183                                 theA == '{' || theA == '}' || theA == ';' ||
184                                 theA == '\n')) {
185                 out.write(theA);
186                 out.write(theB);
187                 for (;;) {
188                     theA = get();
189                     if (theA == '/') {
190                         break;
191                     } else if (theA == '\\') {
192                         out.write(theA);
193                         theA = get();
194                     } else if (theA <= '\n') {
195                         throw new UnterminatedRegExpLiteralException();
196                     }
197                     out.write(theA);
198                 }
199                 theB = next();
200             }
201         }
202     }
203 
204     /**
205      * jsmin -- Copy the input to the output, deleting the characters which are
206      * insignificant to JavaScript. Comments will be removed. Tabs will be
207      * replaced with spaces. Carriage returns will be replaced with linefeeds.
208      * Most spaces and linefeeds will be removed.
209      */
210     public void jsmin() throws IOException, UnterminatedRegExpLiteralException, UnterminatedCommentException, UnterminatedStringLiteralException{
211         theA = '\n';
212         action(3);
213         while (theA != EOF) {
214             switch (theA) {
215             case ' ':
216                 if (isAlphanum(theB)) {
217                     action(1);
218                 } else {
219                     action(2);
220                 }
221                 break;
222             case '\n':
223                 switch (theB) {
224                 case '{':
225                 case '[':
226                 case '(':
227                 case '+':
228                 case '-':
229                     action(1);
230                     break;
231                 case ' ':
232                     action(3);
233                     break;
234                 default:
235                     if (isAlphanum(theB)) {
236                         action(1);
237                     } else {
238                         action(2);
239                     }
240                 }
241                 break;
242             default:
243                 switch (theB) {
244                 case ' ':
245                     if (isAlphanum(theA)) {
246                         action(1);
247                         break;
248                     }
249                     action(3);
250                     break;
251                 case '\n':
252                     switch (theA) {
253                     case '}':
254                     case ']':
255                     case ')':
256                     case '+':
257                     case '-':
258                     case '"':
259                     case '\'':
260                         action(1);
261                         break;
262                     default:
263                         if (isAlphanum(theA)) {
264                             action(1);
265                         } else {
266                             action(3);
267                         }
268                     }
269                     break;
270                 default:
271                     action(1);
272                     break;
273                 }
274             }
275         }
276         out.flush();
277     }
278 
279     class UnterminatedCommentException extends Exception {
280     }
281 
282     class UnterminatedStringLiteralException extends Exception {
283     }
284 
285     class UnterminatedRegExpLiteralException extends Exception {
286     }
287 
288     public static void main(String arg[]) {
289         try {
290             JSMin jsmin = new JSMin(new FileInputStream(arg[0]), System.out);
291             jsmin.jsmin();
292         } catch (FileNotFoundException e) {
293             e.printStackTrace();
294         } catch (ArrayIndexOutOfBoundsException e) {
295             e.printStackTrace();
296         } catch (IOException e) {
297             e.printStackTrace();
298         } catch (UnterminatedRegExpLiteralException e) {
299             e.printStackTrace();
300         } catch (UnterminatedCommentException e) {
301             e.printStackTrace();
302         } catch (UnterminatedStringLiteralException e) {
303             e.printStackTrace();
304         }
305     }
306 
307 
308 }