001: /*
002: * FindBugs - Find Bugs in Java programs
003: * Copyright (C) 2003-2007 University of Maryland
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: */
019:
020: package edu.umd.cs.findbugs.jaif;
021:
022: import java.io.FileReader;
023: import java.io.IOException;
024: import java.io.Reader;
025: import java.util.Locale;
026:
027: /**
028: * Parse an external annotation file.
029: *
030: * @author David Hovemeyer
031: * @see http://pag.csail.mit.edu/jsr308/annotation-file-utilities/
032: */
033: public class JAIFParser {
034: private JAIFScanner scanner;
035: private JAIFEvents callback;
036:
037: public JAIFParser(Reader reader, JAIFEvents callback) {
038: this .scanner = new JAIFScanner(reader);
039: this .callback = callback;
040: }
041:
042: public void parse() throws IOException, JAIFSyntaxException {
043: parseAnnotationFile();
044: }
045:
046: int getLineNumber() {
047: return scanner.getLineNumber();
048: }
049:
050: private JAIFToken expect(String s) throws IOException,
051: JAIFSyntaxException {
052: JAIFToken t = scanner.nextToken();
053: if (!t.lexeme.equals(s)) {
054: throw new JAIFSyntaxException(this , "Unexpected token " + t
055: + " (was expecting " + s + ")");
056: }
057: return t;
058: }
059:
060: private JAIFToken expect(JAIFTokenKind kind) throws IOException,
061: JAIFSyntaxException {
062: JAIFToken t = scanner.nextToken();
063: if (t.kind != kind) {
064: throw new JAIFSyntaxException(this , "Unexpected token " + t
065: + " (was expecting a `" + kind.toString()
066: + "' token)");
067: }
068: return t;
069: }
070:
071: private void expectEndOfLine() throws IOException,
072: JAIFSyntaxException {
073: // extract-annotations seems to sometimes produce multiple newlines where
074: // the grammar indicates that only one will appear.
075: // So, we treat any sequence of one or more newlines as one newline.
076: int nlCount = 0;
077: JAIFToken t;
078:
079: while (true) {
080: if (scanner.atEOF()) {
081: t = null;
082: break;
083: }
084:
085: t = scanner.peekToken();
086: if (t.kind != JAIFTokenKind.NEWLINE) {
087: break;
088: }
089:
090: ++nlCount;
091: scanner.nextToken();
092: }
093:
094: if (nlCount < 1) {
095: String msg = (t == null) ? "Unexpected end of file"
096: : "Unexpected token " + t
097: + " (was expecting <newline>)";
098: throw new JAIFSyntaxException(this , msg);
099: }
100: }
101:
102: private String readCompoundName() throws IOException,
103: JAIFSyntaxException {
104: StringBuffer buf = new StringBuffer();
105:
106: boolean firstToken = true;
107:
108: while (true) {
109: JAIFToken t = scanner.nextToken();
110: assert t.kind == JAIFTokenKind.IDENTIFIER_OR_KEYWORD;
111:
112: if (firstToken) {
113: firstToken = false;
114: } else if (t.lexeme.startsWith("@")) {
115: throw new JAIFSyntaxException(this ,
116: "Illegal compound name (unexpected '@' character)");
117: }
118:
119: buf.append(t.lexeme);
120:
121: t = scanner.peekToken();
122: if (t.kind != JAIFTokenKind.DOT) {
123: break;
124: } else {
125: buf.append(t.lexeme);
126: scanner.nextToken();
127: }
128: }
129:
130: return buf.toString();
131: }
132:
133: private String readType() throws IOException, JAIFSyntaxException {
134: StringBuffer buf = new StringBuffer();
135:
136: JAIFToken t = expect(JAIFTokenKind.IDENTIFIER_OR_KEYWORD);
137:
138: if (t.lexeme.equals("enum")) {
139:
140: }
141:
142: return buf.toString();
143: }
144:
145: private void parseAnnotationFile() throws IOException,
146: JAIFSyntaxException {
147: parsePackageDefinition();
148: while (!scanner.atEOF()) {
149: parsePackageDefinition();
150: }
151: }
152:
153: private void parsePackageDefinition() throws IOException,
154: JAIFSyntaxException {
155: expect("package");
156: JAIFToken t = scanner.peekToken();
157:
158: String pkgName;
159:
160: if (t.kind != JAIFTokenKind.NEWLINE) {
161: // Optional package name and package-level annotations
162:
163: // Hmmm....the spec says just a plain identifier here.
164: // However, I'm pretty sure we want a compound name.
165: pkgName = readCompoundName();
166:
167: expect(":");
168:
169: t = scanner.peekToken();
170: while (t.isStartOfAnnotationName()) {
171: parseAnnotation();
172: }
173: } else {
174: pkgName = ""; // default package
175: }
176:
177: expectEndOfLine();
178:
179: callback.startPackageDefinition(pkgName);
180:
181: while (!scanner.atEOF()) {
182: t = scanner.peekToken();
183: if (t.lexeme.equals("package")) {
184: break;
185: }
186:
187: parseAnnotationDefinitionOrClassDefinition();
188: }
189:
190: callback.endPackageDefinition(pkgName);
191: }
192:
193: private void parseAnnotation() throws IOException,
194: JAIFSyntaxException {
195: String annotationName = readCompoundName();
196: assert annotationName.startsWith("@");
197:
198: callback.startAnnotation(annotationName);
199:
200: JAIFToken t = scanner.peekToken();
201: if (t.kind == JAIFTokenKind.LPAREN) {
202: parseAnnotationField();
203: t = scanner.peekToken();
204: while (t.kind != JAIFTokenKind.RPAREN) {
205: expect(",");
206: parseAnnotationField();
207: t = scanner.peekToken();
208: }
209: assert t.kind == JAIFTokenKind.RPAREN;
210: scanner.nextToken();
211: }
212:
213: callback.endAnnotation(annotationName);
214: }
215:
216: private void parseAnnotationField() throws IOException,
217: JAIFSyntaxException {
218: JAIFToken id = expect(JAIFTokenKind.IDENTIFIER_OR_KEYWORD);
219: expect("=");
220: Object constant = parseConstant();
221:
222: callback.annotationField(id.lexeme, constant);
223: }
224:
225: private Object parseConstant() throws IOException,
226: JAIFSyntaxException {
227: JAIFToken t = scanner.peekToken();
228:
229: switch (t.kind) {
230: case IDENTIFIER_OR_KEYWORD:
231: // This is an enum constant specified by a compound name.
232: // Represent it as a JAIFEnumConstant object.
233: String name = readCompoundName();
234: return new JAIFEnumConstant(name);
235: case DECIMAL_LITERAL:
236: t = scanner.nextToken();
237: return Integer.parseInt(t.lexeme);
238: case OCTAL_LITERAL:
239: t = scanner.nextToken();
240: return Integer.parseInt(t.lexeme, 8);
241: case HEX_LITERAL:
242: t = scanner.nextToken();
243: return Integer.parseInt(t.lexeme, 16);
244: case FLOATING_POINT_LITERAL:
245: t = scanner.nextToken();
246: boolean isFloat = t.lexeme.toLowerCase(Locale.ENGLISH)
247: .endsWith("f");
248: if (isFloat) {
249: return Float.parseFloat(t.lexeme);
250: } else {
251: return Double.parseDouble(t.lexeme);
252: }
253: case STRING_LITERAL:
254: t = scanner.nextToken();
255: return unparseStringLiteral(t.lexeme);
256: default:
257: throw new JAIFSyntaxException(this , "Illegal constant");
258: }
259: }
260:
261: private Object unparseStringLiteral(String lexeme) {
262: StringBuffer buf = new StringBuffer();
263:
264: int where = 1; // skip initial double quote char
265:
266: while (true) {
267: assert where < lexeme.length();
268: char c = lexeme.charAt(where);
269:
270: if (c == '"') {
271: break;
272: }
273:
274: if (c != '\\') {
275: buf.append(c);
276: where++;
277: continue;
278: }
279:
280: where++;
281: assert where < lexeme.length();
282:
283: c = lexeme.charAt(where);
284: switch (c) {
285: case 'b':
286: buf.append('\b');
287: where++;
288: break;
289: case 't':
290: buf.append('\t');
291: where++;
292: break;
293: case 'n':
294: buf.append('\n');
295: where++;
296: break;
297: case 'f':
298: buf.append('\t');
299: where++;
300: break;
301: case 'r':
302: buf.append('\r');
303: where++;
304: break;
305: case '"':
306: buf.append('"');
307: where++;
308: break;
309: case '\'':
310: buf.append('\'');
311: where++;
312: break;
313: case '\\':
314: buf.append('\\');
315: where++;
316: break;
317: default:
318: char value = (char) 0;
319: while (c >= '0' && c <= '7') {
320: value *= 8;
321: value += (c - '0');
322: where++;
323: assert where < lexeme.length();
324: c = lexeme.charAt(where);
325: }
326: buf.append(value);
327: }
328: }
329:
330: return buf.toString();
331: }
332:
333: private void parseAnnotationDefinitionOrClassDefinition()
334: throws IOException, JAIFSyntaxException {
335: JAIFToken t = scanner.peekToken();
336:
337: if (t.lexeme.equals("annotation")) {
338: parseAnnotationDefinition();
339: } else if (t.lexeme.equals("class")) {
340: parseClassDefinition();
341: } else {
342: throw new JAIFSyntaxException(this , "Unexpected token " + t
343: + " (expected `annotation' or `class')");
344: }
345: }
346:
347: private void parseAnnotationDefinition() throws IOException,
348: JAIFSyntaxException {
349: expect("annotation");
350:
351: String retention = null;
352:
353: JAIFToken t = scanner.peekToken();
354: if (t.lexeme.equals("visible") || t.lexeme.equals("invisible")
355: || t.lexeme.equals("source")) {
356: retention = t.lexeme;
357: scanner.nextToken();
358: }
359:
360: String annotationName = expect(JAIFTokenKind.IDENTIFIER_OR_KEYWORD).lexeme;
361:
362: expect(JAIFTokenKind.COLON);
363: expectEndOfLine();
364:
365: callback.startAnnotationDefinition(annotationName, retention);
366:
367: t = scanner.peekToken();
368: while (t.kind != JAIFTokenKind.NEWLINE) {
369: parseAnnotationFieldDefinition();
370: }
371: }
372:
373: private void parseAnnotationFieldDefinition() throws IOException,
374: JAIFSyntaxException {
375: String type = readType();
376: String fieldName = expect(JAIFTokenKind.IDENTIFIER_OR_KEYWORD).lexeme;
377:
378: callback.annotationFieldDefinition(type, fieldName);
379: }
380:
381: private void parseClassDefinition() {
382:
383: }
384:
385: public static void main(String[] args) throws Exception {
386: if (args.length != 1) {
387: System.err.println("Usage: " + JAIFParser.class.getName()
388: + " <jaif file>");
389: System.exit(1);
390: }
391:
392: JAIFEvents callback = new JAIFEvents() {
393: public void annotationField(String fieldName,
394: Object constant) {
395: System.out.println(" " + fieldName + "=" + constant);
396: }
397:
398: public void endAnnotation(String annotationName) {
399: }
400:
401: public void endPackageDefinition(String pkgName) {
402: }
403:
404: public void startAnnotation(String annotationName) {
405: System.out.println(" annotation " + annotationName);
406: }
407:
408: public void startPackageDefinition(String pkgName) {
409: System.out.println("package " + pkgName);
410: }
411:
412: public void startAnnotationDefinition(
413: String annotationName, String retention) {
414: System.out.println(" annotation " + annotationName
415: + " " + retention);
416: }
417:
418: public void endAnnotationDefinition(String annotationName) {
419: }
420:
421: public void annotationFieldDefinition(String type,
422: String fieldName) {
423: System.out.println(" " + type + " " + fieldName);
424: }
425: };
426:
427: JAIFParser parser = new JAIFParser(new FileReader(args[0]),
428: callback);
429: parser.parse();
430: }
431: }
|