package com.coverity.ps.sac.parser.java; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import com.coverity.ps.sac.io.CoveritySaFormatter; import com.coverity.ps.sac.parser.Parser; /** * Java language parser that detects classes and methods. * @author rhollines */ public class JavaParser implements Parser { static int parseCount = 0; private List<JavaScanner.Token> tokens; private int index = 0; JavaScanner.Token currentToken; Map<Integer, String> functions = new HashMap<Integer, String>(); private JavaScanner scanner; String className = ""; private StringBuffer functionMetrics; private String filename; private int functionCount = 0; /** * Default Constructor * */ public JavaParser() { } /** * Initializes the parser class * * @param filename * file to parse */ private void initialize(String filename) throws IOException { this.filename = filename; BufferedReader reader = null; FileWriter inclFile = null; FileWriter sourceFile = null; try { // read file StringBuilder input = new StringBuilder(); reader = new BufferedReader(new FileReader(filename)); String line = reader.readLine(); while (line != null) { input.append(line); input.append('\n'); line = reader.readLine(); } File outputDirectory = new File("output/intdir"); outputDirectory.mkdir(); outputDirectory = new File(CoveritySaFormatter.OUTPUT_PATH); outputDirectory.mkdir(); outputDirectory = new File(CoveritySaFormatter.OUTPUT_PATH + "/emit"); outputDirectory.mkdir(); outputDirectory = new File(CoveritySaFormatter.OUTPUT_PATH + "/output"); outputDirectory.mkdir(); outputDirectory = new File(CoveritySaFormatter.OUTPUT_PATH + "/emit/f" + (parseCount++)); outputDirectory.mkdir(); // create incl file inclFile = new FileWriter(outputDirectory.toString() + "/incl"); inclFile.write(filename + '|'); // create source file final String source = input.toString(); sourceFile = new FileWriter(outputDirectory.toString() + "/source"); sourceFile.write(source); // scan source this.scanner = new JavaScanner(source); tokens = this.scanner.scan(); nextToken(); } finally { try { if (reader != null) { reader.close(); } if (inclFile != null) { inclFile.close(); } if (sourceFile != null) { sourceFile.close(); } } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) throws Exception { if (args.length > 0) { JavaParser parser = new JavaParser(); parser.parse(args[0], new StringBuffer()); } } /** * Get next token */ private void nextToken() { if (index < tokens.size()) { currentToken = (JavaScanner.Token) tokens.get(index++); } else { currentToken = new JavaScanner.Token(-1, JavaScanner.Token.Type.EOS); } } /** * Get next token * * @param i * position */ private JavaScanner.Token getToken(int i) { if (i < tokens.size()) { return tokens.get(i); } return new JavaScanner.Token(-1, JavaScanner.Token.Type.EOS); } private boolean match(JavaScanner.Token.Type type) { return type == currentToken.getType(); } private JavaScanner.Token.Type getTokenType() { return currentToken.getType(); } private int getTokenLineNumber() { return currentToken.getLineNumber(); } private String getTokenValue() { return currentToken.getValue(); } public String getFunction(int lineNumber) { String function = this.functions.get(lineNumber); if (function != null) { return function; } return "unknown"; } public int getFunctionCount() { return this.functionCount; } public int getLineCount() { return this.scanner.getLineCount(); } /** * Parses Java file * * @throws IOException */ public boolean parse(String filename, StringBuffer functionMetrics) throws IOException { initialize(filename); this.functionMetrics = functionMetrics; String packageName = ""; while (!match(JavaScanner.Token.Type.EOS)) { // match package if (match(JavaScanner.Token.Type.PACKAGE)) { nextToken(); packageName = parseIdentifier(); System.out.println("package='" + packageName + "'"); } // match class while (match(JavaScanner.Token.Type.CLASS)) { nextToken(); String classIdent = parseIdentifier(); if (this.className.length() > 0) { classIdent = this.className + '.' + classIdent; } else if (packageName.length() > 0) { classIdent = packageName + '.' + classIdent; } className = classIdent; // System.out.println("class='" + className + "'"); } // attempt to match a method if (match(JavaScanner.Token.Type.IDENT)) { String methodName = parseIdentifier(); if (match(JavaScanner.Token.Type.OPRN)) { nextToken(); StringBuffer buffer = new StringBuffer(); buffer.append('('); boolean match = true; do { if (matchType(buffer)) { if (match(JavaScanner.Token.Type.IDENT)) { nextToken(); } else { match = false; } } else if(!match(JavaScanner.Token.Type.CPRN)) { match = false; } } while (match(JavaScanner.Token.Type.COMMA) && match); buffer.append(')'); nextToken(); // verify we've matched a method (not a method call) if (match(JavaScanner.Token.Type.OCBR) && match) { // System.out.println("\tmethod='" + methodName+ buffer.toString() + "'"); this.functionCount++; // add function entry this.functionMetrics.append("<fnmetric><file>"); this.functionMetrics.append(this.filename); this.functionMetrics.append("</file><fnmet>"); final String fullMethodName = className + "." + methodName; this.functionMetrics.append(fullMethodName); this.functionMetrics.append("</fnmet></fnmetric>"); int start = currentToken.getLineNumber(); // find method end via curly braces int state = -1; do { nextToken(); if (currentToken.getLineNumber() != start) { functions.put(start, fullMethodName); start = currentToken.getLineNumber(); } if (match(JavaScanner.Token.Type.OCBR)) { if (state < 0) { state = 1; } else { state++; } } if (match(JavaScanner.Token.Type.CCBR)) { state--; } } while (!match(JavaScanner.Token.Type.EOS) && state != 0); functions.put(start, methodName); } } } // default update nextToken(); } return true; } private boolean matchType() { return matchType(null); } private boolean matchType(StringBuffer buffer) { boolean isType = false; switch (getTokenType()) { case VOID: if (buffer != null) { buffer.append("void"); } nextToken(); isType = true; break; case BYTE: if (buffer != null) { buffer.append("byte"); } nextToken(); isType = true; break; case SHORT: if (buffer != null) { buffer.append("short"); } nextToken(); isType = true; break; case CHAR: if (buffer != null) { buffer.append("char"); } nextToken(); isType = true; break; case INT: if (buffer != null) { buffer.append("int"); } nextToken(); isType = true; break; case LONG: if (buffer != null) { buffer.append("long"); } nextToken(); isType = true; break; case FLOAT: if (buffer != null) { buffer.append("float"); } nextToken(); isType = true; break; case DOUBLE: if (buffer != null) { buffer.append("double"); } nextToken(); isType = true; break; case BOOLEAN: if (buffer != null) { buffer.append("boolean"); } nextToken(); isType = true; break; case IDENT: { if (buffer != null) { buffer.append(currentToken.getValue()); } parseIdentifier(); isType = true; } break; } if (isType) { matchGeneric(buffer); matchArray(buffer); if (match(JavaScanner.Token.Type.DOT)) { nextToken(); if (match(JavaScanner.Token.Type.DOT)) { nextToken(); if (match(JavaScanner.Token.Type.DOT)) { nextToken(); buffer.append("..."); } } } } return isType; } private boolean matchGeneric(StringBuffer buffer) { if (match(JavaScanner.Token.Type.LESS)) { if (buffer != null) { buffer.append('<'); } nextToken(); if (matchType(buffer)) { if (match(JavaScanner.Token.Type.GTR)) { if (buffer != null) { buffer.append('>'); } nextToken(); return true; } } } return false; } private boolean matchArray(StringBuffer buffer) { if (match(JavaScanner.Token.Type.OBR)) { while (match(JavaScanner.Token.Type.OBR)) { if (buffer != null) { buffer.append('['); } nextToken(); if (match(JavaScanner.Token.Type.CBR)) { if (buffer != null) { buffer.append(']'); } nextToken(); } } return true; } return false; } private String parseIdentifier() { StringBuffer ident = new StringBuffer(); boolean extend = true; while (match(JavaScanner.Token.Type.IDENT) && extend) { ident.append(currentToken.getValue()); nextToken(); if (match(JavaScanner.Token.Type.DOT) && getToken(index + 1).getType() != JavaScanner.Token.Type.DOT) { ident.append('.'); nextToken(); extend = true; } else { extend = false; } } return ident.toString(); } }