001/*
002 *
003 * JSMin.java 2006-02-13
004 *
005 * Updated 2007-08-20 with updates from jsmin.c (2007-05-22)
006 *
007 * Copyright (c) 2006 John Reilly (www.inconspicuous.org)
008 *
009 * This work is a translation from C to Java of jsmin.c published by
010 * Douglas Crockford.  Permission is hereby granted to use the Java
011 * version under the same conditions as the jsmin.c on which it is
012 * based.
013 *
014 *
015 *
016 *
017 * jsmin.c 2003-04-21
018 *
019 * Copyright (c) 2002 Douglas Crockford (www.crockford.com)
020 *
021 * Permission is hereby granted, free of charge, to any person obtaining a copy
022 * of this software and associated documentation files (the "Software"), to deal
023 * in the Software without restriction, including without limitation the rights
024 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
025 * copies of the Software, and to permit persons to whom the Software is
026 * furnished to do so, subject to the following conditions:
027 *
028 * The above copyright notice and this permission notice shall be included in
029 * all copies or substantial portions of the Software.
030 *
031 * The Software shall be used for Good, not Evil.
032 *
033 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
034 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
035 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
036 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
037 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
038 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
039 * SOFTWARE.
040 */
041
042package org.nuxeo.theme.html;
043
044import java.io.FileInputStream;
045import java.io.FileNotFoundException;
046import java.io.IOException;
047import java.io.InputStream;
048import java.io.OutputStream;
049import java.io.PushbackInputStream;
050
051import org.apache.commons.logging.Log;
052import org.apache.commons.logging.LogFactory;
053import org.nuxeo.common.xmap.XAnnotatedList;
054
055public class JSMin {
056
057    private static final Log log = LogFactory.getLog(XAnnotatedList.class);
058
059    private static final int EOF = -1;
060
061    private PushbackInputStream in;
062
063    private OutputStream out;
064
065    private int theA;
066
067    private int theB;
068
069    public JSMin(InputStream in, OutputStream out) {
070        this.in = new PushbackInputStream(in);
071        this.out = out;
072    }
073
074    /**
075     * isAlphanum -- return true if the character is a letter, digit, underscore, dollar sign, or non-ASCII character.
076     */
077    static boolean isAlphanum(int c) {
078        return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || c == '_' || c == '$'
079                || c == '\\' || c > 126);
080    }
081
082    /**
083     * get -- return the next character from stdin. Watch out for lookahead. If the character is a control character,
084     * translate it to a space or linefeed.
085     */
086    int get() throws IOException {
087        int c = in.read();
088
089        if (c >= ' ' || c == '\n' || c == EOF) {
090            return c;
091        }
092
093        if (c == '\r') {
094            return '\n';
095        }
096
097        return ' ';
098    }
099
100    /**
101     * Get the next character without getting it.
102     */
103    int peek() throws IOException {
104        int lookaheadChar = in.read();
105        in.unread(lookaheadChar);
106        return lookaheadChar;
107    }
108
109    /**
110     * next -- get the next character, excluding comments. peek() is used to see if a '/' is followed by a '/' or '*'.
111     */
112    int next() throws IOException, UnterminatedCommentException {
113        int c = get();
114        if (c == '/') {
115            switch (peek()) {
116            case '/':
117                for (;;) {
118                    c = get();
119                    if (c <= '\n') {
120                        return c;
121                    }
122                }
123
124            case '*':
125                get();
126                for (;;) {
127                    switch (get()) {
128                    case '*':
129                        if (peek() == '/') {
130                            get();
131                            return ' ';
132                        }
133                        break;
134                    case EOF:
135                        throw new UnterminatedCommentException();
136                    }
137                }
138
139            default:
140                return c;
141            }
142
143        }
144        return c;
145    }
146
147    /**
148     * action -- do something! What you do is determined by the argument: 1 Output A. Copy B to A. Get the next B. 2
149     * Copy B to A. Get the next B. (Delete A). 3 Get the next B. (Delete B). action treats a string as a single
150     * character. Wow! action recognizes a regular expression if it is preceded by ( or , or =.
151     */
152
153    void action(int d) throws IOException, UnterminatedRegExpLiteralException, UnterminatedCommentException,
154            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 == '/'
181                    && (theA == '(' || theA == ',' || theA == '=' || theA == ':' || theA == '[' || theA == '!'
182                            || theA == '&' || theA == '|' || theA == '?' || theA == '{' || theA == '}' || theA == ';' || theA == '\n')) {
183                out.write(theA);
184                out.write(theB);
185                for (;;) {
186                    theA = get();
187                    if (theA == '/') {
188                        break;
189                    } else if (theA == '\\') {
190                        out.write(theA);
191                        theA = get();
192                    } else if (theA <= '\n') {
193                        throw new UnterminatedRegExpLiteralException();
194                    }
195                    out.write(theA);
196                }
197                theB = next();
198            }
199        }
200    }
201
202    /**
203     * jsmin -- Copy the input to the output, deleting the characters which are insignificant to JavaScript. Comments
204     * will be removed. Tabs will be replaced with spaces. Carriage returns will be replaced with linefeeds. Most spaces
205     * and linefeeds will be removed.
206     */
207    public void jsmin() throws IOException, UnterminatedRegExpLiteralException, UnterminatedCommentException,
208            UnterminatedStringLiteralException {
209        theA = '\n';
210        action(3);
211        while (theA != EOF) {
212            switch (theA) {
213            case ' ':
214                if (isAlphanum(theB)) {
215                    action(1);
216                } else {
217                    action(2);
218                }
219                break;
220            case '\n':
221                switch (theB) {
222                case '{':
223                case '[':
224                case '(':
225                case '+':
226                case '-':
227                    action(1);
228                    break;
229                case ' ':
230                    action(3);
231                    break;
232                default:
233                    if (isAlphanum(theB)) {
234                        action(1);
235                    } else {
236                        action(2);
237                    }
238                }
239                break;
240            default:
241                switch (theB) {
242                case ' ':
243                    if (isAlphanum(theA)) {
244                        action(1);
245                        break;
246                    }
247                    action(3);
248                    break;
249                case '\n':
250                    switch (theA) {
251                    case '}':
252                    case ']':
253                    case ')':
254                    case '+':
255                    case '-':
256                    case '"':
257                    case '\'':
258                        action(1);
259                        break;
260                    default:
261                        if (isAlphanum(theA)) {
262                            action(1);
263                        } else {
264                            action(3);
265                        }
266                    }
267                    break;
268                default:
269                    action(1);
270                    break;
271                }
272            }
273        }
274        out.flush();
275    }
276
277    class UnterminatedCommentException extends Exception {
278    }
279
280    class UnterminatedStringLiteralException extends Exception {
281    }
282
283    class UnterminatedRegExpLiteralException extends Exception {
284    }
285
286    public static void main(String arg[]) {
287        try {
288            JSMin jsmin = new JSMin(new FileInputStream(arg[0]), System.out);
289            jsmin.jsmin();
290        } catch (FileNotFoundException e) {
291            log.error(e, e);
292        } catch (ArrayIndexOutOfBoundsException e) {
293            log.error(e, e);
294        } catch (IOException e) {
295            log.error(e, e);
296        } catch (UnterminatedRegExpLiteralException e) {
297            log.error(e, e);
298        } catch (UnterminatedCommentException e) {
299            log.error(e, e);
300        } catch (UnterminatedStringLiteralException e) {
301            log.error(e, e);
302        }
303    }
304
305}