/******************************************************************************* * Copyright 2005-2006, CHISEL Group, University of Victoria, Victoria, BC, Canada. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * The Chisel Group, University of Victoria *******************************************************************************/ package ca.uvic.cs.tagsea.statistics.svn.jobs; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; import java.util.LinkedList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.runtime.IProgressMonitor; /** * A simple scanner that searches throught the comments in source * code to find out how many comments have tags in them, and how * many do not. * @author Del Myers * */ public class SimpleJavaCodeScanner { public static class Stats { public final Comment[] NONTAGS; public final Comment[] TAGS; public final Comment[] TASKS; private Stats(Comment[] nontags, Comment[] tags, Comment[] tasks) { this.NONTAGS = nontags; this.TAGS = tags; this.TASKS = tasks; } } //ready to start private static final int START = 0; //inside a multiline comment private static final int MULTILINE = 1; //inside a singleline comment private static final int LINE = 2; //found a slash (possible start of a comment) private static final int SLASH = 3; //found a star (possible end of a comment) private static final int STAR = 4; //just after a */ comment is finished private static final int STARSLASH = 5; //no comment private static final int SCANNING = 6; //finished private static final int FINISHED = 7; private static final int NONE = 10; private static final int TAG = 11; private static final int TASK = 12; private static final int BOTH = 13; private static final String TAGS = "@tag"; private static final String TODO = "TODO"; private static final String XXX = "XXX"; private static final String FIXME = "FIXME"; /** * Scans the given source code for comments with tags and without. * @param code the code to scan * @return the gathered statistics, or null if the process has been cancelled. * @throws IOException */ public static Stats scan(String code, IProgressMonitor monitor) throws IOException { StringReader reader = new StringReader(code); //pass a state variable around so that this call can be thread-safe //and not state specific. int state = SCANNING; int cstate = NONE; BufferedReader lineReader = new BufferedReader(reader); Pattern packPatt = Pattern.compile(".*package\\s+([a-zA-Z0-9\\.]+).*;.*"); Pattern clazzPatt = Pattern.compile(".*class\\s+(\\w+).*"); String pack = null; String clazz = null; String s; StringBuffer currentComment = new StringBuffer(); List commentList = new LinkedList(); List tagList = new LinkedList(); List taskList = new LinkedList(); monitor.beginTask("Parsing java source...", code.length()); monitor.subTask("Parsing java source..."); int line = 0; int commentStart = 1; Matcher matcher; while ((s= lineReader.readLine()) != null) { matcher = packPatt.matcher(s); if (matcher.matches()) { pack = matcher.group(1); } String s2 = new String(s); matcher = clazzPatt.matcher(s2); if (matcher.matches()) { clazz = matcher.group(1); } if (clazz != null && pack != null) break; } if (clazz == null) clazz = ""; if (pack == null) pack = "default"; reader.close(); reader = new StringReader(code); lineReader = new BufferedReader(reader); while ((s = lineReader.readLine()) != null) { line++; //scan the line for the beginning of a comment. char[] characters = s.toCharArray(); for (int i = 0; i < characters.length; i++) { if (characters[i] == '/') { switch (state) { case SLASH: state = LINE; commentStart = line; break; case STAR: state = STARSLASH; String comment = currentComment.toString(); Comment c = new Comment(comment, commentStart, line, pack, clazz); currentComment.append(characters[i]); switch (cstate) { case TAG: tagList.add(c); break; case BOTH: tagList.add(c); case TASK: taskList.add(c); break; default: commentList.add(c); break; } cstate = NONE; currentComment = new StringBuffer(); break; case MULTILINE: case LINE: currentComment.append(characters[i]); break; case SCANNING: case STARSLASH: state = SLASH; } } else if (characters[i] == '*') { switch (state) { case SLASH: state = MULTILINE; commentStart = line; break; case LINE: currentComment.append(characters[i]); break; case MULTILINE: state = STAR; break; case STARSLASH: state = SCANNING; break; } } else if (state == SLASH || state == STARSLASH) { state = SCANNING; } else { if (state == STAR) { currentComment.append('*'); state = MULTILINE; } if (state == LINE || state == MULTILINE) { String comment = currentComment.toString(); if (Character.isWhitespace(characters[i]) && cstate == NONE) { if (isTag(comment)) { cstate = TAG; } if (isTask(comment)) { if (cstate == TAG) cstate = BOTH; else cstate = TASK; } } // save the character. currentComment.append(characters[i]); if (characters[i] == '\\') { //append another \ so that there is no ambiguity with new lines currentComment.append('\\'); } } } monitor.worked(1); if (monitor.isCanceled()) { return null; } } if (state == LINE) { String comment = currentComment.toString(); Comment c = new Comment(comment, commentStart, line, pack, clazz); switch (cstate) { case TAG: tagList.add(c); break; case BOTH: tagList.add(c); case TASK: taskList.add(c); break; default: //check for when the info comes at the end if (isTag(comment)) tagList.add(c); if (isTask(comment)) taskList.add(c); else commentList.add(c); break; } currentComment = new StringBuffer(); cstate = NONE; state = SCANNING; } else if (state == MULTILINE) { currentComment.append("\\ "); //append a newline indicator. } } Stats st = new Stats( (Comment[])commentList.toArray(new Comment[0]), (Comment[])tagList.toArray(new Comment[0]), (Comment[])taskList.toArray(new Comment[0]) ); monitor.done(); return st; } private static boolean isTag(String comment) { if (comment.endsWith(TAGS)) { if (comment.length() == TAGS.length()) { return true; } else { char c = comment.charAt(comment.length()-TAGS.length()-1); if (Character.isWhitespace(c)) { return true; } } } return false; } private static boolean isTask(String comment) { int length = -1; if (comment.endsWith(FIXME)) { length = FIXME.length(); } else if (comment.endsWith(TODO)) { length = TODO.length(); } else if (comment.endsWith(XXX)) { length = XXX.length(); } if (length > 0) { if (comment.length() == length) { return true; } else { char c = comment.charAt(comment.length()-length-1); if (Character.isWhitespace(c)) { return true; } } } return false; } }