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 "&lt;";
084                    else if (character == '>')
085                            return "&gt;";
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    }