1 | /** |
2 | * CodeViewer.java |
3 | * |
4 | * Bill Lynch & Matt Tucker |
5 | * CoolServlets.com, October 1999 |
6 | * |
7 | * Please visit CoolServlets.com for high quality, open source Java servlets. |
8 | * |
9 | * Copyright (C) 1999 CoolServlets.com |
10 | * |
11 | * Any errors or suggested improvements to this class can be reported |
12 | * as instructed on Coolservlets.com. We hope you enjoy |
13 | * this program... your comments will encourage further development! |
14 | * |
15 | * This software is distributed under the terms of The BSD License. |
16 | * |
17 | * Redistribution and use in source and binary forms, with or without |
18 | * modification, are permitted provided that the following conditions are met: |
19 | * * Redistributions of source code must retain the above copyright notice, |
20 | * this list of conditions and the following disclaimer. |
21 | * * Redistributions in binary form must reproduce the above copyright notice, |
22 | * this list of conditions and the following disclaimer in the documentation |
23 | * and/or other materials provided with the distribution. |
24 | * Neither name of CoolServlets.com nor the names of its contributors may be |
25 | * used to endorse or promote products derived from this software without |
26 | * specific prior written permission. |
27 | * |
28 | * THIS SOFTWARE IS PROVIDED BY COOLSERVLETS.COM AND CONTRIBUTORS ``AS IS'' AND |
29 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
30 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
31 | * DISCLAIMED. IN NO EVENT SHALL COOLSERVLETS.COM OR CONTRIBUTORS BE LIABLE FOR |
32 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
33 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
34 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
35 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
36 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
37 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
38 | */ |
39 | |
40 | /* |
41 | * @(#)CodeViewer.java 1.6 02/06/13 |
42 | */ |
43 | |
44 | |
45 | import java.io.*; |
46 | import java.util.*; |
47 | |
48 | /** |
49 | * A class that syntax highlights Java code by turning it into html. |
50 | * |
51 | * <p> A <code>CodeViewer</code> object is created and then keeps state as |
52 | * lines are passed in. Each line passed in as java text, is returned as syntax |
53 | * highlighted html text. |
54 | * |
55 | * <p> Users of the class can set how the java code will be highlighted with |
56 | * setter methods. |
57 | * |
58 | * <p> Only valid java lines should be passed in since the object maintains |
59 | * state and may not handle illegal code gracefully. |
60 | * |
61 | * <p> The actual system is implemented as a series of filters that deal with |
62 | * specific portions of the java code. The filters are as follows: |
63 | * |
64 | * <pre> |
65 | * htmlFilter |
66 | * |__ |
67 | * multiLineCommentFilter |
68 | * |___ |
69 | * inlineCommentFilter |
70 | * |___ |
71 | * stringFilter |
72 | * |__ |
73 | * keywordFilter |
74 | * </pre> |
75 | * |
76 | * @version 1.6 06/13/02 |
77 | * @author Bill Lynch, Matt Tucker, CoolServlets.com |
78 | */ |
79 | public class CodeViewer { |
80 | |
81 | private static HashMap reservedWords = new HashMap(); // >= Java2 only (also, not thread-safe) |
82 | //private static Hashtable reservedWords = new Hashtable(); // < Java2 (thread-safe) |
83 | private boolean inMultiLineComment = false; |
84 | private String backgroundColor = "#ffffff"; |
85 | private String commentStart = "</font><font size=2 color=\"#0000aa\"><i>"; |
86 | private String commentEnd = "</font></i><font size=2 color=black>"; |
87 | private String stringStart = "</font><font size=2 color=\"#00bb00\">"; |
88 | private String stringEnd = "</font><font size=2 color=black>"; |
89 | private String reservedWordStart = "<b>"; |
90 | private String reservedWordEnd = "</b>"; |
91 | |
92 | static { |
93 | loadHash(); |
94 | } |
95 | |
96 | public CodeViewer() { |
97 | } |
98 | |
99 | public void setCommentStart(String commentStart) { |
100 | this.commentStart = commentStart; |
101 | } |
102 | public void setCommentEnd(String commentEnd) { |
103 | this.commentEnd = commentEnd; |
104 | } |
105 | public void setStringStart(String stringStart) { |
106 | this.stringStart = stringStart; |
107 | } |
108 | public void setStringEnd(String stringEnd) { |
109 | this.stringEnd = stringEnd; |
110 | } |
111 | public void setReservedWordStart(String reservedWordStart) { |
112 | this.reservedWordStart = reservedWordStart; |
113 | } |
114 | public void setReservedWordEnd(String reservedWordEnd) { |
115 | this.reservedWordEnd = reservedWordEnd; |
116 | } |
117 | |
118 | public String getCommentStart() { |
119 | return commentStart; |
120 | } |
121 | public String getCommentEnd() { |
122 | return commentEnd; |
123 | } |
124 | public String getStringStart() { |
125 | return stringStart; |
126 | } |
127 | public String getStringEnd() { |
128 | return stringEnd; |
129 | } |
130 | public String getReservedWordStart() { |
131 | return reservedWordStart; |
132 | } |
133 | public String getReservedWordEnd() { |
134 | return reservedWordEnd; |
135 | } |
136 | |
137 | /** |
138 | * Passes off each line to the first filter. |
139 | * @param line The line of Java code to be highlighted. |
140 | */ |
141 | public String syntaxHighlight( String line ) { |
142 | return htmlFilter(line); |
143 | } |
144 | |
145 | /* |
146 | * Filter html tags into more benign text. |
147 | */ |
148 | private String htmlFilter( String line ) { |
149 | if( line == null || line.equals("") ) { |
150 | return ""; |
151 | } |
152 | |
153 | // replace ampersands with HTML escape sequence for ampersand; |
154 | line = replace(line, "&", "&"); |
155 | |
156 | // replace the \\ with HTML escape sequences. fixes a problem when |
157 | // backslashes preceed quotes. |
158 | line = replace(line, "\\\\", "\\" ); |
159 | |
160 | // replace \" sequences with HTML escape sequences; |
161 | line = replace(line, "" + (char)92 + (char)34, "\""); |
162 | |
163 | // replace less-than signs which might be confused |
164 | // by HTML as tag angle-brackets; |
165 | line = replace(line, "<", "<"); |
166 | // replace greater-than signs which might be confused |
167 | // by HTML as tag angle-brackets; |
168 | line = replace(line, ">", ">"); |
169 | |
170 | return multiLineCommentFilter(line); |
171 | } |
172 | |
173 | /* |
174 | * Filter out multiLine comments. State is kept with a private boolean |
175 | * variable. |
176 | */ |
177 | private String multiLineCommentFilter(String line) { |
178 | if (line == null || line.equals("")) { |
179 | return ""; |
180 | } |
181 | StringBuffer buf = new StringBuffer(); |
182 | int index; |
183 | //First, check for the end of a multi-line comment. |
184 | if (inMultiLineComment && (index = line.indexOf("*/")) > -1 && !isInsideString(line,index)) { |
185 | inMultiLineComment = false; |
186 | buf.append(line.substring(0,index)); |
187 | buf.append("*/").append(commentEnd); |
188 | if (line.length() > index+2) { |
189 | buf.append(inlineCommentFilter(line.substring(index+2))); |
190 | } |
191 | return buf.toString(); |
192 | } |
193 | //If there was no end detected and we're currently in a multi-line |
194 | //comment, we don't want to do anymore work, so return line. |
195 | else if (inMultiLineComment) { |
196 | return line; |
197 | } |
198 | //We're not currently in a comment, so check to see if the start |
199 | //of a multi-line comment is in this line. |
200 | else if ((index = line.indexOf("/*")) > -1 && !isInsideString(line,index)) { |
201 | inMultiLineComment = true; |
202 | //Return result of other filters + everything after the start |
203 | //of the multiline comment. We need to pass the through the |
204 | //to the multiLineComment filter again in case the comment ends |
205 | //on the same line. |
206 | buf.append(inlineCommentFilter(line.substring(0,index))); |
207 | buf.append(commentStart).append("/*"); |
208 | buf.append(multiLineCommentFilter(line.substring(index+2))); |
209 | return buf.toString(); |
210 | } |
211 | //Otherwise, no useful multi-line comment information was found so |
212 | //pass the line down to the next filter for processesing. |
213 | else { |
214 | return inlineCommentFilter(line); |
215 | } |
216 | } |
217 | |
218 | /* |
219 | * Filter inline comments from a line and formats them properly. |
220 | */ |
221 | private String inlineCommentFilter(String line) { |
222 | if (line == null || line.equals("")) { |
223 | return ""; |
224 | } |
225 | StringBuffer buf = new StringBuffer(); |
226 | int index; |
227 | if ((index = line.indexOf("//")) > -1 && !isInsideString(line,index)) { |
228 | buf.append(stringFilter(line.substring(0,index))); |
229 | buf.append(commentStart); |
230 | buf.append(line.substring(index)); |
231 | buf.append(commentEnd); |
232 | } |
233 | else { |
234 | buf.append(stringFilter(line)); |
235 | } |
236 | return buf.toString(); |
237 | } |
238 | |
239 | /* |
240 | * Filters strings from a line of text and formats them properly. |
241 | */ |
242 | private String stringFilter(String line) { |
243 | if (line == null || line.equals("")) { |
244 | return ""; |
245 | } |
246 | StringBuffer buf = new StringBuffer(); |
247 | if (line.indexOf("\"") <= -1) { |
248 | return keywordFilter(line); |
249 | } |
250 | int start = 0; |
251 | int startStringIndex = -1; |
252 | int endStringIndex = -1; |
253 | int tempIndex; |
254 | //Keep moving through String characters until we want to stop... |
255 | while ((tempIndex = line.indexOf("\"")) > -1) { |
256 | //We found the beginning of a string |
257 | if (startStringIndex == -1) { |
258 | startStringIndex = 0; |
259 | buf.append( stringFilter(line.substring(start,tempIndex)) ); |
260 | buf.append(stringStart).append("\""); |
261 | line = line.substring(tempIndex+1); |
262 | } |
263 | //Must be at the end |
264 | else { |
265 | startStringIndex = -1; |
266 | endStringIndex = tempIndex; |
267 | buf.append(line.substring(0,endStringIndex+1)); |
268 | buf.append(stringEnd); |
269 | line = line.substring(endStringIndex+1); |
270 | } |
271 | } |
272 | |
273 | buf.append( keywordFilter(line) ); |
274 | |
275 | return buf.toString(); |
276 | } |
277 | |
278 | /* |
279 | * Filters keywords from a line of text and formats them properly. |
280 | */ |
281 | private String keywordFilter( String line ) { |
282 | if( line == null || line.equals("") ) { |
283 | return ""; |
284 | } |
285 | StringBuffer buf = new StringBuffer(); |
286 | HashMap usedReservedWords = new HashMap(); // >= Java2 only (not thread-safe) |
287 | //Hashtable usedReservedWords = new Hashtable(); // < Java2 (thread-safe) |
288 | int i=0, startAt=0; |
289 | char ch; |
290 | StringBuffer temp = new StringBuffer(); |
291 | while( i < line.length() ) { |
292 | temp.setLength(0); |
293 | ch = line.charAt(i); |
294 | startAt = i; |
295 | // 65-90, uppercase letters |
296 | // 97-122, lowercase letters |
297 | while( i<line.length() && ( ( ch >= 65 && ch <= 90 ) |
298 | || ( ch >= 97 && ch <= 122 ) ) ) { |
299 | temp.append(ch); |
300 | i++; |
301 | if( i < line.length() ) { |
302 | ch = line.charAt(i); |
303 | } |
304 | } |
305 | String tempString = temp.toString(); |
306 | if( reservedWords.containsKey(tempString) && !usedReservedWords.containsKey(tempString)) { |
307 | usedReservedWords.put(tempString,tempString); |
308 | line = replace( line, tempString, (reservedWordStart+tempString+reservedWordEnd) ); |
309 | i += (reservedWordStart.length() + reservedWordEnd.length()); |
310 | } |
311 | else { |
312 | i++; |
313 | } |
314 | } |
315 | buf.append(line); |
316 | return buf.toString(); |
317 | } |
318 | |
319 | /* |
320 | * All important replace method. Replaces all occurences of oldString in |
321 | * line with newString. |
322 | */ |
323 | private String replace( String line, String oldString, String newString ) { |
324 | int i=0; |
325 | while( ( i=line.indexOf( oldString, i ) ) >= 0 ) { |
326 | line = (new StringBuffer().append(line.substring(0,i)).append(newString).append(line.substring(i+oldString.length()))).toString(); |
327 | i += newString.length(); |
328 | } |
329 | return line; |
330 | } |
331 | |
332 | /* |
333 | * Checks to see if some position in a line is between String start and |
334 | * ending characters. Not yet used in code or fully working :) |
335 | */ |
336 | private boolean isInsideString(String line, int position) { |
337 | if (line.indexOf("\"") < 0) { |
338 | return false; |
339 | } |
340 | int index; |
341 | String left = line.substring(0,position); |
342 | String right = line.substring(position); |
343 | int leftCount = 0; |
344 | int rightCount = 0; |
345 | while ((index = left.indexOf("\"")) > -1) { |
346 | leftCount ++; |
347 | left = left.substring(index+1); |
348 | } |
349 | while ((index = right.indexOf("\"")) > -1) { |
350 | rightCount ++; |
351 | right = right.substring(index+1); |
352 | } |
353 | if (rightCount % 2 != 0 && leftCount % 2 != 0) { |
354 | return true; |
355 | } |
356 | else { |
357 | return false; |
358 | } |
359 | } |
360 | |
361 | /* |
362 | * Load Hashtable (or HashMap) with Java reserved words. |
363 | */ |
364 | private static void loadHash() { |
365 | reservedWords.put( "abstract", "abstract" ); |
366 | reservedWords.put( "do", "do" ); |
367 | reservedWords.put( "inner", "inner" ); |
368 | reservedWords.put( "public", "public" ); |
369 | reservedWords.put( "var", "var" ); |
370 | reservedWords.put( "boolean", "boolean" ); |
371 | reservedWords.put( "continue", "continue" ); |
372 | reservedWords.put( "int", "int" ); |
373 | reservedWords.put( "return", "return" ); |
374 | reservedWords.put( "void", "void" ); |
375 | reservedWords.put( "break", "break" ); |
376 | reservedWords.put( "else", "else" ); |
377 | reservedWords.put( "interface", "interface" ); |
378 | reservedWords.put( "short", "short" ); |
379 | reservedWords.put( "volatile", "volatile" ); |
380 | reservedWords.put( "byvalue", "byvalue" ); |
381 | reservedWords.put( "extends", "extends" ); |
382 | reservedWords.put( "long", "long" ); |
383 | reservedWords.put( "static", "static" ); |
384 | reservedWords.put( "while", "while" ); |
385 | reservedWords.put( "case", "case" ); |
386 | reservedWords.put( "final", "final" ); |
387 | reservedWords.put( "naive", "naive" ); |
388 | reservedWords.put( "super", "super" ); |
389 | reservedWords.put( "transient", "transient" ); |
390 | reservedWords.put( "cast", "cast" ); |
391 | reservedWords.put( "float", "float" ); |
392 | reservedWords.put( "new", "new" ); |
393 | reservedWords.put( "rest", "rest" ); |
394 | reservedWords.put( "catch", "catch" ); |
395 | reservedWords.put( "for", "for" ); |
396 | reservedWords.put( "null", "null" ); |
397 | reservedWords.put( "synchronized", "synchronized" ); |
398 | reservedWords.put( "char", "char" ); |
399 | reservedWords.put( "finally", "finally" ); |
400 | reservedWords.put( "operator", "operator" ); |
401 | reservedWords.put( "this", "this" ); |
402 | reservedWords.put( "class", "class" ); |
403 | reservedWords.put( "generic", "generic" ); |
404 | reservedWords.put( "outer", "outer" ); |
405 | reservedWords.put( "switch", "switch" ); |
406 | reservedWords.put( "const", "const" ); |
407 | reservedWords.put( "goto", "goto" ); |
408 | reservedWords.put( "package", "package" ); |
409 | reservedWords.put( "throw", "throw" ); |
410 | reservedWords.put( "double", "double" ); |
411 | reservedWords.put( "if", "if" ); |
412 | reservedWords.put( "private", "private" ); |
413 | reservedWords.put( "true", "true" ); |
414 | reservedWords.put( "default", "default" ); |
415 | reservedWords.put( "import", "import" ); |
416 | reservedWords.put( "protected", "protected" ); |
417 | reservedWords.put( "try", "try" ); |
418 | } |
419 | } |