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