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