1 /*
2  * Copyright 2008-2019 by Emeric Vernat
3  *
4  *     This file is part of Java Melody.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */

18 package net.bull.javamelody.internal.web.html;
19
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.LinkedHashMap;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.regex.Pattern;
26
27 /**
28  * Transformation de code source Java en HTML.
29  * @author Emeric Vernat
30  */

31 final class JavaHTMLizer {
32     private static final String BR = "<br />\n";
33
34     private static final List<Pattern> RESERVED_WORDS_PATTERNS = createReservedWordPatterns(
35             Arrays.asList("class""finally""return""new""public""static""final""void",
36                     "synchronized""interface""enum""private""protected""import",
37                     "package""try""catch""for""while""do""if""else""switch""case",
38                     "default""goto""byte""short""int""long""float""double""char",
39                     "boolean""extends""implements""super""this""true""false""null",
40                     "abstract""break""continue""assert""instanceof""native""strictfp",
41                     "throws""throw""transient""volatile"));
42
43     private static final Map<Character, String> ESCAPE_MAPS = createEscapeMaps();
44
45     private static final Pattern MULTILINE_COMMENT_PATTERN = Pattern.compile("/\\*(.*?)\\*/",
46             Pattern.DOTALL);
47
48     private static final Pattern SINGLELINE_COMMENT_PATTERN = Pattern.compile("//(.*?)<br />",
49             Pattern.DOTALL);
50
51     private static final Pattern STRING_PATTERN = Pattern.compile("&quot;(.*?)&quot;");
52
53     private JavaHTMLizer() {
54         super();
55     }
56
57     static String htmlize(final String javaSource) {
58         String result = "-" + javaSource;
59         result = htmlEscape(result);
60         result = formatReservedWords(result);
61         result = formatComments(result);
62         result = formatStrings(result);
63         return result.substring(1);
64     }
65
66     static String htmlizeFull(final String javaSource) {
67         final String result = htmlize(javaSource);
68         final String start = "<html><body><style>" + "code { font-size: 12px; } "
69                 + "code .string { color: blue; } "
70                 + "code .comment { font-style: italic; color: green; } "
71                 + "code .keyword { font-weight: bold; color: purple; } "
72                 + "code .comment .keyword { color: green; font-weight: normal; } "
73                 + "code .comment .string { color: green; } " + "</style><code>";
74         final String end = "</code></body></html>";
75         return start + result + end;
76     }
77
78     static String addLineNumbers(final String javaSource) {
79         final StringBuilder sb = new StringBuilder(javaSource);
80         sb.insert(0, "<a name=1 href=#1>1</a> ");
81         int line = 2;
82         int index = sb.indexOf(BR);
83         while (index != -1) {
84             final int offset = index + BR.length();
85             final String strLine = Integer.toString(line);
86             sb.insert(offset, "</a> ");
87             sb.insert(offset, strLine);
88             sb.insert(offset, '>');
89             sb.insert(offset, strLine);
90             sb.insert(offset, " href=#");
91             sb.insert(offset, strLine);
92             sb.insert(offset, "<a name=");
93             index = sb.indexOf(BR, index + 1);
94             line++;
95         }
96         return sb.toString();
97     }
98
99     private static List<Pattern> createReservedWordPatterns(final List<String> reservedWords) {
100         final List<Pattern> result = new ArrayList<>(reservedWords.size());
101         for (final String reservedWord : reservedWords) {
102             result.add(Pattern.compile("(\\W)(" + reservedWord + ")(\\W)"));
103         }
104         return result;
105     }
106
107     private static Map<Character, String> createEscapeMaps() {
108         final Map<Character, String> escapeMaps = new LinkedHashMap<>();
109         escapeMaps.put(' ', "&nbsp;");
110         escapeMaps.put('\t', "&nbsp;&nbsp;&nbsp;&nbsp;");
111         escapeMaps.put('<', "&lt;");
112         escapeMaps.put('>', "&gt;");
113         escapeMaps.put('\"', "&quot;");
114         escapeMaps.put('&', "&amp;");
115         escapeMaps.put('\'', "&#39;");
116         escapeMaps.put('\n', BR);
117         return escapeMaps;
118     }
119
120     private static String escapeChar(final char c) {
121         return ESCAPE_MAPS.get(c);
122     }
123
124     private static String htmlEscape(final String text) {
125         final StringBuilder sb = new StringBuilder();
126         for (int i = 0; i < text.length(); i++) {
127             final char c = text.charAt(i);
128             final String escapedOrNull = escapeChar(c);
129             if (escapedOrNull == null) {
130                 sb.append(c);
131             } else {
132                 sb.append(escapedOrNull);
133             }
134         }
135         return sb.toString();
136     }
137
138     private static String formatReservedWords(final String text) {
139         String result = text;
140         for (final Pattern reservedWordPattern : RESERVED_WORDS_PATTERNS) {
141             result = reservedWordPattern.matcher(result)
142                     .replaceAll("$1<span class=\"keyword\">$2</span>$3");
143         }
144         return result;
145     }
146
147     private static String formatComments(final String text) {
148         String result = text;
149         result = MULTILINE_COMMENT_PATTERN.matcher(result)
150                 .replaceAll("<span class=\"comment\">/*$1*/</span>");
151         result = SINGLELINE_COMMENT_PATTERN.matcher(result)
152                 .replaceAll("<span class=\"comment\">//$1</span><br />");
153         return result;
154     }
155
156     private static String formatStrings(final String text) {
157         return STRING_PATTERN.matcher(text)
158                 .replaceAll("<span class=\"string\">&quot;$1&quot;</span>");
159     }
160 }
161