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    }