001/* 002 * Cobertura - http://cobertura.sourceforge.net/ 003 * 004 * Copyright (C) 2005 Mark Doliner 005 * 006 * Cobertura is free software; you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published 008 * by the Free Software Foundation; either version 2 of the License, 009 * or (at your option) any later version. 010 * 011 * Cobertura is distributed in the hope that it will be useful, but 012 * WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Cobertura; if not, write to the Free Software 018 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 019 * USA 020 */ 021 022package net.sourceforge.cobertura.reporting.html; 023 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.HashSet; 027 028public class JavaToHtml 029{ 030 031 // Could use a J2SE 5.0 enum instead of this. 032 public abstract static class State 033 { 034 public final static int COMMENT_JAVADOC = 0; 035 public final static int COMMENT_MULTI = 1; 036 public final static int COMMENT_SINGLE = 2; 037 public final static int DEFAULT = 3; 038 public final static int KEYWORD = 4; 039 public final static int IMPORT_NAME = 5; 040 public final static int PACKAGE_NAME = 6; 041 public final static int QUOTE_DOUBLE = 8; 042 public final static int QUOTE_SINGLE = 9; 043 } 044 045 // TODO: Set a style for JavaDoc tags 046 //private static final Collection javaJavaDocTags; 047 private static final Collection javaKeywords; 048 private static final Collection javaPrimitiveLiterals; 049 private static final Collection javaPrimitiveTypes; 050 051 static 052 { 053 // TODO: Probably need to add anything new in J2SE 5.0 054 //final String javaJavaDocTagsArray[] = { "see", "author", "version", "param", "return", "exception", 055 // "deprecated", "throws", "link", "since", "serial", "serialField", "serialData", "beaninfo" }; 056 final String[] javaKeywordsArray = { "abstract", "assert", "break", 057 "case", "catch", "class", "const", "continue", "default", 058 "do", "else", "extends", "final", "finally", "for", "goto", 059 "if", "interface", "implements", "import", "instanceof", 060 "native", "new", "package", "private", "protected", "public", 061 "return", "static", "strictfp", "super", "switch", 062 "synchronized", "this", "throw", "throws", "transient", 063 "try", "volatile", "while" }; 064 final String javaPrimitiveTypesArray[] = { "boolean", "byte", "char", 065 "double", "float", "int", "long", "short", "void" }; 066 final String javaPrimitiveLiteralsArray[] = { "false", "null", "true" }; 067 068 //javaJavaDocTags = new HashSet(Arrays.asList(javaJavaDocTagsArray)); 069 javaKeywords = new HashSet(Arrays.asList(javaKeywordsArray)); 070 javaPrimitiveTypes = new HashSet(Arrays 071 .asList(javaPrimitiveTypesArray)); 072 javaPrimitiveLiterals = new HashSet(Arrays 073 .asList(javaPrimitiveLiteralsArray)); 074 } 075 076 private int state = State.DEFAULT; 077 078 private static String escapeEntity(final char character) 079 { 080 if (character == '&') 081 return "&"; 082 else if (character == '<') 083 return "<"; 084 else if (character == '>') 085 return ">"; 086 else if (character == '\t') 087 return " "; 088 else 089 return new Character(character).toString(); 090 } 091 092 /** 093 * Add HTML colorization to a block of Java code. 094 * 095 * @param text The block of Java code. 096 * @return The same block of Java code with added span tags. 097 * Newlines are preserved. 098 */ 099 public String process(final String text) 100 { 101 if (text == null) 102 throw new IllegalArgumentException("\"text\" can not be null."); 103 104 StringBuffer ret = new StringBuffer(); 105 106 // This look is really complicated because it preserves all 107 // combinations of \r, \n, \r\n, and \n\r 108 int begin, end, nextCR; 109 begin = 0; 110 end = text.indexOf('\n', begin); 111 nextCR = text.indexOf('\r', begin); 112 if ((nextCR != -1) && ((end == -1) || (nextCR < end))) 113 end = nextCR; 114 while (end != -1) 115 { 116 ret.append(processLine(text.substring(begin, end)) + "<br/>"); 117 118 if ((end + 1 < text.length()) 119 && ((text.charAt(end + 1) == '\n') || (text 120 .charAt(end + 1) == '\r'))) 121 { 122 ret.append(text.substring(end, end + 1)); 123 begin = end + 2; 124 } 125 else 126 { 127 ret.append(text.charAt(end)); 128 begin = end + 1; 129 } 130 131 end = text.indexOf('\n', begin); 132 nextCR = text.indexOf('\r', begin); 133 if ((nextCR != -1) && ((end == -1) || (nextCR < end))) 134 end = nextCR; 135 } 136 ret.append(processLine(text.substring(begin))); 137 138 return ret.toString(); 139 } 140 141 /** 142 * Add HTML colorization to a single line of Java code. 143 * 144 * @param line One line of Java code. 145 * @return The same line of Java code with added span tags. 146 */ 147 private String processLine(final String line) 148 { 149 if (line == null) 150 throw new IllegalArgumentException("\"line\" can not be null."); 151 if ((line.indexOf('\n') != -1) || (line.indexOf('\r') != -1)) 152 throw new IllegalArgumentException( 153 "\"line\" can not contain newline or carriage return characters."); 154 155 StringBuffer ret = new StringBuffer(); 156 int currentIndex = 0; 157 158 while (currentIndex != line.length()) 159 { 160 if (state == State.DEFAULT) 161 { 162 if ((currentIndex + 2 < line.length()) 163 && line.substring(currentIndex, currentIndex + 3) 164 .equals("/**")) 165 { 166 state = State.COMMENT_JAVADOC; 167 168 } 169 else if ((currentIndex + 1 < line.length()) 170 && line.substring(currentIndex, currentIndex + 2) 171 .equals("/*")) 172 { 173 state = State.COMMENT_MULTI; 174 175 } 176 else if ((currentIndex + 1 < line.length()) 177 && (line.substring(currentIndex, currentIndex + 2) 178 .equals("//"))) 179 { 180 state = State.COMMENT_SINGLE; 181 182 } 183 else if (Character.isJavaIdentifierStart(line 184 .charAt(currentIndex))) 185 { 186 state = State.KEYWORD; 187 188 } 189 else if (line.charAt(currentIndex) == '\'') 190 { 191 state = State.QUOTE_SINGLE; 192 193 } 194 else if (line.charAt(currentIndex) == '"') 195 { 196 state = State.QUOTE_DOUBLE; 197 198 } 199 else 200 { 201 // Default: No highlighting. 202 ret.append(escapeEntity(line.charAt(currentIndex++))); 203 } 204 } // End of State.DEFAULT 205 206 else if ((state == State.COMMENT_MULTI) 207 || (state == State.COMMENT_JAVADOC)) 208 { 209 // Print everything from the current character until the 210 // closing */ No exceptions. 211 ret.append("<span class=\"comment\">"); 212 while ((currentIndex != line.length()) 213 && !((currentIndex + 1 < line.length()) && (line 214 .substring(currentIndex, currentIndex + 2) 215 .equals("*/")))) 216 { 217 ret.append(escapeEntity(line.charAt(currentIndex++))); 218 } 219 if (currentIndex == line.length()) 220 { 221 ret.append("</span>"); 222 } 223 else 224 { 225 ret.append("*/</span>"); 226 state = State.DEFAULT; 227 currentIndex += 2; 228 } 229 } // End of State.COMMENT_MULTI 230 231 else if (state == State.COMMENT_SINGLE) 232 { 233 // Print everything from the current character until the 234 // end of the line 235 ret.append("<span class=\"comment\">"); 236 while (currentIndex != line.length()) 237 { 238 ret.append(escapeEntity(line.charAt(currentIndex++))); 239 } 240 ret.append("</span>"); 241 state = State.DEFAULT; 242 243 } // End of State.COMMENT_SINGLE 244 245 else if (state == State.KEYWORD) 246 { 247 StringBuffer tmp = new StringBuffer(); 248 do 249 { 250 tmp.append(line.charAt(currentIndex++)); 251 } while ((currentIndex != line.length()) 252 && (Character.isJavaIdentifierPart(line 253 .charAt(currentIndex)))); 254 if (javaKeywords.contains(tmp.toString())) 255 ret.append("<span class=\"keyword\">" + tmp + "</span>"); 256 else if (javaPrimitiveLiterals.contains(tmp.toString())) 257 ret.append("<span class=\"keyword\">" + tmp + "</span>"); 258 else if (javaPrimitiveTypes.contains(tmp.toString())) 259 ret.append("<span class=\"keyword\">" + tmp + "</span>"); 260 else 261 ret.append(tmp); 262 if (tmp.toString().equals("import")) 263 state = State.IMPORT_NAME; 264 else if (tmp.toString().equals("package")) 265 state = State.PACKAGE_NAME; 266 else 267 state = State.DEFAULT; 268 } // End of State.KEYWORD 269 270 else if (state == State.IMPORT_NAME) 271 { 272 ret.append(escapeEntity(line.charAt(currentIndex++))); 273 state = State.DEFAULT; 274 } // End of State.IMPORT_NAME 275 276 else if (state == State.PACKAGE_NAME) 277 { 278 ret.append(escapeEntity(line.charAt(currentIndex++))); 279 state = State.DEFAULT; 280 } // End of State.PACKAGE_NAME 281 282 else if (state == State.QUOTE_DOUBLE) 283 { 284 // Print everything from the current character until the 285 // closing ", checking for \" 286 ret.append("<span class=\"string\">"); 287 do 288 { 289 ret.append(escapeEntity(line.charAt(currentIndex++))); 290 } while ((currentIndex != line.length()) 291 && (!(line.charAt(currentIndex) == '"') || ((line 292 .charAt(currentIndex - 1) == '\\') && (line 293 .charAt(currentIndex - 2) != '\\')))); 294 if (currentIndex == line.length()) 295 { 296 ret.append("</span>"); 297 } 298 else 299 { 300 ret.append("\"</span>"); 301 state = State.DEFAULT; 302 currentIndex++; 303 } 304 } // End of State.QUOTE_DOUBLE 305 306 else if (state == State.QUOTE_SINGLE) 307 { 308 // Print everything from the current character until the 309 // closing ', checking for \' 310 ret.append("<span class=\"string\">"); 311 do 312 { 313 ret.append(escapeEntity(line.charAt(currentIndex++))); 314 } while ((currentIndex != line.length()) 315 && (!(line.charAt(currentIndex) == '\'') || ((line 316 .charAt(currentIndex - 1) == '\\') && (line 317 .charAt(currentIndex - 2) != '\\')))); 318 if (currentIndex == line.length()) 319 { 320 ret.append("</span>"); 321 } 322 else 323 { 324 ret.append("\'</span>"); 325 state = State.DEFAULT; 326 currentIndex++; 327 } 328 } // End of State.QUOTE_SINGLE 329 330 else 331 { 332 // Default: No highlighting. 333 ret.append(escapeEntity(line.charAt(currentIndex++))); 334 } // End of unknown state 335 } 336 337 return ret.toString(); 338 } 339 340 /** 341 * Reset the state of this Java parser. Call this if you have 342 * been parsing one Java file and you want to begin parsing 343 * another Java file. 344 * 345 */ 346 public void reset() 347 { 348 state = State.DEFAULT; 349 } 350 351}