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
022package net.sourceforge.cobertura.reporting.html;
023
024import java.util.Arrays;
025import java.util.Collection;
026import java.util.HashSet;
027
028public 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}