/* Copyright (C) 2003 Shayne Steele * License and warranty are under the terms of the GNU General Public License * Version 2.0. See http://www.gnu.org for license and warranty details. */ /** Java2xhtml.java Version 0.9 * Produces an XHTML file from Java source code with syntax highlighting, * includes additional options (line numbering, tab spacing, etc.) *
* NOTE: Common java naming structure is assumed * Capitalize the first letter that appears in a class or interface name * Use lowercase for the first letter in a method or variable name * Use only uppercase letters when naming constants * * @version 0.9, March 2003 * @author Shayne Steele */ package gnu.classpath.tools.java2xhtml; import java.io.*; import java.util.*; public class Java2xhtml { // parse the command line arguments // give a decent responce for bad input // call the HTMLifier on good input public static void main(String args[]) { // parse the invokation arguments if (args.length < 1 || args.length > 3) // invoked program incorrectly { System.out.println("Java2xhtml Version 0.9 (C) 2003 Shayne Steele"); System.out.println(" Produces an XHTML file of Java source" + " code with syntax highlighting,"); System.out.println(" includes additional options " + "(line numbering, tab spacing, etc.)"); System.out.println(" License and warranty are under the terms " + "of the GNU General Public License"); System.out.println(" (GPL) Version 2.0"); System.out.println(" See http://www.gnu.org for license and " + "warranty details."); System.out.println(" NOTE: Common java naming structure is " + "assumed"); System.out.println(""); System.out.println("USAGE:"); System.out.println("java [java options] Java2xhtml " + "source.java [options file] " + "[output file]"); System.out.println(""); System.out.println(" - java is the name of the Java interpreter"); System.out.println(" - [java options] are the optional options " + "of the Java interpreter"); System.out.println(" - Java2xhtml is the name of this " + "application"); System.out.println(" - source is a file or the directory to the " + "Java source file(s)"); System.out.println(" - [options file] is the optional " + "path of a file with"); System.out.println(" a structure like this:"); System.out.println(" externalStyleSheetName=file_name" + " (default style.css)"); System.out.println(" tabSize=integer (default value is 4)"); System.out.println(" extraIndentation=integer " + "(default value is 0)"); System.out.println(" lineModulus=integer (default value 5)"); System.out.println(" isCodeSnippet=boolean" + " (default false)"); System.out.println(" isXHTML_1_1=boolean" + " (default true)"); System.out.println(" hasInternalStyleSheet=boolean" + " (default true)"); System.out.println(" hasExternalStyleSheet=boolean" + " (default true)"); System.out.println(" hasTitle=boolean" + " (default false)"); System.out.println(" hasLegend=boolean" + " (default false)"); System.out.println(" hasAllBoldSourceCode=boolean" + " (default false)"); System.out.println(" hasLineNumbers=boolean" + " (default false)"); System.out.println(" hasLineModulusDrawnLines=boolean" + " (default false)"); System.out.println(" hasLineModulusCodeBlocks=boolean" + " (default false)"); System.out.println(" hasFooter=boolean" + " (default false)"); System.out.println(" hasFooterIcons=boolean" + " (default false)"); System.out.println(" hasFooterDate=boolean" + " (default true)"); System.out.println(" NOTE: filename must end with '.prop'"); System.out.println(" Default [options file] is " + "options.prop"); System.out.println(" - [output file] is name of the XHTML file " + "that is produced"); System.out.println(" Default [output file] is source_java.html"); System.out.println(""); System.out.println("Output: source.java --> [output file]"); System.out.println(" Default Output is "); System.out.println(" source.java --> source_java.html"); System.out.println(""); System.out.println("Examples of calling the program:"); System.out.println(" process one file (say Java2xhtml.java):"); System.out.println(" java Java2xhtml Java2xhtml.java"); System.out.println(" process one directory (say C:\\HOME):"); System.out.println(" java Java2xhtml C:\\HOME"); System.out.println(" process one directory (say C:\\HOME with a " + "given options file (options.prop)):"); System.out.println(" java Java2xhtml C:\\HOME options.prop"); } else { // invoked program correctly, now get command line arguments // get the source file name String sourceName; sourceName = args[0]; // make sure that the source file exist and if so HTMLify it File sourceFilePath = new File(sourceName); if (sourceFilePath.exists()) { // good pathname so HTMLify it // get the default html options file name String propertiesFileName = "options.prop"; // create a unique default html file name, // bubba.java -> bubba_java.html String htmlFileName = sourceName.replace('.', '_') + ".html"; if (args.length == 2 || args.length == 3) { if (args[1].endsWith(".prop")) { // get the user supplied html options file name propertiesFileName = args[1]; } else { // get the user supplied html outputfile name htmlFileName = args[1]; } } if (args.length == 3) { if (args[2].endsWith(".prop")) { // get the user supplied html options file name propertiesFileName = args[2]; } else { // get the user supplied html outputfile name htmlFileName = args[2]; } } new Java2xhtml(propertiesFileName, sourceFilePath, htmlFileName); } else // source file does not exist, print message and exit normally { System.out.println("The source parameter must be an existent" + " file or directory"); System.out.println("Run Java2xHtml without parameters for " + "help"); } } } // collect various sets of keywords static Collection keywordCollection; static Collection primitiveTypeCollection; static Collection primitiveLiteralCollection; static Collection javadocTagCollection; // all these variables are changeable by a options file int extraIndentation = 0; int tabSize = 4; int lineModulus = 5; boolean hasLegend = false; boolean hasLineNumbers = false; boolean hasLineModulusDrawnLines = false; boolean hasLineModulusCodeBlocks = false; boolean hasFooter = false; boolean hasFooterIcons = false; boolean hasFooterDate = true; boolean isCodeSnippet = false; boolean isXHTML_1_1 = true; boolean hasTitle = false; boolean hasAllBoldSourceCode = false; boolean hasInternalStyleSheet = true; boolean hasExternalStyleSheet = true; String externalStyleSheetName = "style.css"; static { // collection type is Hashset for unique elements and fast retieval String keywordArray[] = { "abstract", "default", "if", "private", "do", "implements", "protected", "throws", "break", "import", "public", "transient", "else", "instanceof", "return", "try", "case", "extends", "throw", "static", "catch", "final", "interface", "while", "volatile", "finally", "super", "synchronized", "class", "native", "switch", "package", "const", "for", "new", "goto", "continue", "this", "assert", "strictfp" }; keywordCollection = new HashSet(Arrays.asList(keywordArray)); String primitiveTypeArray[] = { "boolean", "char", "byte", "short", "int", "long", "float", "double", "void" }; primitiveTypeCollection = new HashSet(Arrays.asList(primitiveTypeArray)); String primitiveLiteralArray[]= { "false", "null", "true" }; primitiveLiteralCollection = new HashSet(Arrays.asList(primitiveLiteralArray)); String javadocTagArray[]= { "see", "author", "version", "param", "return", "exception", "deprecated", "throws", "link", "since", "serial", "serialField","serialData", "beaninfo" }; javadocTagCollection = new HashSet(Arrays.asList(javadocTagArray)); } public Java2xhtml() { } // create the various keyword collections // parse the html options file Java2xhtml(String propertiesFileName, File sourceFilePath, String htmlFileName) { // get html properties (use defaults if necessary) File propertiesFilePath = new File (propertiesFileName); if (propertiesFilePath.exists()) { // html properies file exist try parsing it try { InputStream propertiesFile = new FileInputStream(propertiesFileName); Properties htmlProperties = new Properties(); htmlProperties.load(propertiesFile); propertiesFile.close(); setProperties(htmlProperties); } catch (IOException exception) { System.out.println(exception); } } if (sourceFilePath.isFile()) { // process the file processFile(sourceFilePath, htmlFileName); } else if (sourceFilePath.isDirectory()) { // process a directory File [] sourceFilePathArray = sourceFilePath.listFiles(); for (int i = 0; i < sourceFilePathArray.length; i++) { if (((sourceFilePathArray[i]).getName()).endsWith(".java")) { // process each file that ends in .java // create a unique default html file name, // bubba.java -> bubba_java.html htmlFileName = ((sourceFilePathArray[i]).getName()).replace( '.', '_') + ".html"; processFile(sourceFilePathArray[i], htmlFileName); } } } } public void setProperties(Properties htmlProperties) { hasLegend = Boolean.valueOf(htmlProperties.getProperty("hasLegend", "false")).booleanValue(); extraIndentation = Integer.parseInt(htmlProperties.getProperty("extraIndentation", "0")); tabSize = Integer.parseInt(htmlProperties.getProperty("tabSize", "4")); hasLineNumbers = Boolean.valueOf(htmlProperties.getProperty("hasLineNumbers", "false")).booleanValue(); lineModulus = Integer.parseInt(htmlProperties.getProperty("lineModulus", "5")); hasLineModulusDrawnLines = Boolean.valueOf(htmlProperties.getProperty("hasLineModulusDrawnLines", "false")).booleanValue(); hasLineModulusCodeBlocks = Boolean.valueOf(htmlProperties.getProperty("hasLineModulusCodeBlocks", "false")).booleanValue(); hasFooter = Boolean.valueOf(htmlProperties.getProperty("hasFooter", "false")).booleanValue(); hasFooterIcons = Boolean.valueOf(htmlProperties.getProperty("hasFooterIcons", "false")).booleanValue(); hasFooterDate = Boolean.valueOf(htmlProperties.getProperty("hasFooterDate", "true")).booleanValue(); isXHTML_1_1 = Boolean.valueOf(htmlProperties.getProperty("isXHTML_1_1", "true")).booleanValue(); isCodeSnippet = Boolean.valueOf(htmlProperties.getProperty("isCodeSnippet", "false")).booleanValue(); hasTitle = Boolean.valueOf(htmlProperties.getProperty("hasTitle", "false")).booleanValue(); hasAllBoldSourceCode = Boolean.valueOf(htmlProperties.getProperty("hasAllBoldSourceCode", "false")).booleanValue(); hasInternalStyleSheet = Boolean.valueOf(htmlProperties.getProperty("hasInternalStyleSheet", "true")).booleanValue(); hasExternalStyleSheet = Boolean.valueOf(htmlProperties.getProperty("hasExternalStyleSheet", "true")).booleanValue(); externalStyleSheetName = htmlProperties.getProperty("externalStyleSheetName", "style.css"); } // read the file and put it into a stringbuffer void processFile(File sourceFilePath, String htmlFileName) { // open the file, copy it to a Stringbuffer , process into an // HTMLified String and convert result into an HTML file try { BufferedReader sourceReader = new BufferedReader(new FileReader(sourceFilePath)); StringBuffer bufferIn = new StringBuffer(); int readInInt = 0; char presentChar = 0; // copy file into a Stringbuffer while (readInInt != -1) // -1 value means end of stream/file { // put the file into a Stringbuffer readInInt= sourceReader.read(); presentChar = ((readInInt >= 0) ? (char) readInInt : 0); bufferIn.append(presentChar); } sourceReader.close(); BufferedWriter tempBufferedWriter = new BufferedWriter(new FileWriter(htmlFileName)); tempBufferedWriter.write(makeHTML(bufferIn, sourceFilePath.getName())); tempBufferedWriter.close(); System.out.println(sourceFilePath.getName() + " --> " + htmlFileName); } catch (IOException exception) { System.out.println(exception); } } // constant 'States' java source code can be in public final static class State { public final static State TEXT = new State(); public final static State IMPORT_NAME = new State(); public final static State PARAM_VARIABLE = new State(); public final static State JAVADOC = new State(); public final static State PACKAGE_NAME = new State(); public final static State DOUBLE_QUOTE = new State(); public final static State SINGLE_QUOTE = new State(); public final static State TRADITIONAL_COMMENT = new State(); public final static State LINE_COMMENT = new State(); // empty constructor private State() { // empty body } } // Convert java source code StringBufffer into colorized (and tab spaced) // HTML String . // Assumes that Java naming convention is used // Uses a very basic state machine design. public String makeHTML(StringBuffer bufferIn, String sourceFileName) { int codeLineNumber = 0; boolean isNewLine = true; boolean isNewBlock = true; int identifierLength = 0; int qualifiedIdentifierLength = 0; int presentIndex = -1; int spaceLength = 0; int saveIndex = 0; char presentChar = 0; State presentState = State.TEXT; StringBuffer bufferOut = new StringBuffer(); if (!isCodeSnippet) { bufferOut.append("\r\n"); if (isXHTML_1_1) { bufferOut.append("\r\n"); bufferOut.append("\r\n"); } else { bufferOut.append("\r\n"); bufferOut.append("\r\n"); } bufferOut.append("
\r\n"); bufferOut.append("\r\n"); } else { bufferOut.append("\r\n"); } // process the input Java code Stringbuffer // subtract 2 from the bufferIn.length() to get EOF marker while (presentIndex++ != (bufferIn.length() - 2)) { for (int i = 0; i < extraIndentation; i++) { bufferOut.append(" "); } if ((hasLineNumbers || hasLineModulusCodeBlocks) && isNewLine) { // add line numbers if desired // line numbers are 1 - 9999 then rotate line numbers codeLineNumber = (++codeLineNumber)%10000; if ((lineModulus > 0) && hasLineModulusCodeBlocks && (codeLineNumber%lineModulus == 1)) { if (isNewBlock) { if ((State.TRADITIONAL_COMMENT == presentState) || (State.JAVADOC == presentState)) { bufferOut.insert((bufferOut.length() - ("\r\n").length()), ""); } bufferOut.append("\r\n"); bufferOut.append(""); bufferOut.append("\r\n\r\n"); bufferOut.append("\r\n"); if (State.TRADITIONAL_COMMENT == presentState) { bufferOut.append(""); } if (State.JAVADOC == presentState) { bufferOut.append(""); } } isNewBlock = !isNewBlock; } // make straight columns of line numbers if (codeLineNumber < 1000) { bufferOut.append(" "); } if (codeLineNumber < 100) { bufferOut.append(" "); } if (codeLineNumber < 10) { bufferOut.append(" "); } bufferOut.append(""); if (hasLineNumbers) { if ((lineModulus > 0) && (codeLineNumber%lineModulus == 0)) { bufferOut.append(""); bufferOut.append(codeLineNumber); bufferOut.append(": "); if (hasLineModulusDrawnLines) { // compute spaceLength so a line can be drawn while ((presentIndex != (bufferIn.length() - 1)) && ((Character.isSpaceChar( bufferIn.charAt(presentIndex))) || (bufferIn.charAt(presentIndex) == '\t'))) { // for each tab, insert tabSize spaces if (bufferIn.charAt(presentIndex) == '\t') { for (int i = 0; i < tabSize; i++) { bufferIn.insert(presentIndex + 1, " "); } presentIndex++; continue; } if (' ' == bufferIn.charAt(presentIndex)) { // read a space so place a space bufferOut.append(" "); spaceLength += (" ").length(); } else { // a white space character was read bufferOut.append(bufferIn.charAt( presentIndex)); ++spaceLength; } presentIndex++; } // check if line is empty // (no printable characters on line) if ((presentIndex == (bufferIn.length() - 1)) || (Character.isWhitespace(bufferIn.charAt( presentIndex)))) { spaceLength = 0; } // draw the line if (spaceLength > 1) { bufferOut.insert((bufferOut.length() - spaceLength), ""); bufferOut.insert((bufferOut.length() - (" ").length()), ""); } spaceLength = 0; } } else { // line numbers are in lineNumberColor bufferOut.append(""); bufferOut.append(codeLineNumber); bufferOut.append(": "); } } isNewLine = false; } // a state machine presentChar = bufferIn.charAt(presentIndex); if ((Character.isJavaIdentifierPart(presentChar)) || ((State.IMPORT_NAME == presentState) && (presentChar == '*'))) { // this is an identifier bufferOut.append(presentChar); identifierLength++; continue; // keep adding characters until identifier is done } if (identifierLength > 0) { // identifier qualifiedIdentifierLength = qualifiedIdentifierLength + identifierLength; if (bufferIn.charAt(presentIndex) == '.') { // qualified identifier bufferOut.append(presentChar); qualifiedIdentifierLength++; identifierLength = 0; continue; // keep adding characters to qualified identifier } String identifier = bufferOut.toString().substring(bufferOut.length() - identifierLength); if ((State.PARAM_VARIABLE == presentState)) { // any identifier after a param in a javadoc is assumed to // be a variable bufferOut.insert(bufferOut.length() - qualifiedIdentifierLength, ""); bufferOut.append(""); presentState = State.JAVADOC; } else if (State.JAVADOC == presentState) { // in javadoc state if ((javadocTagCollection.contains(identifier)) && (bufferIn.charAt(presentIndex - (identifierLength + 1)) == '@')) { // identifier is a javadocTag bufferOut.insert(bufferOut.length() - identifierLength, ""); bufferOut.append(""); if (("param").equals(identifier)) { // any identifier after a param is assumed to // be a variable, get into a state to do this presentState = State.PARAM_VARIABLE; } } } else if (State.IMPORT_NAME == presentState) { // import identifier bufferOut.insert(bufferOut.length() - qualifiedIdentifierLength, ""); bufferOut.append(""); presentState = State.TEXT; } else if (State.PACKAGE_NAME == presentState) { // package identifier bufferOut.insert(bufferOut.length() - qualifiedIdentifierLength, ""); bufferOut.append(""); presentState = State.TEXT; } else if (State.TEXT == presentState) { if (keywordCollection.contains(identifier)) { // identifier is a keyword bufferOut.insert(bufferOut.length() - qualifiedIdentifierLength, ""); bufferOut.append(""); if (("import").equals(identifier)) { // anything after an import in text mode must be // an import name, so enter state to process this presentState = State.IMPORT_NAME; } else if (("package").equals(identifier)) { // anything after an package in text mode must be // an package name, so enter state to process this presentState = State.PACKAGE_NAME; } } else if (primitiveTypeCollection.contains(identifier)) { // identifier is a primitive type bufferOut.insert(bufferOut.length() - qualifiedIdentifierLength, ""); bufferOut.append(""); } else if ((identifier.equals(identifier.toUpperCase())) && (!(Character.isDigit(identifier.charAt(0))))) { // identifier is a constant bufferOut.insert(bufferOut.length() - qualifiedIdentifierLength, ""); bufferOut.append(""); } else if (Character.isUpperCase(identifier.charAt(0))) { // identifier is a constructor or non-primitive type // eat white space saveIndex = presentIndex; while (Character.isWhitespace( bufferIn.charAt(saveIndex++))) { //empty body } if (bufferIn.charAt(--saveIndex) == '(') { // identifier is a constructor bufferOut.insert(bufferOut.length() - qualifiedIdentifierLength, ""); bufferOut.append(""); } else { // identifier is a non-primitive type bufferOut.insert(bufferOut.length() - qualifiedIdentifierLength, ""); bufferOut.append(""); } } else if (!(Character.isDigit(identifier.charAt(0)) || primitiveLiteralCollection.contains(identifier))) { // identifier is a method or a variable // eat white space saveIndex = presentIndex; while (Character.isWhitespace( bufferIn.charAt(saveIndex++))) { // empty body } --saveIndex; // identifier is a method if (bufferIn.charAt(saveIndex) == '(') { bufferOut.insert(bufferOut.length() - qualifiedIdentifierLength, ""); bufferOut.append(""); } else if (bufferIn.charAt(saveIndex) == ',') { // comma seperated variables bufferOut.insert(bufferOut.length() - qualifiedIdentifierLength, ""); bufferOut.append(""); } else { // a variable // take care of cases such as array[index].variable if (bufferIn.charAt(presentIndex - (qualifiedIdentifierLength + 1)) == '.') { qualifiedIdentifierLength++; } bufferOut.insert(bufferOut.length() - qualifiedIdentifierLength, ""); bufferOut.append(""); } } else { if (primitiveLiteralCollection.contains(identifier)) { // primitiveLiteral (boolean or null) bufferOut.insert(bufferOut.length() - identifierLength, ""); bufferOut.append(""); } // a numerical literal else { if (((presentIndex - (qualifiedIdentifierLength + 1)) > 0) && (bufferIn.charAt(presentIndex - (qualifiedIdentifierLength + 1)) == '.')) { qualifiedIdentifierLength++; } bufferOut.insert(bufferOut.length() - qualifiedIdentifierLength, ""); bufferOut.append(""); } } } qualifiedIdentifierLength = 0; identifierLength = 0; } // process characters NOT in identifiers switch (presentChar) { case '&': //ampersand bufferOut.append("&"); // HTMLify character break; case '<': // less than sign bufferOut.append("<"); // HTMLify character break; case '>': // greater than sign bufferOut.append(">"); // HTMLify character break; case '\"': // double quote bufferOut.append("""); // HTMLify character if (State.TEXT == presentState) { presentState = State.DOUBLE_QUOTE; bufferOut.insert(bufferOut.length()-(""").length(), ""); } else if (State.DOUBLE_QUOTE == presentState) { presentState = State.TEXT; bufferOut.append(""); } break; case '\'': // single quote bufferOut.append("\'"); if (State.TEXT == presentState) { presentState = State.SINGLE_QUOTE; bufferOut.insert(bufferOut.length() - ("\'").length(), ""); } else if (State.SINGLE_QUOTE == presentState) { presentState = State.TEXT; bufferOut.append(""); } break; case '\\': // backslash bufferOut.append("\\"); if ((State.DOUBLE_QUOTE == presentState) || (State.SINGLE_QUOTE == presentState)) { // treat as a character escape sequence bufferOut.append(bufferIn.charAt(++presentIndex)); } break; case '\t': // tab // replace tabs with tabsize number of spaces for (int i = 0; i < tabSize; i++) { bufferOut.append(' '); } break; case '*': // star bufferOut.append("*"); if ((State.TEXT == presentState) && (bufferIn.charAt(presentIndex - 1) == '/')) { if (((bufferIn.length() - 1) > presentIndex) && (bufferIn.charAt(presentIndex + 1) == '*')) { presentState = State.JAVADOC; bufferOut.insert(bufferOut.length() - ("/*").length(), ""); } else { presentState = State.TRADITIONAL_COMMENT; bufferOut.insert(bufferOut.length() - ("/*").length(), ""); } } break; case '/': // foward slash bufferOut.append("/"); if (((State.TRADITIONAL_COMMENT == presentState) || (State.JAVADOC == presentState)) && (bufferIn.charAt(presentIndex - 1) == '*')) { bufferOut.append(""); presentState = State.TEXT; } if ((State.TEXT == presentState) && (presentIndex > 0) && (bufferIn.charAt(presentIndex - 1) == '/')) { bufferOut.insert(bufferOut.length() - ("//").length(), ""); presentState = State.LINE_COMMENT; } break; case '\r': // carriage return // fall through case '\n': // line feed // all HTML lines end in \r\n if ((bufferIn.charAt(presentIndex) == '\r') && ((bufferIn.length() - 1) > presentIndex) && (bufferIn.charAt(presentIndex + 1) == '\n')) { ++presentIndex; } // end single line comments if (State.LINE_COMMENT == presentState) { bufferOut.append(""); presentState = State.TEXT; } // end of block if ((lineModulus > 0) && hasLineModulusCodeBlocks && ((codeLineNumber%lineModulus == 0) && !isNewBlock)) { // end multi-line spanning states if ((State.TRADITIONAL_COMMENT == presentState) || (State.JAVADOC == presentState)) { bufferOut.append(""); } bufferOut.append("\r\n"); bufferOut.append("
\r\n"); bufferOut.append("\r\n"); // restart multi-line spanning states if (State.TRADITIONAL_COMMENT == presentState) { bufferOut.append(""); } if (State.JAVADOC == presentState) { bufferOut.append(""); } } else { // div automatically starts new line bufferOut.append("\r\n"); } isNewLine = true; break; case 0: // nul character if ((State.LINE_COMMENT == presentState) && (presentIndex == (bufferIn.length() - 1))) { bufferOut.append(""); } break; default: // everything else bufferOut.append(presentChar); } qualifiedIdentifierLength = 0; } if (presentState == State.LINE_COMMENT) { bufferOut.append("\r\n"); } bufferOut.append("
\r\n"); // end block early if no more source code if ((lineModulus > 0) && hasLineModulusCodeBlocks && !isNewBlock && (codeLineNumber%lineModulus != 0)) { bufferOut.append("