View Javadoc
1   package org.apache.maven.jxr;
2   
3   /*
4    * CodeViewer.java
5    * CoolServlets.com
6    * March 2000
7    *
8    * Copyright (C) 2000 CoolServlets.com
9    *
10   * Redistribution and use in source and binary forms, with or without
11   * modification, are permitted provided that the following conditions are met:
12   * 1) Redistributions of source code must retain the above copyright notice,
13   *   this list of conditions and the following disclaimer.
14   * 2) Redistributions in binary form must reproduce the above copyright notice,
15   *   this list of conditions and the following disclaimer in the documentation
16   *   and/or other materials provided with the distribution.
17   * 3) Neither the name CoolServlets.com nor the names of its contributors may be
18   *   used to endorse or promote products derived from this software without
19   *   specific prior written permission.
20   *
21   * THIS SOFTWARE IS PROVIDED BY COOLSERVLETS.COM AND CONTRIBUTORS ``AS IS'' AND
22   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24   * DISCLAIMED. IN NO EVENT SHALL COOLSERVLETS.COM OR CONTRIBUTORS BE LIABLE FOR
25   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31   */
32  
33  import org.apache.commons.lang.StringUtils;
34  import org.apache.maven.jxr.pacman.ClassType;
35  import org.apache.maven.jxr.pacman.FileManager;
36  import org.apache.maven.jxr.pacman.ImportType;
37  import org.apache.maven.jxr.pacman.JavaFile;
38  import org.apache.maven.jxr.pacman.PackageManager;
39  import org.apache.maven.jxr.pacman.PackageType;
40  import org.apache.maven.jxr.util.SimpleWordTokenizer;
41  import org.apache.maven.jxr.util.StringEntry;
42  
43  import java.io.BufferedReader;
44  import java.io.File;
45  import java.io.FileInputStream;
46  import java.io.FileOutputStream;
47  import java.io.FileReader;
48  import java.io.FileWriter;
49  import java.io.IOException;
50  import java.io.InputStreamReader;
51  import java.io.ObjectInputStream;
52  import java.io.ObjectOutputStream;
53  import java.io.OutputStreamWriter;
54  import java.io.PrintWriter;
55  import java.io.Reader;
56  import java.io.Serializable;
57  import java.io.Writer;
58  import java.util.Hashtable;
59  import java.util.Locale;
60  import java.util.Vector;
61  
62  /**
63   * Syntax highlights java by turning it into html. A codeviewer object is
64   * created and then keeps state as lines are passed in. Each line passed in as
65   * java test, is returned as syntax highlighted html text. Users of the class
66   * can set how the java code will be highlighted with setter methods. Only valid
67   * java lines should be passed in since the object maintains state and may not
68   * handle illegal code gracefully. The actual system is implemented as a series
69   * of filters that deal with specific portions of the java code. The filters are
70   * as follows: <pre>
71   *  htmlFilter
72   *    |__
73   *      ongoingMultiLineCommentFilter -> uriFilter
74   *        |__
75   *          inlineCommentFilter
76   *            |__
77   *              beginMultiLineCommentFilter -> ongoingMultiLineCommentFilter
78   *                |__
79   *                  stringFilter
80   *                    |__
81   *                      keywordFilter
82   *                        |__
83   *                          uriFilter
84   *                            |__
85   *                              jxrFilter
86   *                                |__
87   *                                  importFilter
88   * </pre>
89   */
90  public class JavaCodeTransform
91      implements Serializable
92  {
93      // ----------------------------------------------------------------------
94      // public fields
95      // ----------------------------------------------------------------------
96  
97      /**
98       * show line numbers
99       */
100     public static final boolean LINE_NUMBERS = true;
101 
102     /**
103      * start comment delimiter
104      */
105     public static final String COMMENT_START = "<em class=\"jxr_comment\">";
106 
107     /**
108      * end comment delimiter
109      */
110     public static final String COMMENT_END = "</em>";
111 
112     /**
113      * start javadoc comment delimiter
114      */
115     public static final String JAVADOC_COMMENT_START = "<em class=\"jxr_javadoccomment\">";
116 
117     /**
118      * end javadoc comment delimiter
119      */
120     public static final String JAVADOC_COMMENT_END = "</em>";
121 
122     /**
123      * start String delimiter
124      */
125     public static final String STRING_START = "<span class=\"jxr_string\">";
126 
127     /**
128      * end String delimiter
129      */
130     public static final String STRING_END = "</span>";
131 
132     /**
133      * start reserved word delimiter
134      */
135     public static final String RESERVED_WORD_START = "<strong class=\"jxr_keyword\">";
136 
137     /**
138      * end reserved word delimiter
139      */
140     public static final String RESERVED_WORD_END = "</strong>";
141 
142     /**
143      * stylesheet file name
144      */
145     public static final String STYLESHEET_FILENAME = "stylesheet.css";
146 
147     /**
148      * Description of the Field
149      */
150     public static final String[] VALID_URI_SCHEMES = {"http://", "mailto:"};
151 
152     /**
153      * Specify the only characters that are allowed in a URI besides alpha and
154      * numeric characters. Refer RFC2396 - http://www.ietf.org/rfc/rfc2396.txt
155      */
156     public static final char[] VALID_URI_CHARS = {'?', '+', '%', '&', ':', '/', '.', '@', '_', ';', '=', '$', ',', '-',
157         '!', '~', '*', '\'', '(', ')'};
158 
159     // ----------------------------------------------------------------------
160     // private fields
161     // ----------------------------------------------------------------------
162 
163     /**
164      * HashTable containing java reserved words
165      */
166     private Hashtable reservedWords = new Hashtable();
167 
168     /**
169      * flag set to true when a multi-line comment is started
170      */
171     private boolean inMultiLineComment = false;
172 
173     /**
174      * flag set to true when a javadoc comment is started
175      */
176     private boolean inJavadocComment = false;
177 
178     /**
179      * Set the filename that is currently being processed.
180      */
181     private String currentFilename = null;
182 
183     /**
184      * The current CVS revision of the currently transformed document
185      */
186     private String revision = null;
187 
188     /**
189      * The currently being transformed source file
190      */
191     private String sourcefile = null;
192 
193     /**
194      * The currently being written destination file
195      */
196     private String destfile = null;
197 
198     /**
199      * The virtual source directory that is being read from: <code>src/java</code>
200      */
201     private String sourcedir = null;
202 
203     /**
204      * The input encoding
205      */
206     private String inputEncoding = null;
207 
208     /**
209      * The output encoding
210      */
211     private String outputEncoding = null;
212 
213     /**
214      * The wanted locale
215      */
216     private Locale locale = null;
217 
218     /**
219      * Relative path to javadocs, suitable for hyperlinking
220      */
221     private String javadocLinkDir;
222 
223     /**
224      * Package Manager for this project.
225      */
226     private PackageManager packageManager;
227 
228     /**
229      * current file manager
230      */
231     private FileManager fileManager;
232 
233     // ----------------------------------------------------------------------
234     // constructor
235     // ----------------------------------------------------------------------
236 
237     /**
238      * Constructor for the JavaCodeTransform object
239      *
240      * @param packageManager PackageManager for this project
241      */
242     public JavaCodeTransform( PackageManager packageManager )
243     {
244         this.packageManager = packageManager;
245         loadHash();
246         this.fileManager = packageManager.getFileManager();
247     }
248 
249     // ----------------------------------------------------------------------
250     // public methods
251     // ----------------------------------------------------------------------
252 
253     /**
254      * Now different method of seeing if at end of input stream, closes inputs
255      * stream at end.
256      *
257      * @param line String
258      * @return filtered line of code
259      */
260     public final String syntaxHighlight( String line )
261     {
262         return htmlFilter( line );
263     }
264 
265     /**
266      * Gets the header attribute of the JavaCodeTransform object
267      * @param out the writer where the header is appended to
268      *
269      * @return String
270      */
271     public void appendHeader( PrintWriter out )
272     {
273         String outputEncoding = this.outputEncoding;
274         if ( outputEncoding == null )
275         {
276             outputEncoding = "ISO-8859-1";
277         }
278 
279         // header
280         out.println(
281                 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">" );
282         out.print( "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"" );
283         out.print( locale );
284         out.print( "\" lang=\"" );
285         out.print( locale );
286         out.println( "\">" );
287         out.print( "<head>" );
288         out.print( "<meta http-equiv=\"content-type\" content=\"text/html; charset=" );
289         out.print( outputEncoding );
290         out.println( "\" />" );
291 
292         // title ("classname xref")
293         out.print( "<title>" );
294         try
295         {
296             JavaFile javaFile = fileManager.getFile( this.getCurrentFilename() );
297             // Use the name of the file instead of the class to handle inner classes properly
298             if ( javaFile.getClassType() != null && javaFile.getClassType().getFilename() != null )
299             {
300             	out.print( javaFile.getClassType().getFilename() );
301             }
302             else
303             {
304             	out.print( this.getCurrentFilename() );
305             }
306             out.print( " " );
307         }
308         catch ( IOException e )
309         {
310             e.printStackTrace();
311         }
312         finally
313         {
314             out.println( "xref</title>" );
315         }
316 
317         // stylesheet link
318         out.print( "<link type=\"text/css\" rel=\"stylesheet\" href=\"" );
319         out.print( this.getPackageRoot() );
320         out.print( STYLESHEET_FILENAME );
321         out.println( "\" />" );
322 
323         out.println( "</head>" );
324         out.println( "<body>" );
325         out.print( this.getFileOverview() );
326 
327         // start code section
328         out.println( "<pre>" );
329     }
330 
331     /**
332      * Gets the footer attribute of the JavaCodeTransform object
333      * @param out the writer where the header is appended to
334      * @param bottom the bottom text
335      * @return String
336      */
337     public final void appendFooter( PrintWriter out, String bottom )
338     {
339         out.println( "</pre>" );
340         out.println( "<hr/>" );
341         out.print( "<div id=\"footer\">" );
342         out.print( bottom );
343         out.println( "</div>" );
344         out.println( "</body>" );
345         out.println( "</html>" );
346     }
347 
348     /**
349      * This is the public method for doing all transforms of code.
350      *
351      * @param sourceReader Reader
352      * @param destWriter Writer
353      * @param locale String
354      * @param inputEncoding String
355      * @param outputEncoding String
356      * @param javadocLinkDir String
357      * @param revision String
358      * @param bottom string
359      * @throws IOException
360      */
361     public final void transform( Reader sourceReader, Writer destWriter, Locale locale, String inputEncoding,
362                                  String outputEncoding, String javadocLinkDir, String revision, String bottom )
363         throws IOException
364     {
365         this.locale = locale;
366         this.inputEncoding = inputEncoding;
367         this.outputEncoding = outputEncoding;
368         this.javadocLinkDir = javadocLinkDir;
369         this.revision = revision;
370 
371         BufferedReader in = new BufferedReader( sourceReader );
372 
373         PrintWriter out = new PrintWriter( destWriter );
374 
375         String line = "";
376 
377         appendHeader( out );
378 
379         int linenumber = 1;
380         while ( ( line = in.readLine() ) != null )
381         {
382             if ( LINE_NUMBERS )
383             {
384                 out.print( "<a class=\"jxr_linenumber\" name=\"L" + linenumber + "\" " + "href=\"#L" + linenumber + "\">" + linenumber
385                     + "</a>" + getLineWidth( linenumber ) );
386             }
387 
388             out.println( this.syntaxHighlight( line ) );
389 
390             ++linenumber;
391         }
392 
393         appendFooter( out, bottom );
394 
395         out.flush();
396     }
397 
398     /**
399      * This is the public method for doing all transforms of code.
400      *
401      * @param sourcefile String
402      * @param destfile String
403      * @param locale String
404      * @param inputEncoding String
405      * @param outputEncoding String
406      * @param javadocLinkDir String
407      * @param revision String
408      * @param bottom TODO
409      * @throws IOException
410      */
411     public final void transform( String sourcefile, String destfile, Locale locale, String inputEncoding,
412                                  String outputEncoding, String javadocLinkDir, String revision, String bottom )
413         throws IOException
414     {
415         this.setCurrentFilename( sourcefile );
416 
417         this.sourcefile = sourcefile;
418         this.destfile = destfile;
419 
420         //make sure that the parent directories exist...
421         new File( new File( destfile ).getParent() ).mkdirs();
422 
423         Reader fr = null;
424         Writer fw = null;
425         try
426         {
427             if ( inputEncoding != null )
428             {
429                 fr = new InputStreamReader( new FileInputStream( sourcefile ), inputEncoding );
430             }
431             else
432             {
433                 fr = new FileReader( sourcefile );
434             }
435             if ( outputEncoding != null )
436             {
437                 fw = new OutputStreamWriter( new FileOutputStream( destfile ), outputEncoding );
438             }
439             else
440             {
441                 fw = new FileWriter( destfile );
442             }
443 
444             transform( fr, fw, locale, inputEncoding, outputEncoding, javadocLinkDir, revision, bottom );
445         }
446         catch ( RuntimeException e )
447         {
448             System.out.println( "Unable to processPath " + sourcefile + " => " + destfile );
449             throw e;
450         }
451         finally
452         {
453             if ( fr != null )
454             {
455                 try
456                 {
457                     fr.close();
458                 }
459                 catch ( Exception ex )
460                 {
461                     ex.printStackTrace();
462                 }
463             }
464             if ( fw != null )
465             {
466                 try
467                 {
468                     fw.close();
469                 }
470                 catch ( Exception ex )
471                 {
472                     ex.printStackTrace();
473                 }
474             }
475         }
476     }
477 
478     /**
479      * Get the current filename
480      *
481      * @return String
482      */
483     public final String getCurrentFilename()
484     {
485         return this.currentFilename;
486     }
487 
488     /**
489      * Set the current filename
490      *
491      * @param filename String
492      */
493     public final void setCurrentFilename( String filename )
494     {
495         this.currentFilename = filename;
496     }
497 
498     /**
499      * From the current file, determine the package root based on the current
500      * path.
501      *
502      * @return String
503      */
504     public final String getPackageRoot()
505     {
506         StringBuffer buff = new StringBuffer();
507 
508         JavaFile jf = null;
509 
510         try
511         {
512             jf = fileManager.getFile( this.getCurrentFilename() );
513         }
514         catch ( IOException e )
515         {
516             e.printStackTrace();
517             return null;
518         }
519 
520         String current = jf.getPackageType().getName();
521 
522         int count = this.getPackageCount( current );
523 
524         for ( int i = 0; i < count; ++i )
525         {
526             buff.append( "../" );
527         }
528 
529         return buff.toString();
530     }
531 
532     /**
533      * Given a line of text, search for URIs and make href's out of them
534      *
535      * @param line String
536      * @return String
537      */
538     public final String uriFilter( String line )
539     {
540         for ( int i = 0; i < VALID_URI_SCHEMES.length; ++i )
541         {
542             String scheme = VALID_URI_SCHEMES[i];
543 
544             int index = line.indexOf( scheme );
545 
546             if ( index != -1 )
547             {
548                 int start = index;
549                 int end = -1;
550 
551                 for ( int j = start; j < line.length(); ++j )
552                 {
553                     char current = line.charAt( j );
554 
555                     if ( !Character.isLetterOrDigit( current ) && isInvalidURICharacter( current ) )
556                     {
557                         end = j;
558                         break;
559                     }
560 
561                     end = j;
562                 }
563 
564                 //now you should have the full URI so you can replace this
565                 //in the current buffer
566 
567                 if ( end != -1 )
568                 {
569                     String uri = line.substring( start, end );
570 
571                     line = StringUtils.replace( line, uri,
572                                                 "<a href=\"" + uri + "\" target=\"alexandria_uri\">" + uri + "</a>" );
573                 }
574             }
575         }
576 
577         //if we are in a multiline comment we should not call JXR here.
578         if ( !inMultiLineComment && !inJavadocComment )
579         {
580             return jxrFilter( line );
581         }
582 
583         return line;
584     }
585 
586     /**
587      * The current revision of the CVS module
588      *
589      * @return String
590      */
591     public final String getRevision()
592     {
593         return this.revision;
594     }
595 
596     /**
597      * The current source file being read
598      *
599      * @return source file name
600      */
601     public final String getSourcefile()
602     {
603         return this.sourcefile;
604     }
605 
606     /**
607      * The current destination file being written
608      *
609      * @return destination file name
610      */
611     public final String getDestfile()
612     {
613         return this.destfile;
614     }
615 
616     /**
617      * The current source directory being read from.
618      *
619      * @return source directory
620      */
621     public final String getSourceDirectory()
622     {
623         return this.sourcedir;
624     }
625 
626     /**
627      * Cross Reference the given line with JXR returning the new content.
628      *
629      * @param line String
630      * @param packageName String
631      * @param classType ClassType
632      * @return String
633      */
634     public final String xrLine( String line, String packageName, ClassType classType )
635     {
636         StringBuffer buff = new StringBuffer( line );
637 
638         String link = null;
639         String find = null;
640         String href = null;
641 
642         if ( classType != null )
643         {
644             href = this.getHREF( packageName, classType );
645             find = classType.getName();
646         }
647         else
648         {
649             href = this.getHREF( packageName );
650             find = packageName;
651         }
652 
653         //build out what the link would be.
654         link = "<a href=\"" + href + "\">" + find + "</a>";
655 
656         //use the SimpleWordTokenizer to find all entries
657         //that match word.  Then replace these with the link
658 
659         //now replace the word in the buffer with the link
660 
661         String replace = link;
662         StringEntry[] tokens = SimpleWordTokenizer.tokenize( buff.toString(), find );
663 
664         for ( int l = 0; l < tokens.length; ++l )
665         {
666 
667             int start = tokens[l].getIndex();
668             int end = tokens[l].getIndex() + find.length();
669 
670             buff.replace( start, end, replace );
671 
672         }
673 
674         return buff.toString();
675     }
676 
677     /**
678      * Highlight the package in this line.
679      *
680      * @param line input line
681      * @param packageName package name
682      * @return input line with linked package
683      */
684     public final String xrLine( String line, String packageName )
685     {
686         String href = this.getHREF( packageName );
687 
688         String find = packageName;
689 
690         //build out what the link would be.
691         String link = "<a href=\"" + href + "\">" + find + "</a>";
692 
693         return StringUtils.replace( line, find, link );
694     }
695 
696     // ----------------------------------------------------------------------
697     // private methods
698     // ----------------------------------------------------------------------
699 
700     /**
701      * Filter html tags into more benign text.
702      *
703      * @param line String
704      * @return html encoded line
705      */
706     private final String htmlFilter( String line )
707     {
708         if ( line == null || line.equals( "" ) )
709         {
710             return "";
711         }
712         line = replace( line, "&", "&amp;" );
713         line = replace( line, "<", "&lt;" );
714         line = replace( line, ">", "&gt;" );
715         line = replace( line, "\\\\", "&#92;&#92;" );
716         line = replace( line, "\\\"", "\\&quot;" );
717         line = replace( line, "'\"'", "'&quot;'" );
718         return ongoingMultiLineCommentFilter( line );
719     }
720 
721     /**
722      * Handle ongoing multi-line comments, detecting ends if present.
723      * State is maintained in private boolean members,
724      * one each for javadoc and (normal) multiline comments.
725      *
726      * @param line String
727      * @return String
728      */
729     private final String ongoingMultiLineCommentFilter( String line )
730     {
731         if ( line == null || line.equals( "" ) )
732         {
733             return "";
734         }
735         final String[] tags =
736             inJavadocComment
737                 ? new String[] { JAVADOC_COMMENT_START, JAVADOC_COMMENT_END } :
738             inMultiLineComment
739                 ? new String[] { COMMENT_START, COMMENT_END } :
740             null;
741 
742         if ( tags == null )
743         {
744             //pass the line down to the next filter for processing.
745             return inlineCommentFilter( line );
746         }
747 
748         int index = line.indexOf( "*/" );
749         // only filter the portion without the end-of-comment,
750         // since * and / seem to be valid URI characters
751         String comment = uriFilter( index < 0 ? line : line.substring( 0, index ) );
752         if ( index >= 0 )
753         {
754             inJavadocComment = false;
755             inMultiLineComment = false;
756         }
757         StringBuilder buf = new StringBuilder( tags[0] ).append(
758             comment );
759 
760         if ( index >= 0 )
761         {
762             buf.append( "*/" );
763         }
764         buf.append( tags[1] );
765 
766         if ( index >= 0 && line.length() > index + 2 )
767         {
768             buf.append( inlineCommentFilter( line.substring( index + 2 ) ) );
769         }
770         return buf.toString();
771     }
772 
773     /**
774      * Filter inline comments from a line and formats them properly. One problem
775      * we'll have to solve here: comments contained in a string should be
776      * ignored... this is also true of the multi-line comments. So, we could
777      * either ignore the problem, or implement a function called something like
778      * isInsideString(line, index) where index points to some point in the line
779      * that we need to check... started doing this function below.
780      *
781      * @param line String
782      * @return String
783      */
784     private final String inlineCommentFilter( String line )
785     {
786         //assert !inJavadocComment;
787         //assert !inMultiLineComment;
788 
789         if ( line == null || line.equals( "" ) )
790         {
791             return "";
792         }
793         int index;
794         if ( ( index = line.indexOf( "//" ) ) >= 0 && !isInsideString( line, index ) )
795         {
796             return new StringBuffer(
797                 beginMultiLineCommentFilter( line.substring( 0, index ) ) )
798                 .append( COMMENT_START )
799                 .append( line.substring( index ) )
800                 .append( COMMENT_END )
801                 .toString();
802         }
803 
804         return beginMultiLineCommentFilter( line );
805     }
806 
807     /**
808      * Detect and handle the start of multiLine comments.
809      * State is maintained in private boolean members
810      * one each for javadoc and (normal) multiline comments.
811      *
812      * @param line String
813      * @return String
814      */
815     private final String beginMultiLineCommentFilter( String line )
816     {
817         //assert !inJavadocComment;
818         //assert !inMultiLineComment;
819 
820         if ( line == null || line.equals( "" ) )
821         {
822             return "";
823         }
824 
825         int index;
826         //check to see if a multi-line comment starts on this line:
827         if ( ( index = line.indexOf( "/*" ) ) > -1 && !isInsideString( line, index ) )
828         {
829             String fromIndex = line.substring( index );
830             if ( fromIndex.startsWith( "/**" )
831                 && !( fromIndex.startsWith( "/**/" ) ) )
832             {
833                 inJavadocComment = true;
834             } else {
835                 inMultiLineComment = true;
836             }
837             //Return result of other filters + everything after the start
838             //of the multiline comment. We need to pass the through the
839             //to the ongoing multiLineComment filter again in case the comment
840             //ends on the same line.
841             return new StringBuilder(
842                 stringFilter( line.substring( 0, index ) ) ).append(
843                 ongoingMultiLineCommentFilter( fromIndex ) ).toString();
844         }
845 
846         //Otherwise, no useful multi-line comment information was found so
847         //pass the line down to the next filter for processesing.
848         else
849         {
850             return stringFilter( line );
851         }
852     }
853 
854     /**
855      * Filters strings from a line of text and formats them properly.
856      *
857      * @param line String
858      * @return String
859      */
860     private final String stringFilter( String line )
861     {
862         if ( line == null || line.equals( "" ) )
863         {
864             return "";
865         }
866         StringBuffer buf = new StringBuffer();
867         if ( line.indexOf( "\"" ) <= -1 )
868         {
869             return keywordFilter( line );
870         }
871         int start = 0;
872         int startStringIndex = -1;
873         int endStringIndex = -1;
874         int tempIndex;
875         //Keep moving through String characters until we want to stop...
876         while ( ( tempIndex = line.indexOf( "\"" ) ) > -1 )
877         {
878             //We found the beginning of a string
879             if ( startStringIndex == -1 )
880             {
881                 startStringIndex = 0;
882                 buf.append( stringFilter( line.substring( start, tempIndex ) ) );
883                 buf.append( STRING_START ).append( "\"" );
884                 line = line.substring( tempIndex + 1 );
885             }
886             //Must be at the end
887             else
888             {
889                 startStringIndex = -1;
890                 endStringIndex = tempIndex;
891                 buf.append( line.substring( 0, endStringIndex + 1 ) );
892                 buf.append( STRING_END );
893                 line = line.substring( endStringIndex + 1 );
894             }
895         }
896 
897         buf.append( keywordFilter( line ) );
898 
899         return buf.toString();
900     }
901 
902     /**
903      * Filters keywords from a line of text and formats them properly.
904      *
905      * @param line String
906      * @return String
907      */
908     private final String keywordFilter( String line )
909     {
910         final String CLASS_KEYWORD = "class";
911 
912         if ( line == null || line.equals( "" ) )
913         {
914             return "";
915         }
916         StringBuffer buf = new StringBuffer();
917         int i = 0;
918         char ch;
919         StringBuffer temp = new StringBuffer();
920         while ( i < line.length() )
921         {
922             temp.setLength( 0 );
923             ch = line.charAt( i );
924             while ( i < line.length() && ( ( ch >= 65 && ch <= 90 ) || ( ch >= 97 && ch <= 122 ) ) )
925             {
926                 temp.append( ch );
927                 i++;
928                 if ( i < line.length() )
929                 {
930                     ch = line.charAt( i );
931                 }
932             }
933             String tempString = temp.toString();
934 
935             // Special handling of css style class definitions
936             if ( CLASS_KEYWORD.equals( tempString ) && ch == '=' )
937             {
938                 i++;
939             }
940             else if ( reservedWords.containsKey( tempString ) )
941             {
942                 StringBuffer newLine = new StringBuffer( line.substring( 0, i - tempString.length() ) );
943                 newLine.append( RESERVED_WORD_START );
944                 newLine.append( tempString );
945                 newLine.append( RESERVED_WORD_END );
946                 newLine.append( line.substring( i ) );
947                 line = newLine.toString();
948                 i += ( RESERVED_WORD_START.length() + RESERVED_WORD_END.length() );
949             }
950             else
951             {
952                 i++;
953             }
954         }
955         buf.append( line );
956 
957         return uriFilter( buf.toString() );
958     }
959 
960     /**
961      * Replace... I made it use a <code>StringBuffer</code>... hope it still works :)
962      *
963      * @param line String
964      * @param oldString String
965      * @param newString String
966      * @return String
967      */
968     private final String replace( String line, String oldString, String newString )
969     {
970         int i = 0;
971         while ( ( i = line.indexOf( oldString, i ) ) >= 0 )
972         {
973             line = ( new StringBuffer().append( line.substring( 0, i ) ).append( newString ).append(
974                 line.substring( i + oldString.length() ) ) ).toString();
975             i += newString.length();
976         }
977         return line;
978     }
979 
980     /**
981      * Checks to see if some position in a line is between String start and
982      * ending characters. Not yet used in code or fully working :)
983      *
984      * @param line String
985      * @param position int
986      * @return boolean
987      */
988     private final boolean isInsideString( String line, int position )
989     {
990         if ( line.indexOf( '"' ) < 0 )
991         {
992             return false;
993         }
994         int index;
995         String left = line.substring( 0, position );
996         String right = line.substring( position );
997         int leftCount = 0;
998         int rightCount = 0;
999         while ( ( index = left.indexOf( '"' ) ) > -1 )
1000         {
1001             leftCount++;
1002             left = left.substring( index + 1 );
1003         }
1004         while ( ( index = right.indexOf( '"' ) ) > -1 )
1005         {
1006             rightCount++;
1007             right = right.substring( index + 1 );
1008         }
1009         return ( rightCount % 2 != 0 && leftCount % 2 != 0 );
1010     }
1011 
1012     /**
1013      * Description of the Method
1014      */
1015     private final void loadHash()
1016     {
1017         reservedWords.put( "abstract", "abstract" );
1018         reservedWords.put( "do", "do" );
1019         reservedWords.put( "inner", "inner" );
1020         reservedWords.put( "public", "public" );
1021         reservedWords.put( "var", "var" );
1022         reservedWords.put( "boolean", "boolean" );
1023         reservedWords.put( "continue", "continue" );
1024         reservedWords.put( "int", "int" );
1025         reservedWords.put( "return", "return" );
1026         reservedWords.put( "void", "void" );
1027         reservedWords.put( "break", "break" );
1028         reservedWords.put( "else", "else" );
1029         reservedWords.put( "interface", "interface" );
1030         reservedWords.put( "short", "short" );
1031         reservedWords.put( "volatile", "volatile" );
1032         reservedWords.put( "byvalue", "byvalue" );
1033         reservedWords.put( "extends", "extends" );
1034         reservedWords.put( "long", "long" );
1035         reservedWords.put( "static", "static" );
1036         reservedWords.put( "while", "while" );
1037         reservedWords.put( "case", "case" );
1038         reservedWords.put( "final", "final" );
1039         reservedWords.put( "native", "native" );
1040         reservedWords.put( "super", "super" );
1041         reservedWords.put( "transient", "transient" );
1042         reservedWords.put( "cast", "cast" );
1043         reservedWords.put( "float", "float" );
1044         reservedWords.put( "new", "new" );
1045         reservedWords.put( "rest", "rest" );
1046         reservedWords.put( "catch", "catch" );
1047         reservedWords.put( "for", "for" );
1048         reservedWords.put( "null", "null" );
1049         reservedWords.put( "synchronized", "synchronized" );
1050         reservedWords.put( "char", "char" );
1051         reservedWords.put( "finally", "finally" );
1052         reservedWords.put( "operator", "operator" );
1053         reservedWords.put( "this", "this" );
1054         reservedWords.put( "class", "class" );
1055         reservedWords.put( "generic", "generic" );
1056         reservedWords.put( "outer", "outer" );
1057         reservedWords.put( "switch", "switch" );
1058         reservedWords.put( "const", "const" );
1059         reservedWords.put( "goto", "goto" );
1060         reservedWords.put( "package", "package" );
1061         reservedWords.put( "throw", "throw" );
1062         reservedWords.put( "double", "double" );
1063         reservedWords.put( "if", "if" );
1064         reservedWords.put( "private", "private" );
1065         reservedWords.put( "true", "true" );
1066         reservedWords.put( "default", "default" );
1067         reservedWords.put( "import", "import" );
1068         reservedWords.put( "protected", "protected" );
1069         reservedWords.put( "try", "try" );
1070         reservedWords.put( "throws", "throws" );
1071         reservedWords.put( "implements", "implements" );
1072     }
1073 
1074     /**
1075      * Description of the Method
1076      *
1077      * @param oos ObjectOutputStream
1078      * @throws IOException
1079      */
1080     final void writeObject( ObjectOutputStream oos )
1081         throws IOException
1082     {
1083         oos.defaultWriteObject();
1084     }
1085 
1086     /**
1087      * Description of the Method
1088      *
1089      * @param ois ObjectInputStream
1090      * @throws ClassNotFoundException
1091      * @throws IOException
1092      */
1093     final void readObject( ObjectInputStream ois )
1094         throws ClassNotFoundException, IOException
1095     {
1096         ois.defaultReadObject();
1097     }
1098 
1099     /**
1100      * Get an overview header for this file.
1101      *
1102      * @return String
1103      */
1104     private final String getFileOverview()
1105     {
1106         StringBuffer overview = new StringBuffer();
1107 
1108         // only add the header if javadocs are present
1109         if ( javadocLinkDir != null )
1110         {
1111             overview.append( "<div id=\"overview\">" );
1112             //get the URI to get Javadoc info.
1113             StringBuffer javadocURI = new StringBuffer().append( javadocLinkDir );
1114 
1115             try
1116             {
1117                 JavaFile jf = fileManager.getFile( this.getCurrentFilename() );
1118 
1119                 javadocURI.append( StringUtils.replace( jf.getPackageType().getName(), ".", "/" ) );
1120                 javadocURI.append( "/" );
1121                 // Use the name of the file instead of the class to handle inner classes properly
1122                 if ( jf.getClassType() != null && jf.getClassType().getFilename() != null )
1123                 {
1124                     javadocURI.append( jf.getClassType().getFilename() );
1125                 }
1126                 else
1127                 {
1128                     return "";
1129                 }
1130                 javadocURI.append( ".html" );
1131 
1132             }
1133             catch ( IOException e )
1134             {
1135                 e.printStackTrace();
1136             }
1137 
1138             String javadocHREF = "<a href=\"" + javadocURI + "\">View Javadoc</a>";
1139 
1140             //get the generation time...
1141             overview.append( javadocHREF );
1142 
1143             overview.append( "</div>" );
1144         }
1145 
1146         return overview.toString();
1147     }
1148 
1149     /**
1150      * Handles line width which may need to change depending on which line
1151      * number you are on.
1152      *
1153      * @param linenumber int
1154      * @return String
1155      */
1156     private final String getLineWidth( int linenumber )
1157     {
1158         if ( linenumber < 10 )
1159         {
1160             return "   ";
1161         }
1162         else if ( linenumber < 100 )
1163         {
1164             return "  ";
1165         }
1166         else
1167         {
1168             return " ";
1169         }
1170     }
1171 
1172     /**
1173      * Handles finding classes based on the current filename and then makes
1174      * HREFs for you to link to them with.
1175      *
1176      * @param line String
1177      * @return String
1178      */
1179     private final String jxrFilter( String line )
1180     {
1181         JavaFile jf = null;
1182 
1183         try
1184         {
1185             //if the current file isn't set then just return
1186             if ( this.getCurrentFilename() == null )
1187             {
1188                 return line;
1189             }
1190 
1191             jf = fileManager.getFile( this.getCurrentFilename() );
1192         }
1193         catch ( IOException e )
1194         {
1195             e.printStackTrace();
1196             return line;
1197         }
1198 
1199         Vector v = new Vector();
1200 
1201         //get the imported packages
1202         ImportType[] imports = jf.getImportTypes();
1203         for ( int j = 0; j < imports.length; ++j )
1204         {
1205             v.addElement( imports[j].getPackage() );
1206         }
1207 
1208         //add the current package.
1209         v.addElement( jf.getPackageType().getName() );
1210 
1211         String[] packages = new String[v.size()];
1212         v.copyInto( packages );
1213 
1214         StringEntry[] words = SimpleWordTokenizer.tokenize( line );
1215 
1216         //go through each word and then match them to the correct class if necessary.
1217         for ( int i = 0; i < words.length; ++i )
1218         {
1219             //each word
1220             StringEntry word = words[i];
1221 
1222             for ( int j = 0; j < packages.length; ++j )
1223             {
1224                 //get the package from the PackageManager because this will hold
1225                 //the version with the classes also.
1226 
1227                 PackageType currentImport = packageManager.getPackageType( packages[j] );
1228 
1229                 //the package here might in fact be null because it wasn't parsed out
1230                 //this might be something that is either not included or is part
1231                 //of another package and wasn't parsed out.
1232 
1233                 if ( currentImport == null )
1234                 {
1235                     continue;
1236                 }
1237 
1238                 //see if the current word is within the package
1239 
1240                 //at this point the word could be a fully qualified package name
1241                 //(FQPN) or an imported package name.
1242 
1243                 String wordName = word.toString();
1244 
1245                 if ( wordName.indexOf( "." ) != -1 )
1246                 {
1247                     //if there is a "." in the string then we have to assume
1248                     //it is a package.
1249 
1250                     String fqpn_package = null;
1251                     String fqpn_class = null;
1252 
1253                     fqpn_package = wordName.substring( 0, wordName.lastIndexOf( "." ) );
1254                     fqpn_class = wordName.substring( wordName.lastIndexOf( "." ) + 1, wordName.length() );
1255 
1256                     //note. since this is a reference to a full package then
1257                     //it doesn't have to be explicitly imported so this information
1258                     //is useless.  Instead just see if it was parsed out.
1259 
1260                     PackageType pt = packageManager.getPackageType( fqpn_package );
1261 
1262                     if ( pt != null )
1263                     {
1264                         ClassType ct = pt.getClassType( fqpn_class );
1265 
1266                         if ( ct != null )
1267                         {
1268                             //OK.  the user specified a full package to be imported
1269                             //that is in the package manager so it is time to
1270                             //link to it.
1271 
1272                             line = xrLine( line, pt.getName(), ct );
1273                         }
1274                     }
1275 
1276                     if ( fqpn_package.equals( currentImport.getName() )
1277                         && currentImport.getClassType( fqpn_class ) != null )
1278                     {
1279                         //then the package we are currently in is the one specified in the string
1280                         //and the import class is correct.
1281                         line = xrLine( line, packages[j], currentImport.getClassType( fqpn_class ) );
1282                     }
1283                 }
1284                 else if ( currentImport.getClassType( wordName ) != null )
1285                 {
1286                     line = xrLine( line, packages[j], currentImport.getClassType( wordName ) );
1287                 }
1288             }
1289         }
1290 
1291         return importFilter( line );
1292     }
1293 
1294     /**
1295      * Given the current package, get an HREF to the package and class given
1296      *
1297      * @param dest String
1298      * @param jc ClassType
1299      * @return String
1300      */
1301     private final String getHREF( String dest, ClassType jc )
1302     {
1303         StringBuffer href = new StringBuffer();
1304 
1305         //find out how to go back to the root
1306         href.append( this.getPackageRoot() );
1307 
1308         //now find out how to get to the dest package
1309         dest = StringUtils.replace( dest, ".*", "" );
1310         dest = StringUtils.replace( dest, ".", "/" );
1311 
1312         href.append( dest );
1313 
1314         // Now append filename.html
1315         if ( jc != null )
1316         {
1317             href.append( "/" );
1318             href.append( jc.getFilename() );
1319             href.append( ".html" );
1320         }
1321 
1322         return href.toString();
1323     }
1324 
1325     /**
1326      * Based on the destination package, get the HREF.
1327      *
1328      * @param dest String
1329      * @return String
1330      */
1331     private final String getHREF( String dest )
1332     {
1333         return getHREF( dest, null );
1334     }
1335 
1336     /**
1337      * <p>Given the name of a package... get the number of
1338      * subdirectories/subpackages there would be. </p>
1339      * <p>EX: <code>org.apache.maven == 3</code> </p>
1340      *
1341      * @param packageName String
1342      * @return int
1343      */
1344     private final int getPackageCount( String packageName )
1345     {
1346         if ( packageName == null )
1347         {
1348             return 0;
1349         }
1350 
1351         int count = 0;
1352         int index = 0;
1353 
1354         while ( true )
1355         {
1356             index = packageName.indexOf( '.', index );
1357 
1358             if ( index == -1 )
1359             {
1360                 break;
1361             }
1362             ++index;
1363             ++count;
1364         }
1365 
1366         //need to increment this by one
1367         ++count;
1368 
1369         return count;
1370     }
1371 
1372     /**
1373      * Parse out the current link and look for package/import statements and
1374      * then create HREFs for them
1375      *
1376      * @param line String
1377      * @return String
1378      */
1379     private final String importFilter( String line )
1380     {
1381         int start = -1;
1382 
1383         /*
1384          Used for determining if this is a package declaration.  If it is
1385          then we can make some additional assumptions:
1386          - that this isn't a Class import so the full String is valid
1387          - that it WILL be on the disk since this is based on the current
1388          - file.
1389          */
1390         boolean isPackage = line.trim().startsWith( "package " );
1391         boolean isImport = line.trim().startsWith( "import " );
1392 
1393         if ( isImport || isPackage )
1394         {
1395             start = line.trim().indexOf( " " );
1396         }
1397 
1398         if ( start != -1 )
1399         {
1400             //filter out this packagename...
1401             String pkg = line.substring( start, line.length() ).trim();
1402 
1403             //specify the classname of this import if any.
1404             String classname = null;
1405 
1406             if ( pkg.indexOf( ".*" ) != -1 )
1407             {
1408                 pkg = StringUtils.replace( pkg, ".*", "" );
1409             }
1410             else if ( !isPackage )
1411             {
1412                 //this is an explicit Class import
1413 
1414                 String packageLine = pkg.toString();
1415 
1416                 // This catches a boundary problem where you have something like:
1417                 //
1418                 // Foo foo = FooMaster.getFooInstance().
1419                 //     danceLittleFoo();
1420                 //
1421                 // This breaks Jxr and won't be a problem when we hook
1422                 // in the real parser.
1423 
1424                 int a = packageLine.lastIndexOf( "." ) + 1;
1425                 int b = packageLine.length() - 1;
1426 
1427                 if ( a > b + 1 )
1428                 {
1429                     classname = packageLine.substring( packageLine.lastIndexOf( "." ) + 1, packageLine.length() - 1 );
1430 
1431                     int end = pkg.lastIndexOf( "." );
1432                     if ( end == -1 )
1433                     {
1434                         end = pkg.length() - 1;
1435                     }
1436 
1437                     pkg = pkg.substring( 0, end );
1438                 }
1439             }
1440 
1441             pkg = StringUtils.replace( pkg, ";", "" );
1442             String pkgHREF = getHREF( pkg );
1443             //if this package is within the PackageManager then you can create an HREF for it.
1444 
1445             if ( packageManager.getPackageType( pkg ) != null || isPackage )
1446             {
1447                 //Create an HREF for explicit classname imports
1448                 if ( classname != null )
1449                 {
1450                     line = StringUtils.replace( line, classname, "<a href=\"" + pkgHREF + "/" + classname + ".html"
1451                         + "\">" + classname + "</a>" );
1452                 }
1453 
1454                 //now replace the given package with a href
1455                 line = StringUtils.replace( line, pkg, "<a href=\"" + pkgHREF + "/" + DirectoryIndexer.INDEX + "\">"
1456                     + pkg + "</a>" );
1457             }
1458 
1459         }
1460 
1461         return line;
1462     }
1463 
1464 
1465     /**
1466      * if the given char is not one of the following in VALID_URI_CHARS then
1467      * return true
1468      *
1469      * @param c char to check against VALID_URI_CHARS list
1470      * @return <code>true</code> if c is a valid URI char
1471      */
1472     private final boolean isInvalidURICharacter( char c )
1473     {
1474         for ( int i = 0; i < VALID_URI_CHARS.length; ++i )
1475         {
1476             if ( VALID_URI_CHARS[i] == c )
1477             {
1478                 return false;
1479             }
1480         }
1481 
1482         return true;
1483     }
1484 }