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