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