DBVIEW
src/org/dbview/databaseExporters/dot/DotFull.java
00001 /*
00002         DbView - Graph Visualization
00003     Copyright (C) 2012  Denis BEURIVE
00004 
00005     This program is free software: you can redistribute it and/or modify
00006     it under the terms of the GNU General Public License as published by
00007     the Free Software Foundation, either version 3 of the License, or
00008     (at your option) any later version.
00009 
00010     This program is distributed in the hope that it will be useful,
00011     but WITHOUT ANY WARRANTY; without even the implied warranty of
00012     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013     GNU General Public License for more details.
00014 
00015     You should have received a copy of the GNU General Public License
00016     along with this program.  If not, see <http://www.gnu.org/licenses/>.
00017 */
00018 
00019 /**
00020  * @author Denis Beurive
00021  */
00022 
00023 package org.dbview.databaseExporters.dot;
00024 
00025 import java.util.ArrayList;
00026 import java.util.Enumeration;
00027 import java.util.Hashtable;
00028 import org.dbview.addons.output.table.utils.dot.*;
00029 import org.dbview.db.structure.Database;
00030 import org.dbview.db.structure.Field;
00031 import org.dbview.db.structure.FieldToFieldJoin;
00032 import org.dbview.db.structure.Table;
00033 import org.dbview.input_addons.DbIndex;
00034 import org.dbview.utils.dot.*;
00035 import org.dbview.utils.dot.label.*;
00036 
00037 /**
00038  * This class implements the export to DOT, with a high level of details.
00039  * @see AbstractDatabaseExporter
00040  * @author Denis Beurive
00041  */
00042 public class DotFull extends AbstractDatabaseExporter
00043 {
00044     /**
00045      * Create the exporter.
00046      * @param in_tables List of tables to export.
00047      * @param in_db Handle to the database.
00048      */
00049     public DotFull(ArrayList<Table> in_tables, Database in_db)
00050     {
00051         super(in_tables, in_db);
00052     }
00053 
00054     /**
00055      * This method exports the given tables, from the given database.
00056      * @param in_options Options.
00057      *        Options may be:
00058      *        <ul>
00059      *              <li>"layout"</li>
00060      *        </ul>
00061      * @return The method returns a string that represents the Graphviz representation.
00062      * @throws Exception
00063      */
00064     public String export(Hashtable<String, Object> in_options) throws Exception
00065     {
00066         DiGraph digraph = new DiGraph();
00067         String layout   = "TB"; 
00068         if (null != in_options) { if (in_options.containsKey("layout")) { layout = (String)in_options.get("layout"); } }
00069         digraph.setRankdir(layout);
00070 
00071         // ------------------------------------------------------------------
00072         // Configure the directed graph.
00073         // ------------------------------------------------------------------
00074 
00075         digraph.setNodesep(new Float(0.5));
00076         digraph.setRankset(new Float(0.7));
00077         digraph.setCompound(Boolean.TRUE);
00078 
00079         // ------------------------------------------------------------------
00080         // Add the table.
00081         // ------------------------------------------------------------------
00082 
00083         for (Table table : this._tables)
00084         {
00085             SubGraph sub_graph = new SubGraph(table.getName());
00086             sub_graph.setStyle("filled");
00087             sub_graph.setBackgroundColor(Conf.TABLE_BG_COLOR);
00088             sub_graph.setNodeStyle("filled");
00089             sub_graph.setNodeColor(Conf.TABLE_GRID_COLOR);
00090             sub_graph.setLabel(table.getName());
00091             
00092             // Noeud pour la table.
00093             Node table_node = new Node(table.getName());
00094             table_node.setShape("record");
00095             table_node.setStyle("bold");
00096             table_node.setLabel(this.__createFullTableLabel(table, digraph.getRankdir()));
00097             
00098             // Noeud pour les indexes.
00099             if (table.compositeIndexCount() > 0)
00100             {
00101                 String index_table_name = table.getDatabase().getNonTableTag();
00102                 Node index_node = new Node(index_table_name);
00103                 index_node.setShape("record");
00104                 index_node.setStyle("bold");
00105                 index_node.setLabel(this.__createIndexLabel(table, digraph.getRankdir()));
00106                 sub_graph.addNode(index_node);
00107 
00108                 // For the vertical layout only, we add an invisible arrow.
00109                 if (0 == layout.compareTo("TB"))
00110                 {
00111                     Edge e = new Edge();
00112                     e.setInvisible();
00113                     e.setFrom(table.getName());
00114                     e.setTo(index_table_name);
00115                     sub_graph.addEdge(e);
00116                 }
00117             }
00118             
00119             sub_graph.addNode(table_node);
00120             digraph.addSubGraph(sub_graph);
00121         }
00122 
00123         // ------------------------------------------------------------------
00124         // Add the joins between tables.
00125         // ------------------------------------------------------------------
00126 
00127         for (Table dependent_table : this._tables)
00128         {
00129             for (Table reference_table : this._db.getReferenceTables(dependent_table))
00130             {
00131                 // WARNING: If we work on a sub list of the total list of tables (zoom level is activated), then some "reference" tables may not be printed!
00132                 if (! this._tables.contains(reference_table)) { continue; }
00133 
00134                 ArrayList<FieldToFieldJoin> joins = this._db.getRelationsFromReferenceToTarget(dependent_table, reference_table);
00135 
00136                 String node_name = dependent_table.getName() + "2" + reference_table.getName();
00137                 Node jn = new Node(node_name);
00138                 Edge src2node = new Edge();
00139                 Edge node2dst = new Edge();
00140 
00141                 // Create the node associated to the relation.
00142                 jn.setLabel(this.__createJoinLabel(joins, digraph.getRankdir()));
00143                 jn.setShape("record");
00144                 jn.setStyle("filled");
00145                 jn.setColor(Conf.RELATION_FG_COLOR);
00146                 jn.setFillColor(Conf.RELATION_BG_COLOR);
00147 
00148                 String edge_color = this._relationColor(this._db.getTableToTableRelationType(dependent_table, reference_table));
00149 
00150                 // Create the edge from the source table to the relation's
00151                 // node.
00152                 src2node.setFrom(dependent_table.getName());
00153                 src2node.setTo(node_name);
00154                 src2node.setPenwidth("2");
00155                 src2node.setLtail(SubGraph.generateName(dependent_table.getName()));
00156                 src2node.setColor(edge_color);
00157                 src2node.setArrowhead("none");
00158 
00159                 // Create the edge from the relation's node to the
00160                 // destination table.
00161                 node2dst.setFrom(node_name);
00162                 node2dst.setTo(reference_table.getName());
00163                 node2dst.setPenwidth("2");
00164                 node2dst.setLhead(SubGraph.generateName(reference_table.getName()));
00165                 node2dst.setColor(edge_color);
00166 
00167                 // Add nodes and edge to the graph.
00168                 digraph.addNode(jn);
00169                 digraph.addEdge(src2node);
00170                 digraph.addEdge(node2dst);
00171             }
00172         }
00173 
00174         return digraph.toString();
00175     }
00176 
00177     /**
00178      * This method creates the &quot;full&quot; label for a given table. The label is a
00179      * grid that contains 5 columns, and as many row as the number of fields in
00180      * the table.
00181      *
00182      * @param in_table
00183      *            The table.
00184      * @param in_rankdir
00185      *            Initial orientation of a record node. Values can be:
00186      *            <ul>
00187      *               <li>TB (Top to bottom)</li>
00188      *               <li>LR (Left to right)</li>
00189      *               <li>RL 'Right to left)</li>
00190      *            </ul>
00191      * <p>See GRAPHVIZ' documentation for "rankdir": http://www.graphviz.org/doc/info/shapes.html</p>
00192      * @return The method returns a string that represents the label.
00193      */
00194     private String __createFullTableLabel(Table in_table, String in_rankdir)
00195     {
00196         GridByRow label = new GridByRow();
00197         label.setRankdir(in_rankdir);
00198 
00199         // Create rows: {key, null, index, name, link}.
00200         for (Field field : in_table.getFields())
00201         {
00202             ArrayList<String> line = new ArrayList<String>();
00203 
00204             // Is the field a primary or a foreign key?
00205             // System.out.println("> " + field.toString());
00206             
00207             if (field.isPrimaryKey())
00208             { line.add("PRI"); }
00209             else
00210             {
00211                 if (field.isForeignKey()) { line.add("FK"); }
00212                 else
00213                 { 
00214                     if (field.isDeadForeignKey())
00215                     {
00216                         try { line.add("DFK: " + field.getDeadForeignKey().getFullName()); }
00217                         catch (Exception e) { line.add("Interal error!"); }
00218                     }
00219                     else { line.add("-"); }
00220                 }
00221             }
00222             // Can the field be NULL?
00223             if (field.isNull()) { line.add("NULL"); }
00224             else { line.add("-"); }
00225 
00226             // Is the field an index?
00227             if (field.isMultipleIndex())
00228             { line.add("MUL"); }
00229             else if (field.isUniqueIndex())
00230             { line.add("UNI"); }
00231             else { line.add("-"); }
00232 
00233             // Add the name of the field.
00234             line.add(field.getName());
00235 
00236             // Is the field a starting point for a link?
00237             if (field.isForeignKey())
00238             {
00239                 if (field.isHardForeignKey())
00240                 { line.add("H"); }
00241                 else
00242                 { line.add("S"); }
00243             }
00244             else
00245             { line.add("-"); }
00246 
00247             // Add the line to the label.
00248             label.add(line);
00249         }
00250 
00251         return label.toString();
00252     }
00253 
00254     /**
00255      * This method creates the &quot;index&quot; label for a given table.
00256      *
00257      * @param in_table
00258      *            The table.
00259      * @param in_rankdir
00260      *            Initial orientation of a record node. Values can be:
00261      *            <ul>
00262      *               <li>TB (Top to bottom)</li>
00263      *               <li>LR (Left to right)</li>
00264      *               <li>RL 'Right to left)</li>
00265      *            </ul>
00266      * <p>See GRAPHVIZ' documentation for "rankdir": http://www.graphviz.org/doc/info/shapes.html</p>
00267      * @return The method returns a string that represents the label.
00268      */
00269     private String __createIndexLabel(Table in_table, String in_rankdir) throws Exception
00270     {
00271         GridByRow label = new GridByRow();
00272         label.setRankdir(in_rankdir);
00273         
00274         Hashtable<String, DbIndex> indexes = in_table.getCompositeIndexes();
00275         for (Enumeration<String> e = indexes.keys(); e.hasMoreElements(); )
00276         {
00277             String  index_name = e.nextElement();
00278             DbIndex index      = indexes.get(index_name);
00279             Integer count      = index.fieldCount();
00280             
00281             String idx = index.isUnique() ? "UNI:\\l" : "MUL:\\l";
00282             for (int i=0; i<count; i++)
00283             {
00284                 idx += " - " + index.getField(new Integer(i)).getName() + "\\l";
00285             }
00286             
00287             ArrayList<String> l = new ArrayList<String>();
00288             l.add(idx);
00289             label.add(l);
00290         }
00291         
00292         return label.toString();
00293     }
00294     
00295     /**
00296      * This method generates the label that applies to a given join between two
00297      * tables.
00298      *
00299      * @param in_relations
00300      *            List of relations included in the join.
00301      * @param in_rankdir
00302      *            Initial orientation of a record node. Values can be:
00303      *            <ul>
00304      *               <li>TB (Top to bottom)</li>
00305      *               <li>LR (Left to right)</li>
00306      *               <li>RL 'Right to left)</li>
00307      *            </ul>
00308      *            <p>See GRAPHVIZ' documentation for "rankdir": http://www.graphviz.org/doc/info/shapes.html</p>
00309      * @return The method returns the DOT's representation of the join.
00310      */
00311     private String __createJoinLabel(ArrayList<FieldToFieldJoin> in_relations, String in_rankdir)
00312     {
00313         GridByRow label = new GridByRow();
00314         label.setRankdir(in_rankdir);
00315 
00316         for (FieldToFieldJoin join : in_relations)
00317         {
00318             ArrayList<String> l = new ArrayList<String>();
00319             l.add(join.cardinality_from_reference_to_dependent());
00320             l.add(join.src_field.getName());
00321             l.add(join.dst_field.getName());
00322             l.add(join.cardinality_from_dependent_to_reference());
00323             label.add(l);
00324         }
00325         return label.toString();
00326     }
00327 
00328 }