1/*
2 * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package build.tools.commentchecker;
27
28import java.io.*;
29import java.util.StringTokenizer;
30
31/**
32 * CommentChecker is a utility which verifies that there aren't
33 * "/*" or "/**" tokens inside any comment blocks in one or more
34 * Java source files.  Although it is legal to have beginning
35 * comment delimiters inside of a comment block (JLS 3.7), there
36 * have been errors where a dropped end-comment delimiter in a
37 * method'd doc-comment effectively "erased" that method.  We're
38 * therefore restricting beginning comment delimiters inside of
39 * JDK source (at least the Swing team is for their portion).
40 *
41 * To scan a few files, run CommentChecker as follows:
42 *
43 *     java CommentChecker file1.java file2.java ...
44 *
45 * There are too many Java files in the JDK base for most shells
46 * to support listing in a single command, so CommentChecker also
47 * supports cpio and tar-style filename passing, where "-"
48 * indicates that the list of files is read from stdin:
49 *
50 *     find . -name SCCS -prune -o -name '*.java' -print | \
51 *        java CommentChecker -
52 *
53 * @author Thomas Ball
54 */
55public class CommentChecker {
56
57    static int errors = 0;
58
59    // Turn on this flag and recompile to dump this tool's state changes.
60    final static boolean verbose = false;
61
62    static void check(String fileName) {
63        BufferedReader in = null;
64        boolean inComment = false;
65        boolean inLineComment = false;
66        boolean inQuote = false;
67        boolean inEscape = false;
68        int lastChar = -1;
69        int lineNumber = 1;
70
71        try {
72            in = new BufferedReader(new FileReader(fileName));
73            while (true) {
74                int ch = in.read();
75                if (ch == -1) {
76                    if (inQuote || inComment) {
77                        error(fileName + ": premature EOF.");
78                    }
79                    return;
80                }
81
82                if (verbose) {
83                    System.out.print((char)ch);
84                }
85
86                switch (ch) {
87                  case '\n':
88                    if (inQuote && !inComment) {
89                        error(fileName + ":" + lineNumber +
90                              " dangling quote.");
91                        inQuote = false;
92                    }
93                    if (inLineComment) {
94                        inLineComment = false;
95                        if (verbose) {
96                            System.out.println("\ninLineComment=false");
97                        }
98                    }
99                    lineNumber++;
100                    break;
101
102                  case '\"':
103                    if (!inComment && !inLineComment && !inEscape &&
104                        !(!inQuote && lastChar == '\'')) {
105                        inQuote = !inQuote;
106                        if (verbose) {
107                            System.out.println("\ninQuote=" + inQuote);
108                        }
109                    }
110                    break;
111
112                  case '/':
113                    if (!inQuote && lastChar == '*') {
114                        inComment = false;
115                        if (verbose) {
116                            System.out.println("\ninComment=false");
117                        }
118                    }
119                    if (!inQuote && lastChar == '/') {
120                        inLineComment = true;
121                        if (verbose) {
122                            System.out.println("\ninLineComment=true");
123                        }
124                    }
125                    break;
126
127                  case '*':
128                    if (!inQuote && lastChar == '/') {
129                        if (inComment) {
130                            error(fileName + ":" + lineNumber +
131                                  " nested comment.");
132                        }
133                        inComment = true;
134                        if (verbose) {
135                            System.out.println("\ninComment=true");
136                        }
137                    }
138                    break;
139                }
140
141                lastChar = ch;
142
143                // Watch for escaped characters, such as '\"'.
144                if (ch == '\\' && !inEscape) {
145                    inEscape = true;
146                    if (verbose) {
147                        System.out.println("\ninEscape set");
148                    }
149                } else {
150                    inEscape = false;
151                }
152            }
153        } catch (FileNotFoundException fnfe) {
154            error(fileName + " not found.");
155        } catch (IOException ioe) {
156            error(fileName + ": " + ioe);
157        } finally {
158            if (in != null) {
159                try {
160                    in.close();
161                } catch (IOException e) {
162                    error(fileName + ": " + e);
163                }
164            }
165        }
166    }
167
168    static void error(String description) {
169        System.err.println(description);
170        errors++;
171    }
172
173    static void exit() {
174        if (errors != 1) {
175            System.out.println("There were " + errors + " errors.");
176        } else {
177            System.out.println("There was 1 error.");
178        }
179        System.exit(errors);
180    }
181
182    public static void main(String[] args) {
183        if (args.length == 0) {
184            System.err.println("usage: java CommentChecker [-] file.java ...");
185            System.exit(1);
186        }
187
188        if (args.length == 1 && args[0].equals("-")) {
189            /* read filenames in one per line from stdin, ala cpio.
190             * This is good for checking the whole JDK in one pass:
191             *
192             *    cpio . -name SCCS -prune -o -name '*.java' -print | \
193             *        java CommentChecker -
194             */
195            try {
196                BufferedReader br =
197                    new BufferedReader(new InputStreamReader(System.in));
198                while (true) {
199                    String fileName = br.readLine();
200                    if (fileName == null) {
201                        break;
202                    }
203                    check(fileName);
204                }
205                br.close();
206            } catch (Exception e) {
207                error("error reading System.in: " + e);
208            }
209        } else {
210            for (int i = 0; i < args.length; i++) {
211                check(args[i]);
212            }
213        }
214
215        exit();
216    }
217}
218