diff --git a/dist/releaseNotes.html b/dist/releaseNotes.html index 0e6e236..81d63da 100644 --- a/dist/releaseNotes.html +++ b/dist/releaseNotes.html @@ -111,6 +111,15 @@

SchemaSpy Release Notes

information or not (MySQL does not). Thanks to Reiner Kr�utle for the suggestion. +
  • Added the ability to specify a custom SQL formatter via the -sqlFormatter + option. + This formatter must implement the + + SqlFormatter interface and return the SQL formatted appropriately in HTML. + Implementing a "good" formatter is basically a project in itself and beyond + the scope of SchemaSpy, so plug in your own if desired. + Use -dp to make the specified class visible to SchemaSpy's class loader. +
  • diff --git a/src/net/sourceforge/schemaspy/Config.java b/src/net/sourceforge/schemaspy/Config.java index 5c93b5d..259b6ff 100644 --- a/src/net/sourceforge/schemaspy/Config.java +++ b/src/net/sourceforge/schemaspy/Config.java @@ -30,6 +30,8 @@ import net.sourceforge.schemaspy.model.InvalidConfigurationException; import net.sourceforge.schemaspy.util.DbSpecificConfig; import net.sourceforge.schemaspy.util.Dot; +import net.sourceforge.schemaspy.view.DefaultSqlFormatter; +import net.sourceforge.schemaspy.view.SqlFormatter; /** * Configuration of a SchemaSpy run @@ -73,6 +75,8 @@ public class Config private Integer fontSize; private String description; private String dbPropertiesLoadedFrom; + private SqlFormatter sqlFormatter; + private String sqlFormatterClass; private Boolean generateHtml; private Boolean includeImpliedConstraints; private Boolean logoEnabled; @@ -612,6 +616,7 @@ public void setMaxDbThreads(int maxDbThreads) { /** * @see #setMaxDbThreads(int) + * @throws InvalidConfigurationException if unable to load properties */ public int getMaxDbThreads() throws InvalidConfigurationException { if (maxDbThreads == null) { @@ -897,6 +902,56 @@ public List getSchemas() { return schemas; } + /** + * Set the name of the {@link SqlFormatter SQL formatter} class to use to + * format SQL into HTML.

    + * The implementation of the class must be made available to the class + * loader, typically by specifying the path to its jar with -dp + * ({@link #setDriverPath(String)}). + */ + public void setSqlFormatter(String formatterClassName) { + sqlFormatterClass = formatterClassName; + sqlFormatter = null; + } + + /** + * Set the {@link SqlFormatter SQL formatter} to use to format + * SQL into HTML. + */ + public void setSqlFormatter(SqlFormatter sqlFormatter) { + this.sqlFormatter = sqlFormatter; + if (sqlFormatter != null) + sqlFormatterClass = sqlFormatter.getClass().getName(); + } + + /** + * Returns an implementation of {@link SqlFormatter SQL formatter} to use to format + * SQL into HTML. The default implementation is {@link DefaultSqlFormatter}. + * + * @return + * @throws InvalidConfigurationException if unable to instantiate an instance + */ + @SuppressWarnings("unchecked") + public SqlFormatter getSqlFormatter() throws InvalidConfigurationException { + if (sqlFormatter == null) { + if (sqlFormatterClass == null) { + sqlFormatterClass = pullParam("-sqlFormatter"); + + if (sqlFormatterClass == null) + sqlFormatterClass = DefaultSqlFormatter.class.getName(); + } + + try { + Class clazz = (Class)Class.forName(sqlFormatterClass); + sqlFormatter = clazz.newInstance(); + } catch (Exception exc) { + throw new InvalidConfigurationException("Failed to initialize instance of " + sqlFormatterClass, exc); + } + } + + return sqlFormatter; + } + public void setEvaluateAllEnabled(boolean enabled) { evaluteAll = enabled; } @@ -1048,13 +1103,18 @@ public boolean isDbHelpRequired() { return dbHelpRequired; } - public static String getLoadedFromJar() { String classpath = System.getProperty("java.class.path"); return new StringTokenizer(classpath, File.pathSeparator).nextToken(); } - public Properties getDbProperties(String type) throws IOException { + /** + * @param type + * @return + * @throws IOException + * @throws InvalidConfigurationException if db properties are incorrectly formed + */ + public Properties getDbProperties(String type) throws IOException, InvalidConfigurationException { ResourceBundle bundle = null; try { @@ -1189,7 +1249,15 @@ private String pullRequiredParam(String paramId) { return pullParam(paramId, true, false); } - private String pullParam(String paramId, boolean required, boolean dbTypeSpecific) { + /** + * @param paramId + * @param required + * @param dbTypeSpecific + * @return + * @throws MissingRequiredParameterException + */ + private String pullParam(String paramId, boolean required, boolean dbTypeSpecific) + throws MissingRequiredParameterException { int paramIndex = options.indexOf(paramId); if (paramIndex < 0) { if (required) @@ -1202,6 +1270,9 @@ private String pullParam(String paramId, boolean required, boolean dbTypeSpecifi return param; } + /** + * Thrown to indicate that a required parameter is missing + */ public static class MissingRequiredParameterException extends RuntimeException { private static final long serialVersionUID = 1L; private final boolean dbTypeSpecific; diff --git a/src/net/sourceforge/schemaspy/view/HtmlTablePage.java b/src/net/sourceforge/schemaspy/view/HtmlTablePage.java index 6b309bb..c8e8b48 100755 --- a/src/net/sourceforge/schemaspy/view/HtmlTablePage.java +++ b/src/net/sourceforge/schemaspy/view/HtmlTablePage.java @@ -2,15 +2,12 @@ import java.io.File; import java.io.IOException; -import java.sql.DatabaseMetaData; import java.text.NumberFormat; -import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; -import java.util.StringTokenizer; import java.util.TreeSet; import net.sourceforge.schemaspy.Config; import net.sourceforge.schemaspy.model.Database; @@ -30,7 +27,6 @@ */ public class HtmlTablePage extends HtmlFormatter { private static final HtmlTablePage instance = new HtmlTablePage(); - private Set keywords = null; private int columnCounter = 0; private final Map defaultValueAliases = new HashMap(); @@ -270,11 +266,14 @@ private void writeRelatives(TableColumn baseRelative, boolean dumpParents, Strin } private void writeNumRows(Database db, Table table, LineWriter out) throws IOException { - if (displayNumRows && !table.isView()) - out.write("

    Table contained " + NumberFormat.getIntegerInstance().format(table.getNumRows()) + " rows at "); - else - out.write("

    Analyzed at "); - out.writeln(db.getConnectTime()); + out.write("

    "); + if (displayNumRows && !table.isView()) { + out.write("Table contained " + NumberFormat.getIntegerInstance().format(table.getNumRows()) + " rows at "); + } else { + out.write("Analyzed at "); + } + out.write(db.getConnectTime()); + out.writeln("

    "); } private void writeCheckConstraints(Table table, LineWriter out) throws IOException { @@ -400,135 +399,33 @@ private void writeView(Table table, Database db, LineWriter out) throws IOExcept for (View v : db.getViews()) tables.put(v.getName(), v); - out.writeln("

    "); - out.writeln("View Definition:"); - out.writeln(""); - out.writeln(""); - out.writeln(" "); - - boolean alreadyFormatted = sql.contains("\n") || sql.contains("\r"); - if (alreadyFormatted) - { - out.write("
    "); - - int len = sql.length(); - for (int i = 0; i < len; i++) { - char ch = sql.charAt(i); - if (Character.isWhitespace(ch)) - out.write(ch); - else - out.write(HtmlEncoder.encodeToken(ch)); - } - } - else - { - out.write(" "); - @SuppressWarnings("hiding") - Set keywords = getKeywords(db.getMetaData()); - StringTokenizer tokenizer = new StringTokenizer(sql, " \t\n\r\f()<>|.,", true); - while (tokenizer.hasMoreTokens()) { - String nextToken = tokenizer.nextToken(); - if (keywords.contains(nextToken.toUpperCase())) { - out.write(""); - out.write(nextToken); - out.write(""); - } else { - Table t = tables.get(nextToken); - if (t != null) { - out.write(""); - out.write(t.getName()); - out.write(""); - } else { - out.write(HtmlEncoder.encodeToken(nextToken)); - } - } - } - } + Set references = new TreeSet
    (); + String formatted = Config.getInstance().getSqlFormatter().format(sql, db, references); - out.writeln(""); - out.writeln(" "); - out.writeln("
    "); + out.writeln("
    "); + out.writeln(" View Definition:"); + out.writeln(formatted); out.writeln("
    "); - } - } - - /** - * getSqlKeywords - * - * @return Object - */ - private Set getKeywords(DatabaseMetaData meta) { - if (keywords == null) { - keywords = new HashSet(Arrays.asList(new String[] { - "ABSOLUTE", "ACTION", "ADD", "ALL", "ALLOCATE", "ALTER", "AND", - "ANY", "ARE", "AS", "ASC", "ASSERTION", "AT", "AUTHORIZATION", "AVG", - "BEGIN", "BETWEEN", "BIT", "BIT_LENGTH", "BOTH", "BY", - "CASCADE", "CASCADED", "CASE", "CAST", "CATALOG", "CHAR", "CHARACTER", - "CHAR_LENGTH", "CHARACTER_LENGTH", "CHECK", "CLOSE", "COALESCE", - "COLLATE", "COLLATION", "COLUMN", "COMMIT", "CONNECT", "CONNECTION", - "CONSTRAINT", "CONSTRAINTS", "CONTINUE", "CONVERT", "CORRESPONDING", - "COUNT", "CREATE", "CROSS", "CURRENT", "CURRENT_DATE", "CURRENT_TIME", - "CURRENT_TIMESTAMP", "CURRENT_USER", "CURSOR", - "DATE", "DAY", "DEALLOCATE", "DEC", "DECIMAL", "DECLARE", "DEFAULT", - "DEFERRABLE", "DEFERRED", "DELETE", "DESC", "DESCRIBE", "DESCRIPTOR", - "DIAGNOSTICS", "DISCONNECT", "DISTINCT", "DOMAIN", "DOUBLE", "DROP", - "ELSE", "END", "END - EXEC", "ESCAPE", "EXCEPT", "EXCEPTION", "EXEC", - "EXECUTE", "EXISTS", "EXTERNAL", "EXTRACT", - "FALSE", "FETCH", "FIRST", "FLOAT", "FOR", "FOREIGN", "FOUND", "FROM", "FULL", - "GET", "GLOBAL", "GO", "GOTO", "GRANT", "GROUP", - "HAVING", "HOUR", - "IDENTITY", "IMMEDIATE", "IN", "INDICATOR", "INITIALLY", "INNER", "INPUT", - "INSENSITIVE", "INSERT", "INT", "INTEGER", "INTERSECT", "INTERVAL", "INTO", - "IS", "ISOLATION", - "JOIN", - "KEY", - "LANGUAGE", "LAST", "LEADING", "LEFT", "LEVEL", "LIKE", "LOCAL", "LOWER", - "MATCH", "MAX", "MIN", "MINUTE", "MODULE", "MONTH", - "NAMES", "NATIONAL", "NATURAL", "NCHAR", "NEXT", "NO", "NOT", "NULL", - "NULLIF", "NUMERIC", - "OCTET_LENGTH", "OF", "ON", "ONLY", "OPEN", "OPTION", "OR", "ORDER", - "OUTER", "OUTPUT", "OVERLAPS", - "PAD", "PARTIAL", "POSITION", "PRECISION", "PREPARE", "PRESERVE", "PRIMARY", - "PRIOR", "PRIVILEGES", "PROCEDURE", "PUBLIC", - "READ", "REAL", "REFERENCES", "RELATIVE", "RESTRICT", "REVOKE", "RIGHT", - "ROLLBACK", "ROWS", - "SCHEMA", "SCROLL", "SECOND", "SECTION", "SELECT", "SESSION", "SESSION_USER", - "SET", "SIZE", "SMALLINT", "SOME", "SPACE", "SQL", "SQLCODE", "SQLERROR", - "SQLSTATE", "SUBSTRING", "SUM", "SYSTEM_USER", - "TABLE", "TEMPORARY", "THEN", "TIME", "TIMESTAMP", "TIMEZONE_HOUR", - "TIMEZONE_MINUTE", "TO", "TRAILING", "TRANSACTION", "TRANSLATE", - "TRANSLATION", "TRIM", "TRUE", - "UNION", "UNIQUE", "UNKNOWN", "UPDATE", "UPPER", "USAGE", "USER", "USING", - "VALUE", "VALUES", "VARCHAR", "VARYING", "VIEW", - "WHEN", "WHENEVER", "WHERE", "WITH", "WORK", "WRITE", - "YEAR", - "ZONE" - })); - - try { - String keywordsArray[] = new String[] { - meta.getSQLKeywords(), - meta.getSystemFunctions(), - meta.getNumericFunctions(), - meta.getStringFunctions(), - meta.getTimeDateFunctions() - }; - for (int i = 0; i < keywordsArray.length; ++i) { - StringTokenizer tokenizer = new StringTokenizer(keywordsArray[i].toUpperCase(), ","); - - while (tokenizer.hasMoreTokens()) { - keywords.add(tokenizer.nextToken().trim()); - } + out.writeln("
     
    "); + + if (!references.isEmpty()) { + out.writeln("
    "); + out.writeln(" Possibly Referenced Tables/Views:"); + out.writeln("
    "); + out.write(" "); + for (Table t : references) { + out.write(""); + out.write(t.getName()); + out.write(" "); } - } catch (Exception exc) { - // don't totally fail just because we can't extract these details... - System.err.println(exc); + + out.writeln("
    "); + out.writeln("

    "); } - } - return keywords; + } } /** diff --git a/src/schemaSpy.css b/src/schemaSpy.css index 40f486f..dfb39a7 100755 --- a/src/schemaSpy.css +++ b/src/schemaSpy.css @@ -181,11 +181,27 @@ a:visited { } .viewDefinition { + font-size: 90%; + background-color: #ffffff; + border-style: solid; + border-width: 1px; + float: left; + padding: 4px; font-family: "Courier New", Courier, monospace } +.viewReferences { + font-size: 90%; + padding: 4px; +} + +/* wrap around divs that float so they "take up space" */ +div.spacer { + clear: both; +} + .preFormatted { - white-space:pre; + white-space: pre; } /* Tabs from http://www.alistapart.com/articles/slidingdoors/ */