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