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 }