blob: 835763707fb60aa9528e666ed579f86a835203d4 [file] [log] [blame]
package org.apache.maven.jxr;
/*
* CodeViewer.java
* CoolServlets.com
* March 2000
*
* Copyright (C) 2000 CoolServlets.com
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1) Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2) Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3) Neither the name CoolServlets.com nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY COOLSERVLETS.COM AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL COOLSERVLETS.COM OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import org.apache.commons.lang.StringUtils;
import org.apache.maven.jxr.pacman.ClassType;
import org.apache.maven.jxr.pacman.FileManager;
import org.apache.maven.jxr.pacman.ImportType;
import org.apache.maven.jxr.pacman.JavaFile;
import org.apache.maven.jxr.pacman.PackageManager;
import org.apache.maven.jxr.pacman.PackageType;
import org.apache.maven.jxr.util.SimpleWordTokenizer;
import org.apache.maven.jxr.util.StringEntry;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Serializable;
import java.io.Writer;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Vector;
/**
* Syntax highlights java by turning it into html. A codeviewer object is created and then keeps state as lines are
* passed in. Each line passed in as java test, is returned as syntax highlighted html text. Users of the class can set
* how the java code will be highlighted with setter methods. Only valid java lines should be passed in since the object
* maintains state and may not handle illegal code gracefully. The actual system is implemented as a series of filters
* that deal with specific portions of the java code. The filters are as follows:
*
* <pre>
* htmlFilter
* |__
* ongoingMultiLineCommentFilter -> uriFilter
* |__
* inlineCommentFilter
* |__
* beginMultiLineCommentFilter -> ongoingMultiLineCommentFilter
* |__
* stringFilter
* |__
* keywordFilter
* |__
* uriFilter
* |__
* jxrFilter
* |__
* importFilter
* </pre>
*/
public class JavaCodeTransform
implements Serializable
{
// ----------------------------------------------------------------------
// public fields
// ----------------------------------------------------------------------
/**
* show line numbers
*/
public static final boolean LINE_NUMBERS = true;
/**
* start comment delimiter
*/
public static final String COMMENT_START = "<em class=\"jxr_comment\">";
/**
* end comment delimiter
*/
public static final String COMMENT_END = "</em>";
/**
* start javadoc comment delimiter
*/
public static final String JAVADOC_COMMENT_START = "<em class=\"jxr_javadoccomment\">";
/**
* end javadoc comment delimiter
*/
public static final String JAVADOC_COMMENT_END = "</em>";
/**
* start String delimiter
*/
public static final String STRING_START = "<span class=\"jxr_string\">";
/**
* end String delimiter
*/
public static final String STRING_END = "</span>";
/**
* start reserved word delimiter
*/
public static final String RESERVED_WORD_START = "<strong class=\"jxr_keyword\">";
/**
* end reserved word delimiter
*/
public static final String RESERVED_WORD_END = "</strong>";
/**
* stylesheet file name
*/
public static final String STYLESHEET_FILENAME = "stylesheet.css";
/**
* Description of the Field
*/
public static final String[] VALID_URI_SCHEMES = { "http://", "mailto:" };
/**
* Specify the only characters that are allowed in a URI besides alpha and numeric characters. Refer RFC2396 -
* http://www.ietf.org/rfc/rfc2396.txt
*/
public static final char[] VALID_URI_CHARS = { '?', '+', '%', '&', ':', '/', '.', '@', '_', ';', '=', '$', ',',
'-', '!', '~', '*', '\'', '(', ')' };
// ----------------------------------------------------------------------
// private fields
// ----------------------------------------------------------------------
/**
* HashTable containing java reserved words
*/
private Hashtable<String, String> reservedWords = new Hashtable<String, String>();
/**
* flag set to true when a multi-line comment is started
*/
private boolean inMultiLineComment = false;
/**
* flag set to true when a javadoc comment is started
*/
private boolean inJavadocComment = false;
/**
* Set the filename that is currently being processed.
*/
private String currentFilename = null;
/**
* The current CVS revision of the currently transformed document
*/
private String revision = null;
/**
* The currently being transformed source file
*/
private String sourcefile = null;
/**
* The currently being written destination file
*/
private String destfile = null;
/**
* The virtual source directory that is being read from: <code>src/java</code>
*/
private String sourcedir = null;
/**
* The input encoding
*/
private String inputEncoding = null;
/**
* The output encoding
*/
private String outputEncoding = null;
/**
* The wanted locale
*/
private Locale locale = null;
/**
* Relative path to javadocs, suitable for hyperlinking
*/
private String javadocLinkDir;
/**
* Package Manager for this project.
*/
private PackageManager packageManager;
/**
* current file manager
*/
private FileManager fileManager;
// ----------------------------------------------------------------------
// constructor
// ----------------------------------------------------------------------
/**
* Constructor for the JavaCodeTransform object
*
* @param packageManager PackageManager for this project
*/
public JavaCodeTransform( PackageManager packageManager )
{
this.packageManager = packageManager;
loadHash();
this.fileManager = packageManager.getFileManager();
}
// ----------------------------------------------------------------------
// public methods
// ----------------------------------------------------------------------
/**
* Now different method of seeing if at end of input stream, closes inputs stream at end.
*
* @param line String
* @return filtered line of code
*/
public final String syntaxHighlight( String line )
{
return htmlFilter( line );
}
/**
* Gets the header attribute of the JavaCodeTransform object
*
* @param out the writer where the header is appended to
* @return String
*/
public void appendHeader( PrintWriter out )
{
String outputEncoding = this.outputEncoding;
if ( outputEncoding == null )
{
outputEncoding = "ISO-8859-1";
}
// header
out.println( "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" "
+ "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">" );
out.print( "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"" );
out.print( locale );
out.print( "\" lang=\"" );
out.print( locale );
out.println( "\">" );
out.print( "<head>" );
out.print( "<meta http-equiv=\"content-type\" content=\"text/html; charset=" );
out.print( outputEncoding );
out.println( "\" />" );
// title ("classname xref")
out.print( "<title>" );
try
{
JavaFile javaFile = fileManager.getFile( this.getCurrentFilename() );
// Use the name of the file instead of the class to handle inner classes properly
if ( javaFile.getClassType() != null && javaFile.getClassType().getFilename() != null )
{
out.print( javaFile.getClassType().getFilename() );
}
else
{
out.print( this.getCurrentFilename() );
}
out.print( " " );
}
catch ( IOException e )
{
e.printStackTrace();
}
finally
{
out.println( "xref</title>" );
}
// stylesheet link
out.print( "<link type=\"text/css\" rel=\"stylesheet\" href=\"" );
out.print( this.getPackageRoot() );
out.print( STYLESHEET_FILENAME );
out.println( "\" />" );
out.println( "</head>" );
out.println( "<body>" );
out.print( this.getFileOverview() );
// start code section
out.println( "<pre>" );
}
/**
* Gets the footer attribute of the JavaCodeTransform object
*
* @param out the writer where the header is appended to
* @param bottom the bottom text
* @return String
*/
public final void appendFooter( PrintWriter out, String bottom )
{
out.println( "</pre>" );
out.println( "<hr/>" );
out.print( "<div id=\"footer\">" );
out.print( bottom );
out.println( "</div>" );
out.println( "</body>" );
out.println( "</html>" );
}
/**
* This is the public method for doing all transforms of code.
*
* @param sourceReader Reader
* @param destWriter Writer
* @param locale String
* @param inputEncoding String
* @param outputEncoding String
* @param javadocLinkDir String
* @param revision String
* @param bottom string
* @throws IOException
*/
public final void transform( Reader sourceReader, Writer destWriter, Locale locale, String inputEncoding,
String outputEncoding, String javadocLinkDir, String revision, String bottom )
throws IOException
{
this.locale = locale;
this.inputEncoding = inputEncoding;
this.outputEncoding = outputEncoding;
this.javadocLinkDir = javadocLinkDir;
this.revision = revision;
BufferedReader in = new BufferedReader( sourceReader );
PrintWriter out = new PrintWriter( destWriter );
String line = "";
appendHeader( out );
int linenumber = 1;
while ( ( line = in.readLine() ) != null )
{
if ( LINE_NUMBERS )
{
out.print( "<a class=\"jxr_linenumber\" name=\"L" + linenumber + "\" " + "href=\"#L" + linenumber
+ "\">" + linenumber + "</a>" + getLineWidth( linenumber ) );
}
out.println( this.syntaxHighlight( line ) );
++linenumber;
}
appendFooter( out, bottom );
out.flush();
}
/**
* This is the public method for doing all transforms of code.
*
* @param sourcefile String
* @param destfile String
* @param locale String
* @param inputEncoding String
* @param outputEncoding String
* @param javadocLinkDir String
* @param revision String
* @param bottom TODO
* @throws IOException
*/
public final void transform( String sourcefile, String destfile, Locale locale, String inputEncoding,
String outputEncoding, String javadocLinkDir, String revision, String bottom )
throws IOException
{
this.setCurrentFilename( sourcefile );
this.sourcefile = sourcefile;
this.destfile = destfile;
// make sure that the parent directories exist...
new File( new File( destfile ).getParent() ).mkdirs();
Reader fr = null;
Writer fw = null;
try
{
if ( inputEncoding != null )
{
fr = new InputStreamReader( new FileInputStream( sourcefile ), inputEncoding );
}
else
{
fr = new FileReader( sourcefile );
}
if ( outputEncoding != null )
{
fw = new OutputStreamWriter( new FileOutputStream( destfile ), outputEncoding );
}
else
{
fw = new FileWriter( destfile );
}
transform( fr, fw, locale, inputEncoding, outputEncoding, javadocLinkDir, revision, bottom );
}
catch ( RuntimeException e )
{
System.out.println( "Unable to processPath " + sourcefile + " => " + destfile );
throw e;
}
finally
{
if ( fr != null )
{
try
{
fr.close();
}
catch ( Exception ex )
{
ex.printStackTrace();
}
}
if ( fw != null )
{
try
{
fw.close();
}
catch ( Exception ex )
{
ex.printStackTrace();
}
}
}
}
/**
* Get the current filename
*
* @return String
*/
public final String getCurrentFilename()
{
return this.currentFilename;
}
/**
* Set the current filename
*
* @param filename String
*/
public final void setCurrentFilename( String filename )
{
this.currentFilename = filename;
}
/**
* From the current file, determine the package root based on the current path.
*
* @return String
*/
public final String getPackageRoot()
{
StringBuffer buff = new StringBuffer();
JavaFile jf = null;
try
{
jf = fileManager.getFile( this.getCurrentFilename() );
}
catch ( IOException e )
{
e.printStackTrace();
return null;
}
String current = jf.getPackageType().getName();
int count = this.getPackageCount( current );
for ( int i = 0; i < count; ++i )
{
buff.append( "../" );
}
return buff.toString();
}
/**
* Given a line of text, search for URIs and make href's out of them
*
* @param line String
* @return String
*/
public final String uriFilter( String line )
{
for ( int i = 0; i < VALID_URI_SCHEMES.length; ++i )
{
String scheme = VALID_URI_SCHEMES[i];
int index = line.indexOf( scheme );
if ( index != -1 )
{
int start = index;
int end = -1;
for ( int j = start; j < line.length(); ++j )
{
char current = line.charAt( j );
if ( !Character.isLetterOrDigit( current ) && isInvalidURICharacter( current ) )
{
end = j;
break;
}
end = j;
}
// now you should have the full URI so you can replace this
// in the current buffer
if ( end != -1 )
{
String uri = line.substring( start, end );
line =
StringUtils.replace( line, uri, "<a href=\"" + uri + "\" target=\"alexandria_uri\">" + uri
+ "</a>" );
}
}
}
// if we are in a multiline comment we should not call JXR here.
if ( !inMultiLineComment && !inJavadocComment )
{
return jxrFilter( line );
}
return line;
}
/**
* The current revision of the CVS module
*
* @return String
*/
public final String getRevision()
{
return this.revision;
}
/**
* The current source file being read
*
* @return source file name
*/
public final String getSourcefile()
{
return this.sourcefile;
}
/**
* The current destination file being written
*
* @return destination file name
*/
public final String getDestfile()
{
return this.destfile;
}
/**
* The current source directory being read from.
*
* @return source directory
*/
public final String getSourceDirectory()
{
return this.sourcedir;
}
/**
* Cross Reference the given line with JXR returning the new content.
*
* @param line String
* @param packageName String
* @param classType ClassType
* @return String
*/
public final String xrLine( String line, String packageName, ClassType classType )
{
StringBuffer buff = new StringBuffer( line );
String link = null;
String find = null;
String href = null;
if ( classType != null )
{
href = this.getHREF( packageName, classType );
find = classType.getName();
// build out what the link would be.
link = "<a name=\"" + find + "\" href=\"" + href + "\">" + find + "</a>";
}
else
{
href = this.getHREF( packageName );
find = packageName;
// build out what the link would be.
link = "<a href=\"" + href + "\">" + find + "</a>";
}
// use the SimpleWordTokenizer to find all entries
// that match word. Then replace these with the link
// now replace the word in the buffer with the link
String replace = link;
StringEntry[] tokens = SimpleWordTokenizer.tokenize( buff.toString(), find );
for ( int l = 0; l < tokens.length; ++l )
{
int start = tokens[l].getIndex();
int end = tokens[l].getIndex() + find.length();
buff.replace( start, end, replace );
}
return buff.toString();
}
/**
* Highlight the package in this line.
*
* @param line input line
* @param packageName package name
* @return input line with linked package
*/
public final String xrLine( String line, String packageName )
{
String href = this.getHREF( packageName );
String find = packageName;
// build out what the link would be.
String link = "<a href=\"" + href + "\">" + find + "</a>";
return StringUtils.replace( line, find, link );
}
// ----------------------------------------------------------------------
// private methods
// ----------------------------------------------------------------------
/**
* Filter html tags into more benign text.
*
* @param line String
* @return html encoded line
*/
private String htmlFilter( String line )
{
if ( line == null || line.equals( "" ) )
{
return "";
}
line = replace( line, "&", "&amp;" );
line = replace( line, "<", "&lt;" );
line = replace( line, ">", "&gt;" );
line = replace( line, "\\\\", "&#92;&#92;" );
line = replace( line, "\\\"", "\\&quot;" );
line = replace( line, "'\"'", "'&quot;'" );
return ongoingMultiLineCommentFilter( line );
}
/**
* Handle ongoing multi-line comments, detecting ends if present. State is maintained in private boolean members,
* one each for javadoc and (normal) multiline comments.
*
* @param line String
* @return String
*/
private String ongoingMultiLineCommentFilter( String line )
{
if ( line == null || line.equals( "" ) )
{
return "";
}
final String[] tags =
inJavadocComment ? new String[] { JAVADOC_COMMENT_START, JAVADOC_COMMENT_END }
: inMultiLineComment ? new String[] { COMMENT_START, COMMENT_END } : null;
if ( tags == null )
{
// pass the line down to the next filter for processing.
return inlineCommentFilter( line );
}
int index = line.indexOf( "*/" );
// only filter the portion without the end-of-comment,
// since * and / seem to be valid URI characters
String comment = uriFilter( index < 0 ? line : line.substring( 0, index ) );
if ( index >= 0 )
{
inJavadocComment = false;
inMultiLineComment = false;
}
StringBuilder buf = new StringBuilder( tags[0] ).append( comment );
if ( index >= 0 )
{
buf.append( "*/" );
}
buf.append( tags[1] );
if ( index >= 0 && line.length() > index + 2 )
{
buf.append( inlineCommentFilter( line.substring( index + 2 ) ) );
}
return buf.toString();
}
/**
* Filter inline comments from a line and formats them properly. One problem we'll have to solve here: comments
* contained in a string should be ignored... this is also true of the multi-line comments. So, we could either
* ignore the problem, or implement a function called something like isInsideString(line, index) where index points
* to some point in the line that we need to check... started doing this function below.
*
* @param line String
* @return String
*/
private String inlineCommentFilter( String line )
{
// assert !inJavadocComment;
// assert !inMultiLineComment;
if ( line == null || line.equals( "" ) )
{
return "";
}
int index = line.indexOf( "//" );
if ( ( index >= 0 ) && !isInsideString( line, index ) )
{
return new StringBuffer( beginMultiLineCommentFilter( line.substring( 0, index ) ) ).append( COMMENT_START ).append( line.substring( index ) ).append( COMMENT_END ).toString();
}
return beginMultiLineCommentFilter( line );
}
/**
* Detect and handle the start of multiLine comments. State is maintained in private boolean members one each for
* javadoc and (normal) multiline comments.
*
* @param line String
* @return String
*/
private String beginMultiLineCommentFilter( String line )
{
// assert !inJavadocComment;
// assert !inMultiLineComment;
if ( line == null || line.equals( "" ) )
{
return "";
}
int index = line.indexOf( "/*" );
// check to see if a multi-line comment starts on this line:
if ( ( index > -1 ) && !isInsideString( line, index ) )
{
String fromIndex = line.substring( index );
if ( fromIndex.startsWith( "/**" ) && !( fromIndex.startsWith( "/**/" ) ) )
{
inJavadocComment = true;
}
else
{
inMultiLineComment = true;
}
// Return result of other filters + everything after the start
// of the multiline comment. We need to pass the through the
// to the ongoing multiLineComment filter again in case the comment
// ends on the same line.
return new StringBuilder( stringFilter( line.substring( 0, index ) ) ).append( ongoingMultiLineCommentFilter( fromIndex ) ).toString();
}
// Otherwise, no useful multi-line comment information was found so
// pass the line down to the next filter for processesing.
else
{
return stringFilter( line );
}
}
/**
* Filters strings from a line of text and formats them properly.
*
* @param line String
* @return String
*/
private String stringFilter( String line )
{
if ( line == null || line.equals( "" ) )
{
return "";
}
StringBuffer buf = new StringBuffer();
if ( line.indexOf( "\"" ) <= -1 )
{
return keywordFilter( line );
}
int start = 0;
int startStringIndex = -1;
int endStringIndex = -1;
int tempIndex;
// Keep moving through String characters until we want to stop...
while ( ( tempIndex = line.indexOf( "\"" ) ) > -1 )
{
// We found the beginning of a string
if ( startStringIndex == -1 )
{
startStringIndex = 0;
buf.append( stringFilter( line.substring( start, tempIndex ) ) );
buf.append( STRING_START ).append( "\"" );
line = line.substring( tempIndex + 1 );
}
// Must be at the end
else
{
startStringIndex = -1;
endStringIndex = tempIndex;
buf.append( line.substring( 0, endStringIndex + 1 ) );
buf.append( STRING_END );
line = line.substring( endStringIndex + 1 );
}
}
buf.append( keywordFilter( line ) );
return buf.toString();
}
/**
* Filters keywords from a line of text and formats them properly.
*
* @param line String
* @return String
*/
private String keywordFilter( String line )
{
final String classKeyword = "class";
if ( line == null || line.equals( "" ) )
{
return "";
}
StringBuffer buf = new StringBuffer();
int i = 0;
char ch;
StringBuffer temp = new StringBuffer();
while ( i < line.length() )
{
temp.setLength( 0 );
ch = line.charAt( i );
while ( i < line.length() && ( ( ch >= 'a' && ch <= 'z' ) || ( ch >= 'A' && ch <= 'Z' ) ) )
{
temp.append( ch );
i++;
if ( i < line.length() )
{
ch = line.charAt( i );
}
}
String tempString = temp.toString();
// Special handling of css style class definitions
if ( classKeyword.equals( tempString ) && ch == '=' )
{
i++;
}
else if ( reservedWords.containsKey( tempString ) )
{
StringBuffer newLine = new StringBuffer( line.substring( 0, i - tempString.length() ) );
newLine.append( RESERVED_WORD_START );
newLine.append( tempString );
newLine.append( RESERVED_WORD_END );
newLine.append( line.substring( i ) );
line = newLine.toString();
i += ( RESERVED_WORD_START.length() + RESERVED_WORD_END.length() );
}
else
{
i++;
}
}
buf.append( line );
return uriFilter( buf.toString() );
}
/**
* Replace... I made it use a <code>StringBuffer</code>... hope it still works :)
*
* @param line String
* @param oldString String
* @param newString String
* @return String
*/
private String replace( String line, String oldString, String newString )
{
int i = 0;
while ( ( i = line.indexOf( oldString, i ) ) >= 0 )
{
line =
( new StringBuffer().append( line.substring( 0, i ) ).append( newString ).append( line.substring( i
+ oldString.length() ) ) ).toString();
i += newString.length();
}
return line;
}
/**
* Checks to see if some position in a line is between String start and ending characters. Not yet used in code or
* fully working :)
*
* @param line String
* @param position int
* @return boolean
*/
private boolean isInsideString( String line, int position )
{
if ( line.indexOf( '"' ) < 0 )
{
return false;
}
int index;
String left = line.substring( 0, position );
String right = line.substring( position );
int leftCount = 0;
int rightCount = 0;
while ( ( index = left.indexOf( '"' ) ) > -1 )
{
leftCount++;
left = left.substring( index + 1 );
}
while ( ( index = right.indexOf( '"' ) ) > -1 )
{
rightCount++;
right = right.substring( index + 1 );
}
return ( rightCount % 2 != 0 && leftCount % 2 != 0 );
}
/**
* Description of the Method
*/
private void loadHash()
{
reservedWords.put( "abstract", "abstract" );
reservedWords.put( "do", "do" );
reservedWords.put( "inner", "inner" );
reservedWords.put( "public", "public" );
reservedWords.put( "var", "var" );
reservedWords.put( "boolean", "boolean" );
reservedWords.put( "continue", "continue" );
reservedWords.put( "int", "int" );
reservedWords.put( "return", "return" );
reservedWords.put( "void", "void" );
reservedWords.put( "break", "break" );
reservedWords.put( "else", "else" );
reservedWords.put( "interface", "interface" );
reservedWords.put( "short", "short" );
reservedWords.put( "volatile", "volatile" );
reservedWords.put( "byvalue", "byvalue" );
reservedWords.put( "extends", "extends" );
reservedWords.put( "long", "long" );
reservedWords.put( "static", "static" );
reservedWords.put( "while", "while" );
reservedWords.put( "case", "case" );
reservedWords.put( "final", "final" );
reservedWords.put( "native", "native" );
reservedWords.put( "super", "super" );
reservedWords.put( "transient", "transient" );
reservedWords.put( "cast", "cast" );
reservedWords.put( "float", "float" );
reservedWords.put( "new", "new" );
reservedWords.put( "rest", "rest" );
reservedWords.put( "catch", "catch" );
reservedWords.put( "for", "for" );
reservedWords.put( "null", "null" );
reservedWords.put( "synchronized", "synchronized" );
reservedWords.put( "char", "char" );
reservedWords.put( "finally", "finally" );
reservedWords.put( "operator", "operator" );
reservedWords.put( "this", "this" );
reservedWords.put( "class", "class" );
reservedWords.put( "generic", "generic" );
reservedWords.put( "outer", "outer" );
reservedWords.put( "switch", "switch" );
reservedWords.put( "const", "const" );
reservedWords.put( "goto", "goto" );
reservedWords.put( "package", "package" );
reservedWords.put( "throw", "throw" );
reservedWords.put( "double", "double" );
reservedWords.put( "if", "if" );
reservedWords.put( "private", "private" );
reservedWords.put( "true", "true" );
reservedWords.put( "default", "default" );
reservedWords.put( "import", "import" );
reservedWords.put( "protected", "protected" );
reservedWords.put( "try", "try" );
reservedWords.put( "throws", "throws" );
reservedWords.put( "implements", "implements" );
}
/**
* Description of the Method
*
* @param oos ObjectOutputStream
* @throws IOException
*/
final void writeObject( ObjectOutputStream oos )
throws IOException
{
oos.defaultWriteObject();
}
/**
* Description of the Method
*
* @param ois ObjectInputStream
* @throws ClassNotFoundException
* @throws IOException
*/
final void readObject( ObjectInputStream ois )
throws ClassNotFoundException, IOException
{
ois.defaultReadObject();
}
/**
* Get an overview header for this file.
*
* @return String
*/
private String getFileOverview()
{
StringBuffer overview = new StringBuffer();
// only add the header if javadocs are present
if ( javadocLinkDir != null )
{
overview.append( "<div id=\"overview\">" );
// get the URI to get Javadoc info.
StringBuffer javadocURI = new StringBuffer().append( javadocLinkDir );
try
{
JavaFile jf = fileManager.getFile( this.getCurrentFilename() );
javadocURI.append( StringUtils.replace( jf.getPackageType().getName(), ".", "/" ) );
javadocURI.append( "/" );
// Use the name of the file instead of the class to handle inner classes properly
if ( jf.getClassType() != null && jf.getClassType().getFilename() != null )
{
javadocURI.append( jf.getClassType().getFilename() );
}
else
{
return "";
}
javadocURI.append( ".html" );
}
catch ( IOException e )
{
e.printStackTrace();
}
String javadocHREF = "<a href=\"" + javadocURI + "\">View Javadoc</a>";
// get the generation time...
overview.append( javadocHREF );
overview.append( "</div>" );
}
return overview.toString();
}
/**
* Handles line width which may need to change depending on which line number you are on.
*
* @param linenumber int
* @return String
*/
private String getLineWidth( int linenumber )
{
if ( linenumber < 10 )
{
return " ";
}
else if ( linenumber < 100 )
{
return " ";
}
else
{
return " ";
}
}
/**
* Handles finding classes based on the current filename and then makes HREFs for you to link to them with.
*
* @param line String
* @return String
*/
private String jxrFilter( String line )
{
JavaFile jf = null;
try
{
// if the current file isn't set then just return
if ( this.getCurrentFilename() == null )
{
return line;
}
jf = fileManager.getFile( this.getCurrentFilename() );
}
catch ( IOException e )
{
e.printStackTrace();
return line;
}
Vector<String> v = new Vector<String>();
// get the imported packages
ImportType[] imports = jf.getImportTypes();
for ( int j = 0; j < imports.length; ++j )
{
v.addElement( imports[j].getPackage() );
}
// add the current package.
v.addElement( jf.getPackageType().getName() );
String[] packages = new String[v.size()];
v.copyInto( packages );
StringEntry[] words = SimpleWordTokenizer.tokenize( line );
// go through each word and then match them to the correct class if necessary.
for ( int i = 0; i < words.length; ++i )
{
// each word
StringEntry word = words[i];
for ( int j = 0; j < packages.length; ++j )
{
// get the package from the PackageManager because this will hold
// the version with the classes also.
PackageType currentImport = packageManager.getPackageType( packages[j] );
// the package here might in fact be null because it wasn't parsed out
// this might be something that is either not included or is part
// of another package and wasn't parsed out.
if ( currentImport == null )
{
continue;
}
// see if the current word is within the package
// at this point the word could be a fully qualified package name
// (FQPN) or an imported package name.
String wordName = word.toString();
if ( wordName.indexOf( "." ) != -1 )
{
// if there is a "." in the string then we have to assume
// it is a package.
String fqpnPackage = null;
String fqpnClass = null;
fqpnPackage = wordName.substring( 0, wordName.lastIndexOf( "." ) );
fqpnClass = wordName.substring( wordName.lastIndexOf( "." ) + 1, wordName.length() );
// note. since this is a reference to a full package then
// it doesn't have to be explicitly imported so this information
// is useless. Instead just see if it was parsed out.
PackageType pt = packageManager.getPackageType( fqpnPackage );
if ( pt != null )
{
ClassType ct = pt.getClassType( fqpnClass );
if ( ct != null )
{
// OK. the user specified a full package to be imported
// that is in the package manager so it is time to
// link to it.
line = xrLine( line, pt.getName(), ct );
}
}
if ( fqpnPackage.equals( currentImport.getName() )
&& currentImport.getClassType( fqpnClass ) != null )
{
// then the package we are currently in is the one specified in the string
// and the import class is correct.
line = xrLine( line, packages[j], currentImport.getClassType( fqpnClass ) );
}
}
else if ( currentImport.getClassType( wordName ) != null )
{
line = xrLine( line, packages[j], currentImport.getClassType( wordName ) );
}
}
}
return importFilter( line );
}
/**
* Given the current package, get an HREF to the package and class given
*
* @param dest String
* @param jc ClassType
* @return String
*/
private String getHREF( String dest, ClassType jc )
{
StringBuffer href = new StringBuffer();
// find out how to go back to the root
href.append( this.getPackageRoot() );
// now find out how to get to the dest package
dest = StringUtils.replace( dest, ".*", "" );
dest = StringUtils.replace( dest, ".", "/" );
href.append( dest );
// Now append filename.html
if ( jc != null )
{
href.append( "/" );
href.append( jc.getFilename() );
href.append( ".html" );
href.append('#');
href.append( jc.getName() );
}
return href.toString();
}
/**
* Based on the destination package, get the HREF.
*
* @param dest String
* @return String
*/
private String getHREF( String dest )
{
return getHREF( dest, null );
}
/**
* <p>
* Given the name of a package... get the number of subdirectories/subpackages there would be.
* </p>
* <p>
* EX: <code>org.apache.maven == 3</code>
* </p>
*
* @param packageName String
* @return int
*/
private int getPackageCount( String packageName )
{
if ( packageName == null )
{
return 0;
}
int count = 0;
int index = 0;
while ( true )
{
index = packageName.indexOf( '.', index );
if ( index == -1 )
{
break;
}
++index;
++count;
}
// need to increment this by one
++count;
return count;
}
/**
* Parse out the current link and look for package/import statements and then create HREFs for them
*
* @param line String
* @return String
*/
private String importFilter( String line )
{
int start = -1;
/*
* Used for determining if this is a package declaration. If it is then we can make some additional assumptions:
* - that this isn't a Class import so the full String is valid - that it WILL be on the disk since this is
* based on the current - file.
*/
boolean isPackage = line.trim().startsWith( "package " );
boolean isImport = line.trim().startsWith( "import " );
if ( isImport || isPackage )
{
start = line.trim().indexOf( " " );
}
if ( start != -1 )
{
// filter out this packagename...
String pkg = line.substring( start, line.length() ).trim();
// specify the classname of this import if any.
String classname = null;
if ( pkg.indexOf( ".*" ) != -1 )
{
pkg = StringUtils.replace( pkg, ".*", "" );
}
else if ( !isPackage )
{
// this is an explicit Class import
String packageLine = pkg.toString();
// This catches a boundary problem where you have something like:
//
// Foo foo = FooMaster.getFooInstance().
// danceLittleFoo();
//
// This breaks Jxr and won't be a problem when we hook
// in the real parser.
int a = packageLine.lastIndexOf( "." ) + 1;
int b = packageLine.length() - 1;
if ( a > b + 1 )
{
classname = packageLine.substring( packageLine.lastIndexOf( "." ) + 1, packageLine.length() - 1 );
int end = pkg.lastIndexOf( "." );
if ( end == -1 )
{
end = pkg.length() - 1;
}
pkg = pkg.substring( 0, end );
}
}
pkg = StringUtils.replace( pkg, ";", "" );
String pkgHREF = getHREF( pkg );
// if this package is within the PackageManager then you can create an HREF for it.
if ( packageManager.getPackageType( pkg ) != null || isPackage )
{
// Create an HREF for explicit classname imports
if ( classname != null )
{
line =
StringUtils.replace( line, classname, "<a href=\"" + pkgHREF + "/" + classname + ".html"
+ "\">" + classname + "</a>" );
}
// now replace the given package with a href
line =
StringUtils.replace( line, pkg, "<a href=\"" + pkgHREF + "/" + DirectoryIndexer.INDEX + "\">" + pkg
+ "</a>" );
}
}
return line;
}
/**
* if the given char is not one of the following in VALID_URI_CHARS then return true
*
* @param c char to check against VALID_URI_CHARS list
* @return <code>true</code> if c is a valid URI char
*/
private boolean isInvalidURICharacter( char c )
{
for ( int i = 0; i < VALID_URI_CHARS.length; ++i )
{
if ( VALID_URI_CHARS[i] == c )
{
return false;
}
}
return true;
}
}