From f6970ab58191aebd8578e42e7592ad01c1023923 Mon Sep 17 00:00:00 2001 From: johncurrier Date: Fri, 28 Oct 2005 00:38:57 +0000 Subject: [PATCH] Added ability to exclude tables from analysis to help simplify output --- src/net/sourceforge/schemaspy/Main.java | 37 +++- .../sourceforge/schemaspy/model/Table.java | 7 +- .../schemaspy/model/TableColumn.java | 5 + .../schemaspy/view/DotConnector.java | 74 +++---- .../schemaspy/view/DotConnectorFinder.java | 35 ++-- .../schemaspy/view/DotFormatter.java | 198 +++++++++++++----- .../sourceforge/schemaspy/view/DotNode.java | 61 +++++- .../schemaspy/view/HtmlGraphFormatter.java | 6 +- .../schemaspy/view/StyleSheet.java | 14 ++ .../schemaspy/view/WriteStats.java | 56 ++++- 10 files changed, 374 insertions(+), 119 deletions(-) diff --git a/src/net/sourceforge/schemaspy/Main.java b/src/net/sourceforge/schemaspy/Main.java index 7d957e5..72e9c08 100755 --- a/src/net/sourceforge/schemaspy/Main.java +++ b/src/net/sourceforge/schemaspy/Main.java @@ -5,6 +5,7 @@ import java.sql.*; import java.util.*; import java.util.jar.*; +import java.util.regex.*; //import javax.xml.parsers.*; //import org.w3c.dom.*; import net.sourceforge.schemaspy.model.*; @@ -84,6 +85,14 @@ public static void main(String[] argv) { System.setProperty("sourceforgelogo", "true"); } + Pattern exclusions; + String exclude = getParam(args, "-i", false, false); + if (exclude != null) { + exclusions = Pattern.compile(exclude); + } else { + exclusions = Pattern.compile("[^.]"); // match nothing + } + ConnectionURLBuilder urlBuilder = null; try { urlBuilder = new ConnectionURLBuilder(dbType, args, properties); @@ -155,14 +164,17 @@ public static void main(String[] argv) { File graphsDir = new File(outputDir, "graphs/summary"); String dotBaseFilespec = "relationships"; out = new LineWriter(new FileWriter(new File(graphsDir, dotBaseFilespec + ".real.compact.dot"))); - WriteStats stats = DotFormatter.getInstance().writeRealRelationships(tables, true, out); + WriteStats stats = new WriteStats(exclusions, includeImpliedConstraints); + DotFormatter.getInstance().writeRealRelationships(tables, true, stats, out); boolean hasRelationships = stats.getNumTablesWritten() > 0 || stats.getNumViewsWritten() > 0; + stats = new WriteStats(stats); out.close(); if (hasRelationships) { System.out.print("."); out = new LineWriter(new FileWriter(new File(graphsDir, dotBaseFilespec + ".real.large.dot"))); - DotFormatter.getInstance().writeRealRelationships(tables, false, out); + DotFormatter.getInstance().writeRealRelationships(tables, false, stats, out); + stats = new WriteStats(stats); out.close(); } @@ -182,19 +194,24 @@ public static void main(String[] argv) { File impliedDotFile = new File(graphsDir, dotBaseFilespec + ".implied.compact.dot"); out = new LineWriter(new FileWriter(impliedDotFile)); - boolean hasImplied = DotFormatter.getInstance().writeAllRelationships(tables, true, out).wroteImplied(); + stats = new WriteStats(exclusions, includeImpliedConstraints); + DotFormatter.getInstance().writeAllRelationships(tables, true, stats, out); + boolean hasImplied = stats.wroteImplied(); + Set excludedColumns = stats.getExcludedColumns(); + stats = new WriteStats(stats); out.close(); if (hasImplied) { impliedDotFile = new File(graphsDir, dotBaseFilespec + ".implied.large.dot"); out = new LineWriter(new FileWriter(impliedDotFile)); - DotFormatter.getInstance().writeAllRelationships(tables, false, out); + DotFormatter.getInstance().writeAllRelationships(tables, false, stats, out); + stats = new WriteStats(stats); out.close(); } else { impliedDotFile.delete(); } out = new LineWriter(new FileWriter(new File(outputDir, dotBaseFilespec + ".html"))); - hasRelationships = HtmlGraphFormatter.getInstance().write(db, graphsDir, dotBaseFilespec, hasOrphans, hasImplied, out); + hasRelationships = HtmlGraphFormatter.getInstance().write(db, graphsDir, dotBaseFilespec, hasOrphans, hasImplied, excludedColumns, out); out.close(); } @@ -202,11 +219,13 @@ public static void main(String[] argv) { dotBaseFilespec = "utilities"; out = new LineWriter(new FileWriter(new File(outputDir, dotBaseFilespec + ".html"))); HtmlGraphFormatter.getInstance().writeOrphans(db, orphans, hasRelationships, graphsDir, out); + stats = new WriteStats(stats); out.close(); System.out.print("."); out = new LineWriter(new FileWriter(new File(outputDir, "index.html")), 64 * 1024); HtmlMainIndexFormatter.getInstance().write(db, tables, hasRelationships, hasOrphans, out); + stats = new WriteStats(stats); out.close(); System.out.print("."); @@ -214,16 +233,19 @@ public static void main(String[] argv) { out = new LineWriter(new FileWriter(new File(outputDir, "constraints.html")), 256 * 1024); HtmlConstraintIndexFormatter constraintIndexFormatter = HtmlConstraintIndexFormatter.getInstance(); constraintIndexFormatter.write(db, constraints, tables, hasRelationships, hasOrphans, out); + stats = new WriteStats(stats); out.close(); System.out.print("."); out = new LineWriter(new FileWriter(new File(outputDir, "anomalies.html")), 16 * 1024); HtmlAnomaliesFormatter.getInstance().write(db, tables, impliedConstraints, hasRelationships, hasOrphans, out); + stats = new WriteStats(stats); out.close(); System.out.print("."); out = new LineWriter(new FileWriter(new File(outputDir, "columns.html")), 16 * 1024); HtmlColumnsFormatter.getInstance().write(db, tables, hasRelationships, hasOrphans, out); + stats = new WriteStats(stats); out.close(); @@ -236,7 +258,8 @@ public static void main(String[] argv) { System.out.print('.'); Table table = (Table)iter.next(); out = new LineWriter(new FileWriter(new File(outputDir, "tables/" + table.getName() + ".html")), 24 * 1024); - tableFormatter.write(db, table, hasRelationships, hasOrphans, outputDir, out); + tableFormatter.write(db, table, hasRelationships, hasOrphans, outputDir, stats, out); + stats = new WriteStats(stats); out.close(); // XmlTableFormatter.getInstance().appendTable(schemaNode, table, outputDir); @@ -533,6 +556,8 @@ private static void dumpUsage(String errorMessage, boolean detailed, boolean det System.out.println(" -dbthreads max concurrent threads when accessing metadata"); System.out.println(" defaults to -1 (no limit)"); System.out.println(" use 1 if you get 'already closed' type errors"); + System.out.println(" -i columnNamesRegex ignore matching columns during analysis"); + System.out.println(" e.g.: -i \"(book.isbn)|(borrower.address)\""); System.out.println(" -nohtml defaults to generate html"); System.out.println(" -noimplied defaults to generate implied relationships"); System.out.println(" -nologo don't put SourceForge logo on generated pages"); diff --git a/src/net/sourceforge/schemaspy/model/Table.java b/src/net/sourceforge/schemaspy/model/Table.java index d32d812..dc2bf41 100755 --- a/src/net/sourceforge/schemaspy/model/Table.java +++ b/src/net/sourceforge/schemaspy/model/Table.java @@ -3,7 +3,7 @@ import java.sql.*; import java.util.*; -public class Table { +public class Table implements Comparable { private final String schema; private final String name; private final Map columns = new HashMap(); @@ -579,6 +579,11 @@ public boolean isOrphan(boolean withImpliedRelationships) { return true; } + public int compareTo(Object o) { + Table table = (Table)o; + return getName().compareTo(table.getName()); + } + private static class ByIndexColumnComparator implements Comparator { public int compare(Object object1, Object object2) { TableColumn column1 = (TableColumn)object1; diff --git a/src/net/sourceforge/schemaspy/model/TableColumn.java b/src/net/sourceforge/schemaspy/model/TableColumn.java index 04df048..58f43f8 100755 --- a/src/net/sourceforge/schemaspy/model/TableColumn.java +++ b/src/net/sourceforge/schemaspy/model/TableColumn.java @@ -2,6 +2,7 @@ import java.sql.*; import java.util.*; +import java.util.regex.*; public class TableColumn { private final Table table; @@ -195,6 +196,10 @@ public void setIsAutoUpdated(boolean isAutoUpdated) { this.isAutoUpdated = isAutoUpdated; } + public boolean matches(Pattern regex) { + return regex.matcher(getTable().getName() + "." + getName()).matches(); + } + public String toString() { return getName(); } diff --git a/src/net/sourceforge/schemaspy/view/DotConnector.java b/src/net/sourceforge/schemaspy/view/DotConnector.java index 910523a..2bb5a83 100755 --- a/src/net/sourceforge/schemaspy/view/DotConnector.java +++ b/src/net/sourceforge/schemaspy/view/DotConnector.java @@ -10,14 +10,15 @@ * @author John Currier */ public class DotConnector implements Comparable { - private TableColumn parentColumn; - private Table parentTable; - private TableColumn childColumn; - private Table childTable; + private final TableColumn parentColumn; + private final Table parentTable; + private final TableColumn childColumn; + private final Table childTable; private boolean implied; private boolean bottomJustify; private String parentPort; private String childPort; + private boolean blurred; /** * Create an edge that logically connects a child column to a parent column. @@ -68,27 +69,23 @@ public void connectToChildTitle() { public String toString() { StringBuffer edge = new StringBuffer(); edge.append(" \""); - if (childTable != null) { - edge.append(childTable.getName()); - edge.append("\":\""); - edge.append(childPort); - } + edge.append(childTable.getName()); + edge.append("\":\""); + edge.append(childPort); edge.append("\":"); if (bottomJustify) edge.append("s"); edge.append("w -> \""); - if (parentTable != null) { - edge.append(parentTable.getName()); - edge.append("\":\""); - edge.append(parentPort); - } + edge.append(parentTable.getName()); + edge.append("\":\""); + edge.append(parentPort); edge.append("\":"); if (bottomJustify) edge.append("s"); edge.append("e "); edge.append("[arrowtail="); - if (childPort.endsWith(".heading")) { + if (blurred || childPort.endsWith(".heading")) { edge.append("none"); } else { if (!childColumn.isUnique()) @@ -101,30 +98,23 @@ public String toString() { edge.append(" arrowhead=none"); if (implied) edge.append(" style=dashed"); + if (blurred) + edge.append(" color=\"" + StyleSheet.getInstance().getOutlierBackgroundColor() + "\""); edge.append("];"); return edge.toString(); } public int compareTo(Object o) { DotConnector other = (DotConnector)o; - int rc = childTable == null ? -1 : (other.childTable == null ? 1 : 0); - if (rc == 0) - rc = childTable.getName().compareTo(other.childTable.getName()); + int rc = childTable.getName().compareTo(other.childTable.getName()); if (rc == 0) rc = childColumn.getName().compareTo(other.childColumn.getName()); - if (rc == 0) - rc = parentTable == null ? -1 : (other.parentTable == null ? 1 : 0); if (rc == 0) rc = parentTable.getName().compareTo(other.parentTable.getName()); if (rc == 0) rc = parentColumn.getName().compareTo(other.parentColumn.getName()); if (rc == 0 && implied != other.implied) rc = implied ? 1 : -1; -// if (rc == 0) -// rc = parentPort.compareTo(other.parentPort); -// if (rc == 0) -// rc = childPort.compareTo(other.childPort); - return rc; } @@ -140,23 +130,37 @@ public int hashCode() { return (p << 16) & c; } - /** - * stubMissingTables - * - * @param tablesWritten Set - */ - public void stubMissingTables(Set tables) { - if (!tables.contains(parentTable)) - parentTable = null; - if (!tables.contains(childTable)) - childTable = null; + public TableColumn getParentColumn() { + return parentColumn; } public Table getParentTable() { return parentTable; } + public TableColumn getChildColumn() { + return childColumn; + } + public Table getChildTable() { return childTable; } + + /** + * setBlurred + * + * @param blurred boolean + */ + public void setBlurred(boolean blurred) { + this.blurred = blurred; + } + + // this doesn't belong here, but not sure where...TableColumn shouldn't be dealing with this + static boolean isExcluded(TableColumn column, WriteStats stats) { + if (column.matches(stats.getExclusionPattern())) { + stats.addExcludedColumn(column); + return true; + } + return false; + } } diff --git a/src/net/sourceforge/schemaspy/view/DotConnectorFinder.java b/src/net/sourceforge/schemaspy/view/DotConnectorFinder.java index 301bb9e..3dcc79b 100755 --- a/src/net/sourceforge/schemaspy/view/DotConnectorFinder.java +++ b/src/net/sourceforge/schemaspy/view/DotConnectorFinder.java @@ -1,10 +1,8 @@ package net.sourceforge.schemaspy.view; import java.io.*; -import java.text.*; import java.util.*; import net.sourceforge.schemaspy.model.*; -import net.sourceforge.schemaspy.util.*; /** * Format table data into .dot format to feed to GraphVis' dot program. @@ -27,16 +25,15 @@ public static DotConnectorFinder getInstance() { /** * * @param table Table - * @param includeImplied boolean * @throws IOException * @return Set of dot relationships (as DotEdges) */ - public Set getRelatedConnectors(Table table, boolean includeImplied) throws IOException { + public Set getRelatedConnectors(Table table, WriteStats stats) throws IOException { Set relationships = new HashSet(); Iterator iter = table.getColumns().iterator(); while (iter.hasNext()) { - relationships.addAll(getRelatedConnectors((TableColumn)iter.next(), null, includeImplied)); + relationships.addAll(getRelatedConnectors((TableColumn)iter.next(), null, stats)); } return relationships; @@ -47,21 +44,20 @@ public Set getRelatedConnectors(Table table, boolean includeImplied) throws IOEx * * @param table1 Table * @param table2 Table - * @param includeImplied boolean * @throws IOException * @return Set of dot relationships (as DotEdges) */ - public Set getRelatedConnectors(Table table1, Table table2, boolean includeImplied) throws IOException { + public Set getRelatedConnectors(Table table1, Table table2, WriteStats stats) throws IOException { Set relationships = new HashSet(); Iterator iter = table1.getColumns().iterator(); while (iter.hasNext()) { - relationships.addAll(getRelatedConnectors((TableColumn)iter.next(), table2, includeImplied)); + relationships.addAll(getRelatedConnectors((TableColumn)iter.next(), table2, stats)); } iter = table2.getColumns().iterator(); while (iter.hasNext()) { - relationships.addAll(getRelatedConnectors((TableColumn)iter.next(), table1, includeImplied)); + relationships.addAll(getRelatedConnectors((TableColumn)iter.next(), table1, stats)); } return relationships; @@ -70,21 +66,24 @@ public Set getRelatedConnectors(Table table1, Table table2, boolean includeImpli /** * @param column TableColumn * @param targetTable Table - * @param includeImplied boolean * @throws IOException * @return Set of dot relationships (as DotEdges) */ - private Set getRelatedConnectors(TableColumn column, Table targetTable, boolean includeImplied) throws IOException { - Set relationships = new HashSet(); + private Set getRelatedConnectors(TableColumn column, Table targetTable, WriteStats stats) throws IOException { + Set relatedConnectors = new HashSet(); + if (DotConnector.isExcluded(column, stats)) + return relatedConnectors; for (Iterator iter = column.getParents().iterator(); iter.hasNext(); ) { TableColumn parentColumn = (TableColumn)iter.next(); Table parentTable = parentColumn.getTable(); if (targetTable != null && parentTable != targetTable) continue; + if (DotConnector.isExcluded(parentColumn, stats)) + continue; boolean implied = column.getParentConstraint(parentColumn).isImplied(); - if (includeImplied || !implied) { - relationships.add(new DotConnector(parentColumn, column, implied)); + if (stats.includeImplied() || !implied) { + relatedConnectors.add(new DotConnector(parentColumn, column, implied)); } } @@ -93,12 +92,14 @@ private Set getRelatedConnectors(TableColumn column, Table targetTable, boolean Table childTable = childColumn.getTable(); if (targetTable != null && childTable != targetTable) continue; + if (DotConnector.isExcluded(childColumn, stats)) + continue; boolean implied = column.getChildConstraint(childColumn).isImplied(); - if (includeImplied || !implied) { - relationships.add(new DotConnector(column, childColumn, implied)); + if (stats.includeImplied() || !implied) { + relatedConnectors.add(new DotConnector(column, childColumn, implied)); } } - return relationships; + return relatedConnectors; } } diff --git a/src/net/sourceforge/schemaspy/view/DotFormatter.java b/src/net/sourceforge/schemaspy/view/DotFormatter.java index 68f7521..41d5274 100755 --- a/src/net/sourceforge/schemaspy/view/DotFormatter.java +++ b/src/net/sourceforge/schemaspy/view/DotFormatter.java @@ -2,6 +2,7 @@ import java.io.*; import java.util.*; +import java.util.regex.*; import net.sourceforge.schemaspy.model.*; import net.sourceforge.schemaspy.util.*; @@ -30,36 +31,43 @@ public static DotFormatter getInstance() { /** * Write all relationships (including implied) associated with the given table */ - public WriteStats writeRealRelationships(Table table, boolean twoDegreesOfSeparation, LineWriter dot) throws IOException { - return writeRelationships(table, false, twoDegreesOfSeparation, dot); + public void writeRealRelationships(Table table, boolean twoDegreesOfSeparation, WriteStats stats, LineWriter dot) throws IOException { + boolean origImplied = stats.setIncludeImplied(false); + writeRelationships(table, twoDegreesOfSeparation, stats, dot); + stats.setIncludeImplied(origImplied); } /** * Write implied relationships associated with the given table */ - public WriteStats writeImpliedRelationships(Table table, boolean twoDegreesOfSeparation, LineWriter dot) throws IOException { - return writeRelationships(table, true, twoDegreesOfSeparation, dot); + public void writeAllRelationships(Table table, boolean twoDegreesOfSeparation, WriteStats stats, LineWriter dot) throws IOException { + boolean origImplied = stats.setIncludeImplied(true); + writeRelationships(table, twoDegreesOfSeparation, stats, dot); + stats.setIncludeImplied(origImplied); } /** * Write relationships associated with the given table */ - private WriteStats writeRelationships(Table table, boolean includeImplied, boolean twoDegreesOfSeparation, LineWriter dot) throws IOException { + private void writeRelationships(Table table, boolean twoDegreesOfSeparation, WriteStats stats, LineWriter dot) throws IOException { + Pattern regex = getRegexWithoutTable(table, stats); + Pattern originalRegex = stats.setExclusionPattern(regex); Set tablesWritten = new HashSet(); - WriteStats stats = new WriteStats(); - DotConnectorFinder formatter = DotConnectorFinder.getInstance(); + DotConnectorFinder finder = DotConnectorFinder.getInstance(); - String graphName = includeImplied ? "impliedTwoDegreesRelationshipsGraph" : (twoDegreesOfSeparation ? "twoDegreesRelationshipsGraph" : "oneDegreeRelationshipsGraph"); + String graphName = stats.includeImplied() ? "impliedTwoDegreesRelationshipsGraph" : (twoDegreesOfSeparation ? "twoDegreesRelationshipsGraph" : "oneDegreeRelationshipsGraph"); writeHeader(graphName, false, true, dot); - Set relatedTables = getImmediateRelatives(table, includeImplied, stats); + Set relatedTables = getImmediateRelatives(table, stats); dot.writeln(new DotNode(table, "").toString()); - Set connectors = new TreeSet(formatter.getRelatedConnectors(table, includeImplied)); + Set connectors = new TreeSet(finder.getRelatedConnectors(table, stats)); tablesWritten.add(table); stats.wroteTable(table); + Map nodes = new TreeMap(); + // write immediate relatives first Iterator iter = relatedTables.iterator(); while (iter.hasNext()) { @@ -67,18 +75,17 @@ private WriteStats writeRelationships(Table table, boolean includeImplied, boole if (!tablesWritten.add(relatedTable)) continue; // already written - dot.writeln(new DotNode(relatedTable, true, "").toString()); - stats.wroteTable(relatedTable); - connectors.addAll(formatter.getRelatedConnectors(relatedTable, table, includeImplied)); + nodes.put(relatedTable, new DotNode(relatedTable, true, "")); + connectors.addAll(finder.getRelatedConnectors(relatedTable, table, stats)); } // connect the edges that go directly to the target table // so they go to the target table's type column instead iter = connectors.iterator(); while (iter.hasNext()) { - DotConnector edge = (DotConnector)iter.next(); - if (edge.pointsTo(table)) - edge.connectToParentDetails(); + DotConnector connector = (DotConnector)iter.next(); + if (connector.pointsTo(table)) + connector.connectToParentDetails(); } Set allCousins = new HashSet(); @@ -89,7 +96,7 @@ private WriteStats writeRelationships(Table table, boolean includeImplied, boole iter = relatedTables.iterator(); while (iter.hasNext()) { Table relatedTable = (Table)iter.next(); - Set cousins = getImmediateRelatives(relatedTable, includeImplied, stats); + Set cousins = getImmediateRelatives(relatedTable, stats); Iterator cousinsIter = cousins.iterator(); while (cousinsIter.hasNext()) { @@ -97,32 +104,37 @@ private WriteStats writeRelationships(Table table, boolean includeImplied, boole if (!tablesWritten.add(cousin)) continue; // already written - allCousinConnectors.addAll(formatter.getRelatedConnectors(cousin, relatedTable, includeImplied)); - dot.writeln(new DotNode(cousin, true, "").toString()); - stats.wroteTable(cousin); + allCousinConnectors.addAll(finder.getRelatedConnectors(cousin, relatedTable, stats)); + nodes.put(cousin, new DotNode(cousin, false, "")); } allCousins.addAll(cousins); } } + markExcludedColumns(nodes, stats.getExcludedColumns()); + iter = nodes.values().iterator(); + while (iter.hasNext()) { + DotNode node = (DotNode)iter.next(); + dot.writeln(node.toString()); + stats.wroteTable(node.getTable()); + } + // now figure out what's related at the outskirts to give visual clues Set outskirts = new TreeSet(); iter = tablesWritten.iterator(); while (iter.hasNext()) { Table t = (Table)iter.next(); if (t != table) - outskirts.addAll(formatter.getRelatedConnectors(t, includeImplied)); + outskirts.addAll(finder.getRelatedConnectors(t, stats)); } outskirts.removeAll(connectors); // remove the ones that inappropriately point to main table iter = outskirts.iterator(); while (iter.hasNext()) { - DotConnector edge = (DotConnector)iter.next(); - if (edge.pointsTo(table)) + DotConnector connector = (DotConnector)iter.next(); + if (connector.pointsTo(table)) iter.remove(); - //else - // edge.stubMissingTables(tablesWritten); } // now directly connect the loose ends to the title of the @@ -140,34 +152,101 @@ private WriteStats writeRelationships(Table table, boolean includeImplied, boole connectors.addAll(outskirts); // write the collected connectors + Set missingTables = new HashSet(); iter = connectors.iterator(); - while (iter.hasNext()) - dot.writeln(iter.next().toString()); + while (iter.hasNext()) { + DotConnector connector = (DotConnector)iter.next(); + if (!tablesWritten.contains(connector.getParentTable())) { + missingTables.add(connector.getParentTable()); + connector.setBlurred(true); + } + if (!tablesWritten.contains(connector.getChildTable())) { + missingTables.add(connector.getChildTable()); + connector.setBlurred(true); + } + dot.writeln(connector.toString()); + } + + iter = missingTables.iterator(); + while (iter.hasNext()) { + Table missingTable = (Table)iter.next(); + dot.writeln(" " + new DotNode(missingTable).toString()); + } dot.writeln("}"); - return stats; + stats.setExclusionPattern(originalRegex); + } + + private Pattern getRegexWithoutTable(Table table, WriteStats stats) { + Set pieces = new HashSet(); + List regexes = Arrays.asList(stats.getExclusionPattern().pattern().split("\\)\\|\\(")); + for (int i = 0; i < regexes.size(); ++i) { + String regex = regexes.get(i).toString(); + if (!regex.startsWith("(")) + regex = "(" + regex; + if (!regex.endsWith(")")) + regex = regex + ")"; + pieces.add(regex); + } + + // now removed the pieces that match some of the columns of this table + Iterator iter = pieces.iterator(); + while (iter.hasNext()) { + String regex = iter.next().toString(); + Iterator columnIter = table.getColumns().iterator(); + while (columnIter.hasNext()) { + TableColumn column = (TableColumn)columnIter.next(); + Pattern columnPattern = Pattern.compile(regex); + if (column.matches(columnPattern)) { + iter.remove(); + break; + } + } + } + + StringBuffer pattern = new StringBuffer(); + iter = pieces.iterator(); + while (iter.hasNext()) { + if (pattern.length() > 0) + pattern.append("|"); + pattern.append(iter.next()); + } + + return Pattern.compile(pattern.toString()); } - private Set getImmediateRelatives(Table table, boolean includeImplied, WriteStats stats) { + private Set getImmediateRelatives(Table table, WriteStats stats) { Set relatedColumns = new HashSet(); boolean foundImplied = false; Iterator iter = table.getColumns().iterator(); while (iter.hasNext()) { TableColumn column = (TableColumn)iter.next(); + if (DotConnector.isExcluded(column, stats)) { + stats.addExcludedColumn(column); + continue; + } Iterator childIter = column.getChildren().iterator(); while (childIter.hasNext()) { TableColumn childColumn = (TableColumn)childIter.next(); + if (DotConnector.isExcluded(childColumn, stats)) { + stats.addExcludedColumn(childColumn); + continue; + } boolean implied = column.getChildConstraint(childColumn).isImplied(); foundImplied |= implied; - if (!implied || includeImplied) + if (!implied || stats.includeImplied()) relatedColumns.add(childColumn); } Iterator parentIter = column.getParents().iterator(); while (parentIter.hasNext()) { TableColumn parentColumn = (TableColumn)parentIter.next(); + if (DotConnector.isExcluded(parentColumn, stats)) { + stats.addExcludedColumn(parentColumn); + continue; + } boolean implied = column.getParentConstraint(parentColumn).isImplied(); foundImplied |= implied; - if (!implied || includeImplied) + if (!implied || stats.includeImplied()) relatedColumns.add(parentColumn); } } @@ -200,6 +279,7 @@ private void writeHeader(String graphName, boolean compact, boolean showLabel, L } dot.writeln(" ];"); dot.writeln(" node ["); + dot.writeln(" fontname=\"Helvetica\""); dot.writeln(" fontsize=\"" + (compact ? CompactGraphFontSize : LargeGraphFontSize) + "\""); dot.writeln(" shape=\"plaintext\""); dot.writeln(" ];"); @@ -208,19 +288,22 @@ private void writeHeader(String graphName, boolean compact, boolean showLabel, L dot.writeln(" ];"); } - public WriteStats writeRealRelationships(Collection tables, boolean compact, LineWriter dot) throws IOException { - return writeRelationships(tables, false, compact, dot); + public void writeRealRelationships(Collection tables, boolean compact, WriteStats stats, LineWriter dot) throws IOException { + boolean oldImplied = stats.setIncludeImplied(false); + writeRelationships(tables, compact, stats, dot); + stats.setIncludeImplied(oldImplied); } - public WriteStats writeAllRelationships(Collection tables, boolean compact, LineWriter dot) throws IOException { - return writeRelationships(tables, true, compact, dot); + public void writeAllRelationships(Collection tables, boolean compact, WriteStats stats, LineWriter dot) throws IOException { + boolean oldImplied = stats.setIncludeImplied(true); + writeRelationships(tables, compact, stats, dot); + stats.setIncludeImplied(oldImplied); } - private WriteStats writeRelationships(Collection tables, boolean includeImplied, boolean compact, LineWriter dot) throws IOException { - DotConnectorFinder formatter = DotConnectorFinder.getInstance(); - WriteStats stats = new WriteStats(); + private void writeRelationships(Collection tables, boolean compact, WriteStats stats, LineWriter dot) throws IOException { + DotConnectorFinder finder = DotConnectorFinder.getInstance(); String graphName; - if (includeImplied) { + if (stats.includeImplied()) { if (compact) graphName = "compactImpliedRelationshipsGraph"; else @@ -233,16 +316,14 @@ private WriteStats writeRelationships(Collection tables, boolean includeImplied, } writeHeader(graphName, compact, true, dot); + Map nodes = new HashMap(); + Iterator iter = tables.iterator(); while (iter.hasNext()) { Table table = (Table)iter.next(); - if (!table.isOrphan(includeImplied)) { - dot.writeln(new DotNode(table, true, "tables/").toString()); - stats.wroteTable(table); - if (includeImplied && table.isOrphan(!includeImplied)) { - stats.setWroteImplied(true); - } + if (!table.isOrphan(stats.includeImplied())) { + nodes.put(table, new DotNode(table, true, "tables/")); } } @@ -250,15 +331,38 @@ private WriteStats writeRelationships(Collection tables, boolean includeImplied, iter = tables.iterator(); while (iter.hasNext()) - connectors.addAll(formatter.getRelatedConnectors((Table)iter.next(), includeImplied)); + connectors.addAll(finder.getRelatedConnectors((Table)iter.next(), stats)); + + markExcludedColumns(nodes, stats.getExcludedColumns()); + + iter = nodes.values().iterator(); + while (iter.hasNext()) { + DotNode node = (DotNode)iter.next(); + Table table = node.getTable(); + + dot.writeln(node.toString()); + stats.wroteTable(table); + if (stats.includeImplied() && table.isOrphan(!stats.includeImplied())) { + stats.setWroteImplied(true); + } + } iter = connectors.iterator(); while (iter.hasNext()) dot.writeln(iter.next().toString()); dot.writeln("}"); + } - return stats; + private void markExcludedColumns(Map nodes, Set excludedColumns) { + Iterator iter = excludedColumns.iterator(); + while (iter.hasNext()) { + TableColumn column = (TableColumn)iter.next(); + DotNode node = (DotNode)nodes.get(column.getTable()); + if (node != null) { + node.excludeColumn(column); + } + } } public void writeOrphan(Table table, LineWriter dot) throws IOException { diff --git a/src/net/sourceforge/schemaspy/view/DotNode.java b/src/net/sourceforge/schemaspy/view/DotNode.java index 9025e76..09c83c6 100755 --- a/src/net/sourceforge/schemaspy/view/DotNode.java +++ b/src/net/sourceforge/schemaspy/view/DotNode.java @@ -7,8 +7,11 @@ public class DotNode { private final Table table; private final boolean detailed; - private final boolean hasColumns; + private final boolean showColumns; private final String refDirectory; + private final Set excludedColumns = new HashSet(); + private final Set columnsWithRelationships = new HashSet(); + private final Map referenceCountsByColumn = new HashMap(); private final String lineSeparator = System.getProperty("line.separator"); /** @@ -25,40 +28,74 @@ public DotNode(Table table, String refDirectory) { * Create a DotNode and specify whether it displays its columns. * * @param table Table - * @param hasColumns boolean + * @param showColumns boolean * @param refDirectory String */ - public DotNode(Table table, boolean hasColumns, String refDirectory) { - this(table, false, hasColumns, refDirectory); + public DotNode(Table table, boolean showColumns, String refDirectory) { + this(table, false, showColumns, refDirectory); } - private DotNode(Table table, boolean detailed, boolean hasColumns, String refDirectory) { + public DotNode(Table table) { + this(table, false, false, null); + } + + private DotNode(Table table, boolean detailed, boolean showColumns, String refDirectory) { this.table = table; this.detailed = detailed; - this.hasColumns = hasColumns; + this.showColumns = showColumns; this.refDirectory = refDirectory; + + Iterator iter = table.getColumns().iterator(); + while (iter.hasNext()) { + TableColumn column = (TableColumn)iter.next(); + int referenceCount = column.getChildren().size() + column.getParents().size(); + if (referenceCount > 0) { + columnsWithRelationships.add(column); + } + referenceCountsByColumn.put(column, new Integer(referenceCount)); + } } public boolean isDetailed() { return detailed; } - public boolean hasColumns() { - return hasColumns; + public Table getTable() { + return table; + } + + public void excludeColumn(TableColumn column) { + excludedColumns.add(column); + columnsWithRelationships.remove(column); + } + + public void decrementColumnReferences(TableColumn column) { + Integer referenceCount = (Integer)referenceCountsByColumn.get(column); + referenceCount = new Integer(referenceCount.intValue() - 1); + referenceCountsByColumn.put(column, referenceCount); + if (referenceCount.intValue() < 1) + columnsWithRelationships.remove(column); + } + + public boolean hasRelationships() { + return !columnsWithRelationships.isEmpty(); } public String toString() { + StyleSheet css = StyleSheet.getInstance(); + if (refDirectory == null) + return "\"" + table.getName() + "\" [fontcolor=\"" + css.getOutlierBackgroundColor() + "\"]"; + StringBuffer buf = new StringBuffer(); String tableName = table.getName(); String colspan = detailed ? "COLSPAN=\"2\" " : ""; - StyleSheet css = StyleSheet.getInstance(); buf.append(" \"" + tableName + "\" [" + lineSeparator); buf.append(" label=<" + lineSeparator); buf.append(" " + lineSeparator); buf.append(" " + lineSeparator); - if (hasColumns) { + if (showColumns) { List primaryColumns = table.getPrimaryColumns(); Set indexColumns = new HashSet(); Iterator iter = table.getIndexes().iterator(); @@ -73,7 +110,9 @@ public String toString() { buf.append("
" + tableName + "
"); html.writeln(" "); + html.writeln("
"); + writeExcludedColumns(excludedColumns, html); + dot.writeMap(compactRelationshipsDotFile, html); dot.writeMap(largeRelationshipsDotFile, html); diff --git a/src/net/sourceforge/schemaspy/view/StyleSheet.java b/src/net/sourceforge/schemaspy/view/StyleSheet.java index 1b72438..ad3903e 100755 --- a/src/net/sourceforge/schemaspy/view/StyleSheet.java +++ b/src/net/sourceforge/schemaspy/view/StyleSheet.java @@ -13,6 +13,8 @@ public class StyleSheet { private String primaryKeyBackgroundColor; private String indexedColumnBackgroundColor; private String selectedTableBackgroundColor; + private String ignoredColumnBackgroundColor; + private String outlierBackgroundColor; private final List ids = new ArrayList(); private StyleSheet(BufferedReader cssReader) throws IOException { @@ -55,6 +57,10 @@ else if (id.equals(".indexedcolumn")) indexedColumnBackgroundColor = attribs.get("background").toString(); else if (id.equals(".selectedtable")) selectedTableBackgroundColor = attribs.get("background").toString(); + else if (id.equals(".outlier")) + outlierBackgroundColor = attribs.get("background").toString(); + else if (id.equals(".ignoredcolumn")) + ignoredColumnBackgroundColor = attribs.get("background").toString(); id = null; } } @@ -109,6 +115,14 @@ public String getSelectedTableBackground() { return selectedTableBackgroundColor; } + public String getOutlierBackgroundColor() { + return outlierBackgroundColor; + } + + public String getIgnoredColumnBackgroundColor() { + return ignoredColumnBackgroundColor; + } + public int getOffsetOf(String id) { int offset = ids.indexOf(id.toLowerCase()); if (offset == -1) diff --git a/src/net/sourceforge/schemaspy/view/WriteStats.java b/src/net/sourceforge/schemaspy/view/WriteStats.java index 481f294..177e3db 100755 --- a/src/net/sourceforge/schemaspy/view/WriteStats.java +++ b/src/net/sourceforge/schemaspy/view/WriteStats.java @@ -1,6 +1,8 @@ package net.sourceforge.schemaspy.view; -import net.sourceforge.schemaspy.model.Table; +import java.util.*; +import java.util.regex.*; +import net.sourceforge.schemaspy.model.*; /** * Simple ugly hack that provides details of what was written. @@ -8,8 +10,21 @@ public class WriteStats { private int numTables; private int numViews; + private boolean includeImplied; private boolean wroteImplied; private boolean wroteTwoDegrees; + private Pattern exclusionPattern; + private final Set excludedColumns = new HashSet(); + + public WriteStats(Pattern exclusionPattern, boolean includeImplied) { + this.exclusionPattern = exclusionPattern; + this.includeImplied = includeImplied; + } + + public WriteStats(WriteStats stats) { + this.exclusionPattern = stats.exclusionPattern; + this.includeImplied = stats.includeImplied; + } public void wroteTable(Table table) { if (table.isView()) @@ -34,6 +49,10 @@ public int getNumViewsWritten() { return numViews; } + public boolean includeImplied() { + return includeImplied; + } + public boolean wroteImplied() { return wroteImplied; } @@ -41,4 +60,39 @@ public boolean wroteImplied() { public boolean wroteTwoDegrees() { return wroteTwoDegrees; } + + public void addExcludedColumn(TableColumn column) { + excludedColumns.add(column); + } + + public Set getExcludedColumns() { + return excludedColumns; + } + + public Pattern getExclusionPattern() { + return exclusionPattern; + } + + /** + * setIncludeImplied + * + * @param includeImplied boolean + */ + public boolean setIncludeImplied(boolean includeImplied) { + boolean oldValue = this.includeImplied; + this.includeImplied = includeImplied; + return oldValue; + } + + /** + * setExclusionPattern + * + * @param exclusionPattern Pattern + * @return Pattern + */ + public Pattern setExclusionPattern(Pattern exclusionPattern) { + Pattern orig = this.exclusionPattern; + this.exclusionPattern = exclusionPattern; + return orig; + } }