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( javaFile.getFilename() );
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 beginMultiLineCommentFilter( line.substring( 0, index ) ) + COMMENT_START + line.substring( index )
760                     + COMMENT_END;
761         }
762 
763         return beginMultiLineCommentFilter( line );
764     }
765 
766     /**
767      * Detect and handle the start of multiLine comments. State is maintained in private boolean members one each for
768      * javadoc and (normal) multiline comments.
769      *
770      * @param line String
771      * @return String
772      */
773     private String beginMultiLineCommentFilter( String line )
774     {
775         // assert !inJavadocComment;
776         // assert !inMultiLineComment;
777 
778         if ( line == null || line.equals( "" ) )
779         {
780             return "";
781         }
782 
783         int index = line.indexOf( "/*" );
784         // check to see if a multi-line comment starts on this line:
785         if ( ( index > -1 ) && !isInsideString( line, index ) )
786         {
787             String fromIndex = line.substring( index );
788             if ( fromIndex.startsWith( "/**" ) && !( fromIndex.startsWith( "/**/" ) ) )
789             {
790                 inJavadocComment = true;
791             }
792             else
793             {
794                 inMultiLineComment = true;
795             }
796             // Return result of other filters + everything after the start
797             // of the multiline comment. We need to pass the through the
798             // to the ongoing multiLineComment filter again in case the comment
799             // ends on the same line.
800             return stringFilter( line.substring( 0, index ) ) + ongoingMultiLineCommentFilter( fromIndex );
801         }
802 
803         // Otherwise, no useful multi-line comment information was found so
804         // pass the line down to the next filter for processesing.
805         else
806         {
807             return stringFilter( line );
808         }
809     }
810 
811     /**
812      * Filters strings from a line of text and formats them properly.
813      *
814      * @param line String
815      * @return String
816      */
817     private String stringFilter( String line )
818     {
819         if ( line == null || line.equals( "" ) )
820         {
821             return "";
822         }
823         StringBuilder buf = new StringBuilder();
824         if ( line.indexOf( '"' ) <= -1 )
825         {
826             return keywordFilter( line );
827         }
828         int start = 0;
829         int startStringIndex = -1;
830         int endStringIndex = -1;
831         int tempIndex;
832         // Keep moving through String characters until we want to stop...
833         while ( ( tempIndex = line.indexOf( '"' ) ) > -1 )
834         {
835             // We found the beginning of a string
836             if ( startStringIndex == -1 )
837             {
838                 startStringIndex = 0;
839                 buf.append( stringFilter( line.substring( start, tempIndex ) ) );
840                 buf.append( STRING_START ).append( '"' );
841                 line = line.substring( tempIndex + 1 );
842             }
843             // Must be at the end
844             else
845             {
846                 startStringIndex = -1;
847                 endStringIndex = tempIndex;
848                 buf.append( line, 0, endStringIndex + 1 );
849                 buf.append( STRING_END );
850                 line = line.substring( endStringIndex + 1 );
851             }
852         }
853 
854         buf.append( keywordFilter( line ) );
855 
856         return buf.toString();
857     }
858 
859     /**
860      * Filters keywords from a line of text and formats them properly.
861      *
862      * @param line String
863      * @return String
864      */
865     private String keywordFilter( String line )
866     {
867         final String classKeyword = "class";
868 
869         if ( line == null || line.equals( "" ) )
870         {
871             return "";
872         }
873         StringBuilder buf = new StringBuilder();
874         int i = 0;
875         char ch;
876         StringBuilder temp = new StringBuilder();
877         while ( i < line.length() )
878         {
879             temp.setLength( 0 );
880             ch = line.charAt( i );
881             while ( i < line.length() && ( ( ch >= 'a' && ch <= 'z' ) || ( ch >= 'A' && ch <= 'Z' ) ) )
882             {
883                 temp.append( ch );
884                 i++;
885                 if ( i < line.length() )
886                 {
887                     ch = line.charAt( i );
888                 }
889             }
890             String tempString = temp.toString();
891 
892             // Special handling of css style class definitions
893             if ( classKeyword.equals( tempString ) && ch == '=' )
894             {
895                 i++;
896             }
897             else if ( reservedWords.containsKey( tempString ) )
898             {
899                 line = line.substring( 0, i - tempString.length() ) + RESERVED_WORD_START
900                         + tempString
901                         + RESERVED_WORD_END
902                         + line.substring( i );
903                 i += ( RESERVED_WORD_START.length() + RESERVED_WORD_END.length() );
904             }
905             else
906             {
907                 i++;
908             }
909         }
910         buf.append( line );
911 
912         return uriFilter( buf.toString() );
913     }
914 
915     /**
916      * Checks to see if some position in a line is between String start and ending characters. Not yet used in code or
917      * fully working :)
918      *
919      * @param line String
920      * @param position int
921      * @return boolean
922      */
923     private boolean isInsideString( String line, int position )
924     {
925         if ( line.indexOf( '"' ) < 0 )
926         {
927             return false;
928         }
929         int index;
930         String left = line.substring( 0, position );
931         String right = line.substring( position );
932         int leftCount = 0;
933         int rightCount = 0;
934         while ( ( index = left.indexOf( '"' ) ) > -1 )
935         {
936             leftCount++;
937             left = left.substring( index + 1 );
938         }
939         while ( ( index = right.indexOf( '"' ) ) > -1 )
940         {
941             rightCount++;
942             right = right.substring( index + 1 );
943         }
944         return ( rightCount % 2 != 0 && leftCount % 2 != 0 );
945     }
946 
947     /**
948      * Description of the Method
949      *
950      * @param oos ObjectOutputStream
951      * @throws IOException
952      */
953     final void writeObject( ObjectOutputStream oos )
954         throws IOException
955     {
956         oos.defaultWriteObject();
957     }
958 
959     /**
960      * Description of the Method
961      *
962      * @param ois ObjectInputStream
963      * @throws ClassNotFoundException
964      * @throws IOException
965      */
966     final void readObject( ObjectInputStream ois )
967         throws ClassNotFoundException, IOException
968     {
969         ois.defaultReadObject();
970     }
971 
972     /**
973      * Get an overview header for this file.
974      *
975      * @return String
976      */
977     private String getFileOverview()
978     {
979         StringBuilder overview = new StringBuilder();
980 
981         // only add the header if javadocs are present
982         if ( javadocLinkDir != null )
983         {
984             overview.append( "<div id=\"overview\">" );
985             // get the URI to get Javadoc info.
986             Path javadocURI;
987 
988             try
989             {
990                 JavaFile jf = fileManager.getFile( this.getCurrentFilename() );
991 
992                 javadocURI = javadocLinkDir.resolve( jf.getPackageType().getName().replace( '.', '/' ) )
993                                 ;
994                 // Use the name of the file instead of the class to handle inner classes properly
995                 String fileName;
996                 if ( jf.getClassType() != null && jf.getClassType().getFilename() != null )
997                 {
998                     fileName = jf.getClassType().getFilename();
999                 }
1000                 else
1001                 {
1002                     fileName = jf.getFilename();
1003                 }
1004                 javadocURI = javadocURI.resolve( fileName + ".html" );
1005 
1006                 String javadocHREF = "<a href=\"" + javadocURI.toString().replace( '\\', '/' ) + "\">View Javadoc</a>";
1007 
1008                 // get the generation time...
1009                 overview.append( javadocHREF );
1010             }
1011             catch ( IOException e )
1012             {
1013                 e.printStackTrace();
1014             }
1015 
1016             overview.append( "</div>" );
1017         }
1018 
1019         return overview.toString();
1020     }
1021 
1022     /**
1023      * Handles line width which may need to change depending on which line number you are on.
1024      *
1025      * @param linenumber int
1026      * @return String
1027      */
1028     private String getLineWidth( int linenumber )
1029     {
1030         if ( linenumber < 10 )
1031         {
1032             return "   ";
1033         }
1034         else if ( linenumber < 100 )
1035         {
1036             return "  ";
1037         }
1038         else
1039         {
1040             return " ";
1041         }
1042     }
1043 
1044     /**
1045      * Handles finding classes based on the current filename and then makes HREFs for you to link to them with.
1046      *
1047      * @param line String
1048      * @return String
1049      */
1050     private String jxrFilter( String line )
1051     {
1052         JavaFile jf;
1053 
1054         try
1055         {
1056             // if the current file isn't set then just return
1057             if ( this.getCurrentFilename() == null )
1058             {
1059                 return line;
1060             }
1061 
1062             jf = fileManager.getFile( this.getCurrentFilename() );
1063         }
1064         catch ( IOException e )
1065         {
1066             e.printStackTrace();
1067             return line;
1068         }
1069 
1070         Set<String> packages = new HashSet<>();
1071 
1072         // get the imported packages
1073         for ( ImportType importType : jf.getImportTypes() )
1074         {
1075             packages.add( importType.getPackage() );
1076         }
1077 
1078         // add the current package.
1079         packages.add( jf.getPackageType().getName() );
1080 
1081         List<StringEntry> words = SimpleWordTokenizer.tokenize( line );
1082 
1083         // go through each word and then match them to the correct class if necessary.
1084         for ( StringEntry word : words )
1085         {
1086             for ( String pkg : packages )
1087             {
1088                 // get the package from the PackageManager because this will hold
1089                 // the version with the classes also.
1090 
1091                 PackageType currentImport = packageManager.getPackageType( pkg );
1092 
1093                 // the package here might in fact be null because it wasn't parsed out
1094                 // this might be something that is either not included or is part
1095                 // of another package and wasn't parsed out.
1096 
1097                 if ( currentImport == null )
1098                 {
1099                     continue;
1100                 }
1101 
1102                 // see if the current word is within the package
1103 
1104                 // at this point the word could be a fully qualified package name
1105                 // (FQPN) or an imported package name.
1106 
1107                 String wordName = word.toString();
1108 
1109                 if ( wordName.indexOf( '.' ) != -1 )
1110                 {
1111                     // if there is a "." in the string then we have to assume
1112                     // it is a package.
1113 
1114                     String fqpnPackage = wordName.substring( 0, wordName.lastIndexOf( '.' ) );
1115                     String fqpnClass = wordName.substring( wordName.lastIndexOf( '.' ) + 1 );
1116 
1117                     // note. since this is a reference to a full package then
1118                     // it doesn't have to be explicitly imported so this information
1119                     // is useless. Instead just see if it was parsed out.
1120 
1121                     PackageType pt = packageManager.getPackageType( fqpnPackage );
1122 
1123                     if ( pt != null )
1124                     {
1125                         ClassType ct = pt.getClassType( fqpnClass );
1126 
1127                         if ( ct != null )
1128                         {
1129                             // OK. the user specified a full package to be imported
1130                             // that is in the package manager so it is time to
1131                             // link to it.
1132 
1133                             line = xrLine( line, pt.getName(), ct );
1134                         }
1135                     }
1136 
1137                     if ( fqpnPackage.equals( currentImport.getName() )
1138                         && currentImport.getClassType( fqpnClass ) != null )
1139                     {
1140                         // then the package we are currently in is the one specified in the string
1141                         // and the import class is correct.
1142                         line = xrLine( line, pkg, currentImport.getClassType( fqpnClass ) );
1143                     }
1144                 }
1145                 else if ( currentImport.getClassType( wordName ) != null )
1146                 {
1147                     line = xrLine( line, pkg, currentImport.getClassType( wordName ) );
1148                 }
1149             }
1150         }
1151 
1152         return importFilter( line );
1153     }
1154 
1155     /**
1156      * Given the current package, get an HREF to the package and class given
1157      *
1158      * @param dest String
1159      * @param jc ClassType
1160      * @return String
1161      */
1162     private String getHREF( String dest, ClassType jc )
1163     {
1164         StringBuilder href = new StringBuilder();
1165 
1166         // find out how to go back to the root
1167         href.append( this.getPackageRoot() );
1168 
1169         // now find out how to get to the dest package
1170         dest = StringUtils.replace( dest, ".*", "" ).replace( '.', '/' );
1171 
1172         href.append( dest );
1173 
1174         // Now append filename.html
1175         if ( jc != null )
1176         {
1177             href.append( '/' );
1178             href.append( jc.getFilename() );
1179             href.append( ".html" );
1180             href.append( '#' );
1181             href.append( jc.getName() );
1182         }
1183 
1184         return href.toString();
1185     }
1186 
1187     /**
1188      * Based on the destination package, get the HREF.
1189      *
1190      * @param dest String
1191      * @return String
1192      */
1193     private String getHREF( String dest )
1194     {
1195         return getHREF( dest, null );
1196     }
1197 
1198     /**
1199      * <p>
1200      * Given the name of a package... get the number of subdirectories/subpackages there would be.
1201      * </p>
1202      * <p>
1203      * EX: <code>org.apache.maven == 3</code>
1204      * </p>
1205      *
1206      * @param packageName String
1207      * @return int
1208      */
1209     private int getPackageCount( String packageName )
1210     {
1211         if ( packageName == null )
1212         {
1213             return 0;
1214         }
1215 
1216         int count = 0;
1217         int index = 0;
1218 
1219         while ( true )
1220         {
1221             index = packageName.indexOf( '.', index );
1222 
1223             if ( index == -1 )
1224             {
1225                 break;
1226             }
1227             ++index;
1228             ++count;
1229         }
1230 
1231         // need to increment this by one
1232         ++count;
1233 
1234         return count;
1235     }
1236 
1237     /**
1238      * Parse out the current link and look for package/import statements and then create HREFs for them
1239      *
1240      * @param line String
1241      * @return String
1242      */
1243     private String importFilter( String line )
1244     {
1245         int start = -1;
1246 
1247         /*
1248          * Used for determining if this is a package declaration. If it is then we can make some additional assumptions:
1249          * - that this isn't a Class import so the full String is valid - that it WILL be on the disk since this is
1250          * based on the current - file.
1251          */
1252         boolean isPackage = line.trim().startsWith( "package " );
1253         boolean isImport = line.trim().startsWith( "import " );
1254 
1255         if ( isImport || isPackage )
1256         {
1257             start = line.trim().indexOf( ' ' );
1258         }
1259 
1260         if ( start != -1 )
1261         {
1262             // filter out this packagename...
1263             String pkg = line.substring( start ).trim();
1264 
1265             // specify the classname of this import if any.
1266             String classname = null;
1267 
1268             if (pkg.contains(".*"))
1269             {
1270                 pkg = StringUtils.replace( pkg, ".*", "" );
1271             }
1272             else if ( !isPackage )
1273             {
1274                 // this is an explicit Class import
1275 
1276                 String packageLine = pkg;
1277 
1278                 // This catches a boundary problem where you have something like:
1279                 //
1280                 // Foo foo = FooMaster.getFooInstance().
1281                 // danceLittleFoo();
1282                 //
1283                 // This breaks Jxr and won't be a problem when we hook
1284                 // in the real parser.
1285 
1286                 int a = packageLine.lastIndexOf( '.' ) + 1;
1287                 int b = packageLine.length() - 1;
1288 
1289                 if ( a > b + 1 )
1290                 {
1291                     classname = packageLine.substring( packageLine.lastIndexOf( '.' ) + 1, packageLine.length() - 1 );
1292 
1293                     int end = pkg.lastIndexOf( '.' );
1294                     if ( end == -1 )
1295                     {
1296                         end = pkg.length() - 1;
1297                     }
1298 
1299                     pkg = pkg.substring( 0, end );
1300                 }
1301             }
1302 
1303             pkg = StringUtils.replace( pkg, ";", "" );
1304             String pkgHREF = getHREF( pkg );
1305             // if this package is within the PackageManager then you can create an HREF for it.
1306 
1307             if ( packageManager.getPackageType( pkg ) != null || isPackage )
1308             {
1309                 // Create an HREF for explicit classname imports
1310                 if ( classname != null )
1311                 {
1312                     line =
1313                         StringUtils.replace( line, classname, "<a href=\"" + pkgHREF + '/' + classname + ".html"
1314                             + "\">" + classname + "</a>" );
1315                 }
1316 
1317                 // now replace the given package with a href
1318                 line =
1319                     StringUtils.replace( line, pkg, "<a href=\"" + pkgHREF + '/' + DirectoryIndexer.INDEX + "\">" + pkg
1320                         + "</a>" );
1321             }
1322 
1323         }
1324 
1325         return line;
1326     }
1327 
1328     /**
1329      * if the given char is not one of the following in VALID_URI_CHARS then return true
1330      *
1331      * @param c char to check against VALID_URI_CHARS list
1332      * @return <code>true</code> if c is a valid URI char
1333      */
1334     private boolean isInvalidURICharacter( char c )
1335     {
1336         for ( char validUriChar : VALID_URI_CHARS )
1337         {
1338             if ( validUriChar == c )
1339             {
1340                 return false;
1341             }
1342         }
1343 
1344         return true;
1345     }
1346 }