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