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
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 // 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 }