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.db.structure; 00024 00025 import org.dbview.utils.Strings; 00026 00027 import java.util.Hashtable; 00028 import java.util.ArrayList; 00029 import java.util.Enumeration; 00030 import java.util.Collections; 00031 import org.jgrapht.*; 00032 import org.jgrapht.graph.*; 00033 import java.lang.Math; 00034 00035 00036 00037 00038 /** 00039 * This class represents a database. 00040 * @author Denis Beurive 00041 */ 00042 public class Database 00043 { 00044 /** 00045 * This value is used by the method that generates unique names that don't represent tables. 00046 */ 00047 private static Integer __n = 0; 00048 00049 /** 00050 * This constant represents a minimum cardinality of 0 (for join). 00051 */ 00052 public static final int MIN_0 = 0; 00053 00054 /** 00055 * This constant represents a minimum cardinality of 1 (for join). 00056 */ 00057 public static final int MIN_1 = 1; 00058 00059 /** 00060 * This constant represents a maximum cardinality of 1 (for join). 00061 */ 00062 public static final int MAX_1 = 1; 00063 00064 /** 00065 * This constant represents a maximum cardinality of N (for join). 00066 */ 00067 public static final int MAX_N = -1; 00068 00069 /** 00070 * This constant represents an unknown type of relation. 00071 * @remark Please note that this value is used for initialization only. 00072 * Once the database is loaded, no link should present this value. 00073 */ 00074 public static final int UNDEFINED_LINK = -1; 00075 00076 /** 00077 * This constant represents a "soft relation". 00078 * @remark A soft relation contains only soft joins. 00079 * A soft join is defined by foreign keys' names only. You can find this type of joins in MyIsam (MySql) tables, for example. 00080 */ 00081 public static final int SOFT_LINK = 0; 00082 00083 /** 00084 * This constant represents a "mixed relation". 00085 * This type of relation is a mix between soft and hard joins. 00086 * @remark A soft relation contains only soft joins. 00087 * A soft join is defined by foreign keys' names only. You can find this type of joins in MyIsam (MySql) tables, for example. 00088 * @remark A hard join is defined by the table's constraint. 00089 */ 00090 public static final int SOFT_AND_HARD_LINK = 1; 00091 00092 /** 00093 * This constant represents a "hard relation". 00094 * @remark A hard relation contains only hard joins. 00095 * This type of join is defined by a table's constraints only. 00096 */ 00097 public static final int HARD_LINK = 2; 00098 00099 /** 00100 * This hash table lists all database's tables. 00101 * <ul> 00102 * <li>Hash's key: The name of the table.</li> 00103 * <li>Hash's value: The table which name is defined as key.</li> 00104 * </ul> 00105 */ 00106 private Hashtable<String, Table> __tables = new Hashtable<String, Table>(); 00107 00108 // TDON 00109 /** 00110 * This hash contains all relations between tables. 00111 * <ul> 00112 * <li>Hash's key: The "dependent" table.</li> 00113 * <li>Hash's value: A hash that lists all relation from the (dependent) table used as key. Relations are grouped by "reference" tables. 00114 * <ul> 00115 * <li>Hash's key: The "reference" table.</li> 00116 * <li>Hash's value: Relations' "meta data" container. See class MetaData.</li> 00117 * </ul> 00118 * </li> 00119 * </ul> 00120 */ 00121 private Hashtable<Table, Hashtable<Table, TableToTableRelation>> __relations = null; 00122 00123 /** 00124 * Name of the database. 00125 */ 00126 private String __name = null; 00127 00128 /** 00129 * Create a database. 00130 * @param in_name Name of the database. 00131 */ 00132 public Database(String in_name) 00133 { 00134 this.__name = in_name; 00135 } 00136 00137 /** 00138 * Return the name of the database. 00139 * @return The method returns the name of the database. 00140 */ 00141 public String getName() 00142 { 00143 return this.__name; 00144 } 00145 00146 /** 00147 * Return a table. 00148 * @param in_name Nane of the table to return. 00149 * @return The method returns a table. 00150 * If the table does not exist in the database, then the method returns the value null. 00151 */ 00152 public Table getTable(String in_name) 00153 { 00154 if (! this.__tables.containsKey(in_name)) { /* System.out.println("Key not found!!!"); */ } 00155 else { /* System.out.println(in_name + " > Key found!!!"); */ } 00156 return this.__tables.get(in_name); 00157 } 00158 00159 /** 00160 * Return the list of all tables in the database. 00161 * @return The method returns the list of all tables in the database. 00162 */ 00163 public ArrayList<Table> getTables() 00164 { 00165 return Collections.list(this.__tables.elements()); 00166 } 00167 00168 /** 00169 * Add a table to the database. 00170 * @param in_table Table to add. 00171 * @throws org.dbview.db.structure.DatabaseException 00172 * @remark This method is accessible for package's members only. 00173 * Adding a table to a database is done when a table is created. 00174 */ 00175 void addTable(Table in_table) throws org.dbview.db.structure.DatabaseException 00176 { 00177 String table_name = in_table.getName(); 00178 if (null != this.__tables.get(table_name)) 00179 { throw new org.dbview.db.structure.DatabaseException("Table \"" + table_name + "\" already exists!"); } 00180 this.__tables.put(table_name, in_table); 00181 } 00182 00183 /** 00184 * This method generates the list of all relations for the current database. 00185 * @throws Exception 00186 */ 00187 public void generateRelations() throws Exception 00188 { 00189 this.__relations = new Hashtable<Table, Hashtable<Table, TableToTableRelation>>(); 00190 for (Table table: this.getTables()) 00191 { this.__relations.put(table, this.__getRelationsFromTable(table)); } 00192 } 00193 00194 // TDON 00195 /** 00196 * This method returns the list of "reference" tables in contact with a given "dependent" table. 00197 * @param in_dependent_table The "dependent" table. 00198 * @return the method returns the list of "reference" tables in contact with the given "dependent" table. 00199 */ 00200 public ArrayList<Table> getReferenceTables(Table in_dependent_table) throws Exception 00201 { 00202 if (null == this.__relations) { this.generateRelations(); } 00203 00204 Hashtable<Table, TableToTableRelation> tbls = this.__relations.get(in_dependent_table); 00205 if (null == tbls) { throw new Exception("You try to get the list of reference tables from a reference table that does not exist!"); } 00206 00207 Enumeration<Table> tables = tbls.keys(); 00208 ArrayList<Table> references = new ArrayList<Table>(); 00209 while (tables.hasMoreElements()) { references.add(tables.nextElement()); } 00210 return references; 00211 } 00212 00213 // TDON 00214 /** 00215 * This method returns the list of "reference" tables in contact with a given "dependent" table, identified by its name. 00216 * @param in_dependent_table_name Name of the "dependent" table. 00217 * @return This method returns the list of "reference" tables in contact with a given "dependent" table, identified by its name. 00218 */ 00219 public ArrayList<Table> getReferenceTables(String in_dependent_table_name) throws Exception 00220 { 00221 return this.getReferenceTables(this.getTable(in_dependent_table_name)); 00222 } 00223 00224 // TDON 00225 /** 00226 * This method returns the list of "dependent" tables in contact with a given "reference" table. 00227 * @param in_reference_table The "reference" table. 00228 * @return the method returns the list of "dependent" tables in contact with a given "reference" table. 00229 */ 00230 public ArrayList<Table> getDependentTables(Table in_reference_table) throws Exception 00231 { 00232 if (null == this.__relations) { this.generateRelations(); } 00233 00234 Hashtable<Table, ArrayList<FieldToFieldJoin>> relations = this.__getRelationsToTable(in_reference_table); 00235 00236 return Collections.list(relations.keys()); 00237 } 00238 00239 // TDON 00240 /** 00241 * This method returns the list of "dependent" tables in contact with a given "reference" table, identified by its name. 00242 * @param in_reference_table_name The "reference" table_name. 00243 * @return This method returns the list of "dependent" tables in contact with a given "reference" table, identified by its name. 00244 */ 00245 public ArrayList<Table> getDependentTables(String in_reference_table_name) throws Exception 00246 { 00247 return this.getDependentTables(this.getTable(in_reference_table_name)); 00248 } 00249 00250 // TDON 00251 /** 00252 * This method returns the list of <i>relations</i> from a given "dependent" table to a given "reference" table. 00253 * @param in_dependent_table The "dependent" table. 00254 * @param in_reference_table The "reference" table. 00255 * @return The method returns the list of relations from the given "dependent" table to the given "reference" table. 00256 * @remark A <i>relation</i> is a global link between two tables, while a <i>join</i> is a link between two fields. 00257 */ 00258 public ArrayList<FieldToFieldJoin> getRelationsFromReferenceToTarget(Table in_dependent_table, Table in_reference_table) throws Exception 00259 { 00260 if (null == this.__relations) { this.generateRelations(); } 00261 00262 // System.out.println(in_dependent_table.getName() + " => " + in_reference_table.getName()); 00263 Hashtable<Table, TableToTableRelation> ref_relations = this.__relations.get(in_dependent_table); 00264 if (null == ref_relations) { throw new Exception("You try to get the list of reference tables from a reference table that does not exist!"); } 00265 00266 // We check that we found something. 00267 if (! ref_relations.containsKey(in_reference_table)) { return new ArrayList<FieldToFieldJoin>(); } 00268 ArrayList<FieldToFieldJoin> r = ref_relations.get(in_reference_table).relations; 00269 if (null == r) { return new ArrayList<FieldToFieldJoin>(); } 00270 return r; 00271 } 00272 00273 // TDON 00274 /** 00275 * This method returns the type of <i>relation</i> between to given tables. 00276 * @param in_dependent_table The "dependent" table. 00277 * @param in_reference_table The "reference" table. 00278 * @return The method returns the type of relation between the given dependent table and the given reference table. 00279 * It can be: 00280 * <ul> 00281 * <li>SOFT_AND_HARD_LINK</li> 00282 * <li>SOFT_LINK</li> 00283 * <li>HARD_LINK</li> 00284 * </ul> 00285 */ 00286 public int getTableToTableRelationType(Table in_dependent_table, Table in_reference_table) throws Exception 00287 { 00288 Hashtable<Table, TableToTableRelation> ref_relations = this.__relations.get(in_dependent_table); 00289 if (null == ref_relations) { throw new Exception("You try to get the type of relation from a reference table that does not exist!"); } 00290 00291 TableToTableRelation meta = ref_relations.get(in_reference_table); 00292 if (null == meta) { throw new Exception("You try to get the type of relation to a reference table that does not exist!"); } 00293 00294 if (meta.type == Database.UNDEFINED_LINK) { throw new Exception("The type of relation from a dependent table to a reference table is not defined! This situation should not happen!"); } 00295 00296 return meta.type; 00297 } 00298 00299 // TDON 00300 /** 00301 * This method returns the list of <i>relations</i> from a given "dependent" table (identified by its name) to a given "reference" table (identified by its name). 00302 * @param in_dependent_table_name Name of the dependent table. 00303 * @param in_reference_table_name Name of the reference table. 00304 * @return The method returns the list of relations from then given dependent table to the given reference table. 00305 */ 00306 public ArrayList<FieldToFieldJoin> getRelationsFromReferenceToTarget(String in_dependent_table_name, String in_reference_table_name) throws Exception 00307 { 00308 return this.getRelationsFromReferenceToTarget(this.getTable(in_dependent_table_name), this.getTable(in_reference_table_name)); 00309 } 00310 00311 // TDON 00312 /** 00313 * This method returns the neighborhood of a given table. 00314 * @param in_table The table. 00315 * @return The method returns the list of tables in the neighborhood of the given table. 00316 * @warning The returned list does not contain the given table. 00317 */ 00318 public ArrayList<Table> getNeighborhood(Table in_table) throws Exception 00319 { 00320 ArrayList<Table> references = this.getReferenceTables(in_table); 00321 ArrayList<Table> dependents = this.getDependentTables(in_table); 00322 00323 for(Table table: dependents) 00324 { 00325 if (! references.contains(table)) { references.add(table); } 00326 } 00327 00328 int n = references.indexOf(in_table); 00329 if (-1 != n) { references.remove(n); } 00330 00331 return references; 00332 } 00333 00334 /** 00335 * This method returns the neighborhood of a given table, identified by its name. 00336 * @param in_table_name The name of the table. 00337 * @return The method returns the list of tables in the neighborhood of the given table, identified by its name. 00338 * @warning The returned list does not contain the given table. 00339 */ 00340 public ArrayList<Table> getNeighborhood(String in_table_name) throws Exception 00341 { 00342 Table t = this.getTable(in_table_name); 00343 if (null == t) { throw new Exception("You try to get the neighborhood of a table (" + in_table_name + ") that does not exist!"); } 00344 return this.getNeighborhood(t); 00345 } 00346 00347 /** 00348 * This method returns the neighborhood of a given list of tables. 00349 * The size of the neighbourhood area is defined by an integer (the zoom level). 00350 * @param in_tables The list of tables. 00351 * @param in_level Zoom level (starts at 0). 00352 * @return The method returns the neighborhood of the given list of tables. 00353 * @throws Exception 00354 */ 00355 public ArrayList<Table> zoomAroundByTables(ArrayList<Table> in_tables, Integer in_level) throws Exception 00356 { 00357 // WARNING: This is "shallow copy"... But it is necessary! 00358 @SuppressWarnings("unchecked") 00359 ArrayList<Table> to_zoom = (ArrayList<Table>)in_tables.clone(); 00360 ArrayList<Table> tables = new ArrayList<Table>(); 00361 ArrayList<Table> zoomed = new ArrayList<Table>(); 00362 Integer level = in_level; 00363 00364 // for (Table table: to_zoom) 00365 // { System.out.println("0> " + table.getName()); } 00366 00367 // System.out.println("> " + in_level); 00368 for (int l=0; l<=in_level; l++) 00369 { 00370 ArrayList<Table> new_zoom = new ArrayList<Table>(); 00371 00372 for (Table zoom: to_zoom) 00373 { 00374 // Did we already zoom on this table? 00375 if (zoomed.contains(zoom)) { continue; } 00376 if (! this.__tables.containsValue(zoom)) { throw new Exception("You try to zoom around a table that does not exist! Table: " + zoom.toString()); } 00377 00378 // The neighborhood of a give table contains the given table. 00379 if (! tables.contains(zoom)) { tables.add(zoom); } 00380 00381 // Get the neighborhood of the current table. 00382 if (level > 0) 00383 { 00384 ArrayList<Table> neighborhood = this.getNeighborhood(zoom); 00385 00386 // System.out.println("neighborhood for " + zoom.getName()); 00387 // for (Table table: neighborhood) 00388 // { System.out.println("1> " + table.getName()); } 00389 // System.out.println(); 00390 00391 for (Table table: neighborhood) 00392 { 00393 if (! tables.contains(table)) { tables.add(table); } 00394 // We may have to zoom around this table later. 00395 if (! new_zoom.contains(table)) { new_zoom.add(table); } 00396 } 00397 00398 } 00399 00400 // OK, zoomed of the current table. 00401 zoomed.add(zoom); 00402 00403 00404 } 00405 level--; 00406 00407 // WARNING: This is "shallow copy"... But it is necessary! 00408 to_zoom = (ArrayList<Table>)new_zoom.clone(); 00409 00410 00411 } 00412 00413 // for (Table table: tables) 00414 // { System.out.println("> " + table.getName()); } 00415 00416 return tables; 00417 } 00418 00419 /** 00420 * This method returns the neighborhood of a given list of tables, identified by their names. 00421 * The size of the neighbourhood area is defined by an integer (the zoom level). 00422 * @param in_table_names The list of tables' name. 00423 * @param in_level Zoom level (starts at 0). 00424 * @return The method returns the neighborhood of the given list of tables, identified by their names. 00425 * @throws Exception 00426 */ 00427 public ArrayList<Table> zoomAroundByNames(ArrayList<String> in_table_names, Integer in_level) throws Exception 00428 { 00429 // System.out.println("zoomAroundByNames"); 00430 ArrayList<Table> tables = new ArrayList<Table>(); 00431 for (String table_name: in_table_names) 00432 { 00433 // System.out.println(table_name); 00434 Table t = this.getTable(table_name); 00435 if (null == t) { throw new Exception("You try to zoom around a table (" + table_name + ") that does not exist!"); } 00436 tables.add(t); 00437 } 00438 return this.zoomAroundByTables(tables, in_level); 00439 } 00440 00441 // TDON 00442 /** 00443 * This method returns the relations from a given "dependent" table (to a list of "reference" table(s)). 00444 * @param in_dependent_table Table for which you want to get the list of relations. 00445 * @return The method returns a hash table that contains "table to table" relation(s). 00446 * <ul> 00447 * <li>Keys: A "reference" table.</li> 00448 * <li>Values: The "table to table" relation between the given "dependent" table and its associated "reference" tables.</li> 00449 * </ul> 00450 * @throws Exception 00451 */ 00452 private Hashtable<Table, TableToTableRelation> __getRelationsFromTable(Table in_dependent_table) throws Exception 00453 { 00454 if (null == this.__relations) { this.generateRelations(); } 00455 Hashtable<Table, TableToTableRelation> relations = new Hashtable<Table, TableToTableRelation>(); 00456 00457 // Get the list of foreign keys. 00458 for (Field field: in_dependent_table.getForeignKeys()) 00459 { 00460 FieldToFieldJoin r = new FieldToFieldJoin(); 00461 Field target = field.getReference(); 00462 Table reference = target.getTable(); 00463 00464 r.src_field = field; 00465 r.dst_field = field.getReference(); 00466 r.src_min = field.isNull() ? Database.MIN_0 : Database.MIN_1; 00467 r.src_max = field.isUnique() ? Database.MAX_1 : Database.MAX_N; 00468 r.dst_min = target.isNull() ? Database.MIN_0 : Database.MIN_1; 00469 r.dst_max = target.isUnique() ? Database.MAX_1 : Database.MAX_N; 00470 00471 r.type = field.isHardForeignKey() ? Database.HARD_LINK : Database.SOFT_LINK; 00472 00473 if (! relations.containsKey(reference)) { relations.put(reference, new TableToTableRelation()); } 00474 relations.get(reference).relations.add(r); 00475 this.__updateMetaRelationType(relations.get(reference), r.type); 00476 } 00477 00478 return relations; 00479 } 00480 00481 /** 00482 * This method is used by the method "__getRelationsFromTable()" only. 00483 * @param in_meta "Table to table" relation to update. 00484 * @param new_relation_type Type of the new "field to field" relation found in the "table to table" relation. 00485 */ 00486 private void __updateMetaRelationType(TableToTableRelation in_meta, int new_relation_type) 00487 { 00488 if (Database.UNDEFINED_LINK == in_meta.type) { in_meta.type = new_relation_type; return; }; 00489 if (Database.SOFT_AND_HARD_LINK == in_meta.type) { return; } 00490 if (new_relation_type != in_meta.type) { in_meta.type = Database.SOFT_AND_HARD_LINK; } 00491 } 00492 00493 // TDON 00494 /** 00495 * This method returns the list of <i>joins</i> (between fields) to a given "reference" table. 00496 * @param in_reference_table Table for which you want to get the list of <i>joins</i> to. 00497 * @return The method returns a hash table that contains the list of <i>joins</i> to the given (reference) table. 00498 * <ul> 00499 * <li>Keys : "Dependent" tables.</li> 00500 * <li>Values : A list of joins.</li> 00501 * </ul> 00502 */ 00503 private Hashtable<Table, ArrayList<FieldToFieldJoin>> __getRelationsToTable(Table in_reference_table) throws Exception 00504 { 00505 if (null == this.__relations) { this.generateRelations(); } 00506 Hashtable<Table, ArrayList<FieldToFieldJoin>> relations = new Hashtable<Table, ArrayList<FieldToFieldJoin>>(); 00507 00508 for (Table dependent_table: this.getTables()) 00509 { 00510 ArrayList<FieldToFieldJoin> rels = this.getRelationsFromReferenceToTarget(dependent_table, in_reference_table); 00511 if (rels.size() > 0) { relations.put(dependent_table, rels); } 00512 } 00513 00514 // System.out.println("__getRelationsToTable() done ok"); 00515 return relations; 00516 } 00517 00518 // TDON 00519 /** 00520 * This method returns the JGRAPHT representation of a given set of tables. 00521 * @param in_tables Set of tables. 00522 * If the parameter's value is null, then the model is built using all the database's tables. 00523 * @return The method returns the JGRAPHT representation of the given set of tables (or the complete set of tables, if the specified set is null). 00524 * @see http://jgrapht.org/ 00525 * @throws Exception 00526 */ 00527 public UndirectedGraph<Table, JgraphtTableToTableEdge> jgrapht(ArrayList<Table> in_tables) throws Exception 00528 { 00529 ArrayList<Table> tables = null == in_tables ? this.getTables() : in_tables; 00530 UndirectedGraph<Table, JgraphtTableToTableEdge> g = new Pseudograph<Table, JgraphtTableToTableEdge>(new ClassBasedEdgeFactory<Table, JgraphtTableToTableEdge>(JgraphtTableToTableEdge.class)); 00531 00532 // Create all nodes. 00533 for (Table table: tables) { g.addVertex(table); } 00534 00535 // Create all edges. 00536 for (Table table : tables) 00537 { 00538 for (Table reference: this.getReferenceTables(table.getName())) 00539 { 00540 if (! g.addEdge(table, reference, new JgraphtTableToTableEdge())) 00541 { 00542 System.out.println("The edge already exists!"); 00543 } 00544 } 00545 } 00546 00547 return g; 00548 } 00549 00550 /** 00551 * This method produces a textual representation of the database. 00552 * @return The method produces a textual representation of the database. 00553 * @remark This method is used for debugging. 00554 */ 00555 public String toString() 00556 { 00557 ArrayList<String> result = new ArrayList<String>(); 00558 Enumeration<Table> e = this.__tables.elements(); 00559 00560 result.add(this.__name); 00561 while (e.hasMoreElements()) 00562 { 00563 Table t = e.nextElement(); 00564 result.add(Strings.indent("\t", t.toString())); 00565 } 00566 00567 return Strings.joinWithNewLines(result); 00568 } 00569 00570 /** 00571 * This method returns an identifier that does not represent a table's name. 00572 * Each call to this method returns a unique identifier. 00573 * @return The method returns an identifier that does not represent a table's name. 00574 * @throws Exception 00575 */ 00576 public String getNonTableTag() throws Exception 00577 { 00578 00579 int lower = 111; 00580 int higher = 999999; 00581 String name = null; 00582 00583 while (Boolean.TRUE) 00584 { 00585 Integer random = (int)(Math.random() * (higher-lower)) + lower; 00586 name = "T" + org.dbview.utils.Strings.SHAsum(random.toString().getBytes()) + "_" + Database.__n.toString(); 00587 Database.__n += 1; 00588 if (null == this.getTable(name)) { break; } 00589 } 00590 return name; 00591 } 00592 00593 }