DBVIEW
|
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 "full" 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 "index" 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 }