/*
* SchemaCrawler
* http://sourceforge.net/projects/schemacrawler
* Copyright (c) 2000-2014, Sualeh Fatehi.
* This library is free software; you can redistribute it and/or modify it under
* the terms
* of the GNU Lesser General Public License as published by the Free Software
* Foundation;
* either version 2.1 of the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License
* along with this
* library; if not, write to the Free Software Foundation, Inc., 59 Temple
* Place, Suite 330,
* Boston, MA 02111-1307, USA.
*/
package schemacrawler.tools.text.schema;
import static schemacrawler.utility.MetaDataUtility.getConnectivity;
import static sf.util.Utility.NEWLINE;
import static sf.util.Utility.convertForComparison;
import static sf.util.Utility.isBlank;
import static sf.util.Utility.pastelColorHTMLValue;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import schemacrawler.schema.Column;
import schemacrawler.schema.ColumnDataType;
import schemacrawler.schema.ColumnReference;
import schemacrawler.schema.ForeignKey;
import schemacrawler.schema.NamedObject;
import schemacrawler.schema.Routine;
import schemacrawler.schema.Schema;
import schemacrawler.schema.Synonym;
import schemacrawler.schema.Table;
import schemacrawler.schemacrawler.SchemaCrawlerException;
import schemacrawler.tools.analysis.associations.DatabaseWithAssociations;
import schemacrawler.tools.options.OutputOptions;
import schemacrawler.tools.text.base.BaseDotFormatter;
import schemacrawler.tools.traversal.SchemaTraversalHandler;
import schemacrawler.utility.MetaDataUtility.Connectivity;
import schemacrawler.utility.NamedObjectSort;
/**
* GraphViz DOT formatting of schema.
*
* @author Sualeh Fatehi
*/
public final class SchemaDotFormatter
extends BaseDotFormatter<SchemaTextOptions>
implements SchemaTraversalHandler
{
private final boolean isVerbose;
private final boolean isList;
private final Map<Schema, String> colorMap;
/**
* Text formatting of schema.
*
* @param schemaTextDetailType
* Types for text formatting of schema
* @param options
* Options for text formatting of schema
* @param outputOptions
* Options for text formatting of schema
* @throws SchemaCrawlerException
* On an exception
*/
public SchemaDotFormatter(final SchemaTextDetailType schemaTextDetailType,
final SchemaTextOptions options,
final OutputOptions outputOptions)
throws SchemaCrawlerException
{
super(options,
schemaTextDetailType == SchemaTextDetailType.details,
outputOptions);
isVerbose = schemaTextDetailType == SchemaTextDetailType.details;
isList = schemaTextDetailType == SchemaTextDetailType.list;
colorMap = new HashMap<>();
}
@Override
public void handle(final ColumnDataType columnDataType)
throws SchemaCrawlerException
{
}
/**
* Provides information on the database schema.
*
* @param routine
* Routine metadata.
*/
@Override
public void handle(final Routine routine)
{
}
/**
* Provides information on the database schema.
*
* @param synonym
* Synonym metadata.
*/
@Override
public void handle(final Synonym synonym)
{
}
/**
* Provides information on the database schema.
*
* @param table
* Table metadata.
*/
@Override
public void handle(final Table table)
{
final Schema schema = table.getSchema();
final String tableName;
if (options.isShowUnqualifiedNames())
{
tableName = table.getName();
}
else
{
tableName = table.getFullName();
}
final String tableType = "[" + table.getTableType() + "]";
final String tableNameBgColor;
if (!colorMap.containsKey(schema))
{
tableNameBgColor = pastelColorHTMLValue(schema.getFullName());
colorMap.put(schema, tableNameBgColor);
}
else
{
tableNameBgColor = colorMap.get(schema);
}
out.append(" /* ").append(table.getFullName())
.append(" -=-=-=-=-=-=-=-=-=-=-=-=-=- */").append(NEWLINE);
out.append(" \"").append(nodeId(table)).append("\" [").append(NEWLINE)
.append(" label=<").append(NEWLINE);
out
.append(" <table border=\"1\" cellborder=\"0\" cellpadding=\"2\" cellspacing=\"0\" bgcolor=\"white\">")
.append(NEWLINE);
out.append(" <tr>").append(NEWLINE);
final int colspan = options.isShowOrdinalNumbers()? 3: 2;
out.append(" <td colspan=\"").append(String.valueOf(colspan))
.append("\" bgcolor=\"").append(tableNameBgColor)
.append("\" align=\"left\"><b>").append(tableName).append("</b></td>")
.append(NEWLINE);
out.append(" <td bgcolor=\"").append(tableNameBgColor)
.append("\" align=\"right\">").append(tableType).append("</td>")
.append(NEWLINE);
out.append(" </tr>").append(NEWLINE);
if (!isList)
{
final List<Column> columns = table.getColumns();
printTableColumns(columns);
}
out.append(" </table>").append(NEWLINE);
out.append(" >").append(NEWLINE).append(" ];").append(NEWLINE)
.append(NEWLINE);
if (!isList)
{
for (final ForeignKey foreignKey: table.getForeignKeys())
{
for (final ColumnReference columnReference: foreignKey
.getColumnReferences())
{
if (table.equals(columnReference.getPrimaryKeyColumn().getParent()))
{
out.write(printColumnReference(foreignKey.getName(),
columnReference));
}
}
}
if (isVerbose)
{
printWeakAssociations(table);
}
}
out.append(NEWLINE).append(NEWLINE);
}
@Override
public void handleColumnDataTypesEnd()
{
}
@Override
public void handleColumnDataTypesStart()
{
}
@Override
public void handleRoutinesEnd()
throws SchemaCrawlerException
{
}
@Override
public void handleRoutinesStart()
throws SchemaCrawlerException
{
}
@Override
public void handleSynonymsEnd()
throws SchemaCrawlerException
{
}
@Override
public void handleSynonymsStart()
throws SchemaCrawlerException
{
}
@Override
public void handleTablesEnd()
throws SchemaCrawlerException
{
}
@Override
public void handleTablesStart()
throws SchemaCrawlerException
{
}
private String arrowhead(final Connectivity connectivity)
{
switch (connectivity)
{
case unknown:
return "vee";
case zero_one:
return "teeodot";
case zero_many:
return "crowodot";
case one_one:
return "teetee";
default:
return "vee";
}
}
private String[] getPortIds(final Column column)
{
final String portIds[] = new String[2];
boolean isColumnReference;
try
{
column.getColumnDataType();
isColumnReference = false;
}
catch (final Exception e)
{
isColumnReference = true;
}
if (!isColumnReference)
{
portIds[0] = String.format("\"%s\":\"%s.start\"",
nodeId(column.getParent()),
nodeId(column));
portIds[1] = String.format("\"%s\":\"%s.end\"",
nodeId(column.getParent()),
nodeId(column));
}
else
{
// Create new node
final String nodeId = printNewNode(column);
//
portIds[0] = nodeId;
portIds[1] = nodeId;
}
return portIds;
}
private String nodeId(final NamedObject namedObject)
{
if (namedObject == null)
{
return "";
}
else
{
return convertForComparison(namedObject.getName()) + "_"
+ Integer.toHexString(namedObject.getFullName().hashCode());
}
}
private String printColumnReference(final String associationName,
final ColumnReference columnReference)
{
final Column primaryKeyColumn = columnReference.getPrimaryKeyColumn();
final Column foreignKeyColumn = columnReference.getForeignKeyColumn();
final String[] pkPortIds = getPortIds(primaryKeyColumn);
final String[] fkPortIds = getPortIds(foreignKeyColumn);
final Connectivity connectivity = getConnectivity(foreignKeyColumn);
final String pkSymbol = "teetee";
final String fkSymbol = arrowhead(connectivity);
final String style;
if (isBlank(associationName))
{
style = "dashed";
}
else
{
style = "solid";
}
return String
.format(" %s:w -> %s:e [label=<%s> style=\"%s\" dir=\"both\" arrowhead=\"%s\" arrowtail=\"%s\"];%n",
pkPortIds[0],
fkPortIds[1],
options.isHideForeignKeyNames()? "": associationName,
style,
fkSymbol,
pkSymbol);
}
private String printNewNode(final Column column)
{
final String nodeId = "\"" + nodeId(column) + "\"";
final String columnName;
if (options.isShowUnqualifiedNames())
{
columnName = column.getShortName();
}
else
{
columnName = column.getFullName();
}
final String columnNode = String.format(" %s [label=<%s>];%n",
nodeId,
columnName);
out.write(columnNode);
return nodeId;
}
private void printTableColumns(final List<Column> columns)
{
Collections.sort(columns, NamedObjectSort.getNamedObjectSort(options
.isAlphabeticalSortForTableColumns()));
for (final Column column: columns)
{
String columnTypeName = column.getColumnDataType()
.getDatabaseSpecificTypeName();
if (options.isShowStandardColumnTypeNames())
{
columnTypeName = column.getColumnDataType().getTypeName();
}
final String columnType = columnTypeName + column.getWidth();
final String nullable = column.isNullable()? "": " not null";
final String columnDetails = columnType + nullable;
out.append(" <tr>").append(NEWLINE);
out.append(" <td port=\"").append(nodeId(column))
.append(".start\" ");
if (options.isShowOrdinalNumbers())
{
out.append("align=\"right\">");
final String ordinalNumberString = String.valueOf(column
.getOrdinalPosition());
out.append(ordinalNumberString);
out.append("</td>").append(NEWLINE).append(" <td ");
}
out.append("align=\"left\">");
if (column.isPartOfPrimaryKey())
{
out.append("<b><i><u>");
}
out.append(column.getName());
if (column.isPartOfPrimaryKey())
{
out.append("</u></i></b>");
}
out.append("</td>").append(NEWLINE);
out.append(" <td> </td>").append(NEWLINE);
out.append(" <td port=\"").append(nodeId(column))
.append(".end\" align=\"right\">");
out.append(columnDetails);
out.append("</td>").append(NEWLINE);
out.append(" </tr>").append(NEWLINE);
}
}
private void printWeakAssociations(final Table table)
{
final Collection<ColumnReference> weakAssociations = DatabaseWithAssociations
.getWeakAssociations(table);
for (final ColumnReference weakAssociation: weakAssociations)
{
out.write(printColumnReference("", weakAssociation));
}
}
}