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