001: /*
002: * NEMESIS-FORUM.
003: * Copyright (C) 2002 David Laurent(lithium2@free.fr). All rights reserved.
004: *
005: * Copyright (c) 2000 The Apache Software Foundation. All rights reserved.
006: *
007: * Copyright (C) 2001 Yasna.com. All rights reserved.
008: *
009: * Copyright (C) 2000 CoolServlets.com. All rights reserved.
010: *
011: * NEMESIS-FORUM. is free software; you can redistribute it and/or
012: * modify it under the terms of the Apache Software License, Version 1.1,
013: * or (at your option) any later version.
014: *
015: * NEMESIS-FORUM core framework, NEMESIS-FORUM backoffice, NEMESIS-FORUM frontoffice
016: * application are parts of NEMESIS-FORUM and are distributed under
017: * same terms of licence.
018: *
019: *
020: * NEMESIS-FORUM includes software developed by the Apache Software Foundation (http://www.apache.org/)
021: * and software developed by CoolServlets.com (http://www.coolservlets.com).
022: * and software developed by Yasna.com (http://www.yasna.com).
023: *
024: */
025:
026: package org.nemesis.forum.util.tool;
027:
028: import java.util.Hashtable;
029:
030: /**
031: * A class that syntax highlights Java code into html.
032: * <p>
033: * A CodeViewer object is created and then keeps state as
034: * lines are passed in. Each line passed in as java text, is returned as syntax
035: * highlighted html text.
036: * <p>
037: * Users of the class can set how the java code will be highlighted with
038: * setter methods.
039: * <p>
040: * Only valid java lines should be passed in since the object maintains
041: * state and may not handle illegal code gracefully.
042: * <p>
043: * The actual system is implemented as a series of filters that deal with
044: * specific portions of the java code. The filters are as follows:
045: * <p>
046: * <pre>
047: * htmlFilter
048: * |__
049: * multiLineCommentFilter
050: * |___
051: * inlineCommentFilter
052: * |___
053: * stringFilter
054: * |__
055: * keywordFilter
056: * </pre>
057: *
058: */
059: public class CodeViewer {
060:
061: //private static HashMap reservedWords = new HashMap(80); // >= Java2 only (also, not thread-safe)
062: private static Hashtable reservedWords = new Hashtable(80); // < Java2 (thread-safe)
063: private boolean inMultiLineComment = false;
064: private String backgroundColor = "#ffffff";
065: private String commentStart = "<font color=\"#aa0000\"><i>";
066: private String commentEnd = "</font></i>";
067: private String stringStart = "<font color=\"#000099\">";
068: private String stringEnd = "</font>";
069: private String reservedWordStart = "<b>";
070: private String reservedWordEnd = "</b>";
071:
072: /**
073: * Load all keywords at class loading time.
074: */
075: static {
076: loadKeywords();
077: }
078:
079: /**
080: * Gets the html for the start of a comment block.
081: */
082: public String getCommentStart() {
083: return commentStart;
084: }
085:
086: /**
087: * Sets the html for the start of a comment block.
088: */
089: public void setCommentStart(String commentStart) {
090: this .commentStart = commentStart;
091: }
092:
093: /**
094: * Gets the html for the end of a comment block.
095: */
096: public String getCommentEnd() {
097: return commentEnd;
098: }
099:
100: /**
101: * Sets the html for the end of a comment block.
102: */
103: public void setCommentEnd(String commentEnd) {
104: this .commentEnd = commentEnd;
105: }
106:
107: /**
108: * Gets the html for the start of a String.
109: */
110: public String getStringStart() {
111: return stringStart;
112: }
113:
114: /**
115: * Sets the html for the start of a String.
116: */
117: public void setStringStart(String stringStart) {
118: this .stringStart = stringStart;
119: }
120:
121: /**
122: * Gets the html for the end of a String.
123: */
124: public String getStringEnd() {
125: return stringEnd;
126: }
127:
128: /**
129: * Sets the html for the end of a String.
130: */
131: public void setStringEnd(String stringEnd) {
132: this .stringEnd = stringEnd;
133: }
134:
135: /**
136: * Gets the html for the start of a reserved word.
137: */
138: public String getReservedWordStart() {
139: return reservedWordStart;
140: }
141:
142: /**
143: * Sets the html for the start of a reserved word.
144: */
145: public void setReservedWordStart(String reservedWordStart) {
146: this .reservedWordStart = reservedWordStart;
147: }
148:
149: /**
150: * Gets the html for the end of a reserved word.
151: */
152: public String getReservedWordEnd() {
153: return reservedWordEnd;
154: }
155:
156: /**
157: * Sets the html for the end of a reserved word.
158: */
159: public void setReservedWordEnd(String reservedWordEnd) {
160: this .reservedWordEnd = reservedWordEnd;
161: }
162:
163: /**
164: * Passes off each line to the first filter.
165: * @param line The line of Java code to be highlighted.
166: */
167: public String syntaxHighlight(String line) {
168: return htmlFilter(line);
169: }
170:
171: /*
172: * Filter html tags that appear in the java source into more benign text
173: * that won't disrupt the output.
174: */
175: private String htmlFilter(String line) {
176: if (line == null || line.equals("")) {
177: return "";
178: }
179: // replace ampersands with HTML escape sequence for ampersand;
180: line = replace(line, "&", "&");
181:
182: // replace \" sequences with HTML escape sequences;
183: line = replace(line, "\\\"", "\"");
184:
185: // replace the \\ with HTML escape sequences. fixes a problem when
186: // backslashes preceed quotes.
187: line = replace(line, "\\\\", "\\");
188:
189: // replace less-than signs which might be confused
190: // by HTML as tag angle-brackets;
191: line = replace(line, "<", "<");
192: // replace greater-than signs which might be confused
193: // by HTML as tag angle-brackets;
194: line = replace(line, ">", ">");
195:
196: return multiLineCommentFilter(line);
197: }
198:
199: /*
200: * Filter out multiLine comments. State is kept with a private boolean
201: * variable.
202: */
203: private String multiLineCommentFilter(String line) {
204: if (line == null || line.equals("")) {
205: return "";
206: }
207: StringBuffer buf = new StringBuffer();
208: int index;
209: //First, check for the end of a multi-line comment.
210: if (inMultiLineComment && (index = line.indexOf("*/")) > -1
211: && !isInsideString(line, index)) {
212: inMultiLineComment = false;
213: buf.append(line.substring(0, index));
214: buf.append("*/").append(commentEnd);
215: if (line.length() > index + 2) {
216: buf.append(inlineCommentFilter(line
217: .substring(index + 2)));
218: }
219: return buf.toString();
220: }
221: //If there was no end detected and we're currently in a multi-line
222: //comment, we don't want to do anymore work, so return line.
223: else if (inMultiLineComment) {
224: return line;
225: }
226: //We're not currently in a comment, so check to see if the start
227: //of a multi-line comment is in this line.
228: else if ((index = line.indexOf("/*")) > -1
229: && !isInsideString(line, index)) {
230: inMultiLineComment = true;
231: //Return result of other filters + everything after the start
232: //of the multiline comment. We need to pass the through the
233: //to the multiLineComment filter again in case the comment ends
234: //on the same line.
235: buf.append(inlineCommentFilter(line.substring(0, index)));
236: buf.append(commentStart).append("/*");
237: buf
238: .append(multiLineCommentFilter(line
239: .substring(index + 2)));
240: return buf.toString();
241: }
242: //Otherwise, no useful multi-line comment information was found so
243: //pass the line down to the next filter for processesing.
244: else {
245: return inlineCommentFilter(line);
246: }
247: }
248:
249: /*
250: * Filter inline comments from a line and formats them properly.
251: */
252: private String inlineCommentFilter(String line) {
253: if (line == null || line.equals("")) {
254: return "";
255: }
256: StringBuffer buf = new StringBuffer();
257: int index;
258: if ((index = line.indexOf("//")) > -1
259: && !isInsideString(line, index)) {
260: buf.append(stringFilter(line.substring(0, index)));
261: buf.append(commentStart);
262: buf.append(line.substring(index));
263: buf.append(commentEnd);
264: } else {
265: buf.append(stringFilter(line));
266: }
267: return buf.toString();
268: }
269:
270: /*
271: * Filters strings from a line of text and formats them properly.
272: */
273: private String stringFilter(String line) {
274: if (line == null || line.equals("")) {
275: return "";
276: }
277: StringBuffer buf = new StringBuffer();
278: if (line.indexOf("\"") <= -1) {
279: return keywordFilter(line);
280: }
281: int start = 0;
282: int startStringIndex = -1;
283: int endStringIndex = -1;
284: int tempIndex;
285: //Keep moving through String characters until we want to stop...
286: while ((tempIndex = line.indexOf("\"")) > -1) {
287: //We found the beginning of a string
288: if (startStringIndex == -1) {
289: startStringIndex = 0;
290: buf.append(stringFilter(line
291: .substring(start, tempIndex)));
292: buf.append(stringStart).append("\"");
293: line = line.substring(tempIndex + 1);
294: }
295: //Must be at the end
296: else {
297: startStringIndex = -1;
298: endStringIndex = tempIndex;
299: buf.append(line.substring(0, endStringIndex + 1));
300: buf.append(stringEnd);
301: line = line.substring(endStringIndex + 1);
302: }
303: }
304: buf.append(keywordFilter(line));
305: return buf.toString();
306: }
307:
308: /*
309: * Filters keywords from a line of text and formats them properly.
310: */
311: private String keywordFilter(String line) {
312: if (line == null || line.equals("")) {
313: return "";
314: }
315: StringBuffer buf = new StringBuffer();
316: //HashMap usedReservedWords = new HashMap(); // >= Java2 only (not thread-safe)
317: Hashtable usedReservedWords = new Hashtable(); // < Java2 (thread-safe)
318: int i = 0, startAt = 0;
319: char ch;
320: StringBuffer temp = new StringBuffer();
321: while (i < line.length()) {
322: temp.setLength(0);
323: ch = line.charAt(i);
324: startAt = i;
325: // 65-90, uppercase letters
326: // 97-122, lowercase letters
327: while (i < line.length()
328: && ((ch >= 65 && ch <= 90) || (ch >= 97 && ch <= 122))) {
329: temp.append(ch);
330: i++;
331: if (i < line.length()) {
332: ch = line.charAt(i);
333: }
334: }
335: String tempString = temp.toString();
336: if (reservedWords.containsKey(tempString)
337: && !usedReservedWords.containsKey(tempString)) {
338: usedReservedWords.put(tempString, tempString);
339: line = replace(line, tempString, (reservedWordStart
340: + tempString + reservedWordEnd));
341: i += (reservedWordStart.length() + reservedWordEnd
342: .length());
343: } else {
344: i++;
345: }
346: }
347: buf.append(line);
348: return buf.toString();
349: }
350:
351: /**
352: * Replaces all instances of oldString with newString in line.
353: */
354: public static final String replace(String line, String oldString,
355: String newString) {
356: int i = 0;
357: if ((i = line.indexOf(oldString, i)) >= 0) {
358: char[] line2 = line.toCharArray();
359: char[] newString2 = newString.toCharArray();
360: int oLength = oldString.length();
361: StringBuffer buf = new StringBuffer(line2.length);
362: buf.append(line2, 0, i).append(newString2);
363: i += oLength;
364: int j = i;
365: while ((i = line.indexOf(oldString, i)) > 0) {
366: buf.append(line2, j, i - j).append(newString2);
367: i += oLength;
368: j = i;
369: }
370: buf.append(line2, j, line2.length - j);
371: return buf.toString();
372: }
373: return line;
374: }
375:
376: /*
377: * Checks to see if some position in a line is between String start and
378: * ending characters. Not yet used in code or fully working :)
379: */
380: private boolean isInsideString(String line, int position) {
381: if (line.indexOf("\"") < 0) {
382: return false;
383: }
384: int index;
385: String left = line.substring(0, position);
386: String right = line.substring(position);
387: int leftCount = 0;
388: int rightCount = 0;
389: while ((index = left.indexOf("\"")) > -1) {
390: leftCount++;
391: left = left.substring(index + 1);
392: }
393: while ((index = right.indexOf("\"")) > -1) {
394: rightCount++;
395: right = right.substring(index + 1);
396: }
397: if (rightCount % 2 != 0 && leftCount % 2 != 0) {
398: return true;
399: } else {
400: return false;
401: }
402: }
403:
404: /*
405: * Load Hashtable (or HashMap) with Java reserved words. Improved list
406: * in version 1.1 taken directly from Java language spec.
407: */
408: private static void loadKeywords() {
409: reservedWords.put("abstract", "abstract");
410: reservedWords.put("boolean", "boolean");
411: reservedWords.put("break", "break");
412: reservedWords.put("byte", "byte");
413: reservedWords.put("case", "case");
414: reservedWords.put("catch", "catch");
415: reservedWords.put("char", "char");
416: reservedWords.put("class", "class");
417: reservedWords.put("const", "const");
418: reservedWords.put("continue", "continue");
419: reservedWords.put("default", "default");
420: reservedWords.put("do", "do");
421: reservedWords.put("double", "double");
422: reservedWords.put("else", "else");
423: reservedWords.put("extends", "extends");
424: reservedWords.put("final", "final");
425: reservedWords.put("finally", "finally");
426: reservedWords.put("float", "float");
427: reservedWords.put("for", "for");
428: reservedWords.put("goto", "goto");
429: reservedWords.put("if", "if");
430: reservedWords.put("implements", "implements");
431: reservedWords.put("import", "import");
432: reservedWords.put("instanceof", "instanceof");
433: reservedWords.put("int", "int");
434: reservedWords.put("interface", "interface");
435: reservedWords.put("long", "long");
436: reservedWords.put("native", "native");
437: reservedWords.put("new", "new");
438: reservedWords.put("package", "package");
439: reservedWords.put("private", "private");
440: reservedWords.put("protected", "protected");
441: reservedWords.put("public", "public");
442: reservedWords.put("return", "return");
443: reservedWords.put("short", "short");
444: reservedWords.put("static", "static");
445: reservedWords.put("strictfp", "strictfp");
446: reservedWords.put("super", "super");
447: reservedWords.put("switch", "switch");
448: reservedWords.put("synchronized", "synchronized");
449: reservedWords.put("this", "this");
450: reservedWords.put("throw", "throw");
451: reservedWords.put("throws", "throws");
452: reservedWords.put("transient", "transient");
453: reservedWords.put("try", "try");
454: reservedWords.put("void", "void");
455: reservedWords.put("volatile", "volatile");
456: reservedWords.put("while", "while");
457: }
458: }
|