DBVIEW
src/org/dbview/db/structure/Database.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.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 }