package tap.metadata;
/*
* This file is part of TAPLibrary.
*
* TAPLibrary 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 3 of the License, or
* (at your option) any later version.
*
* TAPLibrary 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 TAPLibrary. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2012-2016 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
* Astronomisches Rechen Institut (ARI)
*/
import java.awt.List;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import adql.db.DBColumn;
import adql.db.DBTable;
import adql.db.DBType;
import tap.TAPException;
/**
* <p>Represent a table as described by the IVOA standard in the TAP protocol definition.</p>
*
* <p>
* This object representation has exactly the same fields as the column of the table TAP_SCHEMA.tables.
* But it also provides a way to add other data. For instance, if information not listed in the standard
* may be stored here, they can be using the function {@link #setOtherData(Object)}. This object can be
* a single value (integer, string, ...), but also a {@link Map}, {@link List}, etc...
* </p>
*
* <p><i><b>Important note:</b>
* A {@link TAPTable} object MUST always have a DB name. That's why, {@link #getDBName()} returns
* what {@link #getADQLName()} returns when no DB name is set. After creation, it is possible to set
* the DB name with {@link #setDBName(String)}.
* <br/>
* This DB name MUST be UNqualified and without double quotes. If a NULL or empty value is provided,
* {@link #getDBName()} returns what {@link #getADQLName()} returns.
* </i></p>
*
* @author Grégory Mantelet (CDS;ARI)
* @version 2.1 (07/2016)
*/
public class TAPTable implements DBTable {
/**
* Different types of table according to the TAP protocol.
* The default one should be "table".
*
* @author Grégory Mantelet (ARI)
* @version 2.0 (08/2014)
*
* @since 2.0
*/
public enum TableType{
output, table, view;
}
/** Name that this table MUST have in ADQL queries. */
private final String adqlName;
/** Indicates whether the given ADQL name must be simplified by {@link #getADQLName()}.
* <p>Here, "simplification" means removing the surrounding double quotes and the schema prefix if any.</p>
* @since 2.1 */
private final boolean simplificationNeeded;
/** <p>Indicate whether the ADQL name has been given at creation with a schema prefix or not.</p>
* <p><i>Note: This information is used only when writing TAP_SCHEMA.tables or when writing the output of the resource /tables.</i></p>
* @since 2.0
* @deprecated See {@link #simplificationNeeded}, {@link #getRawName()} and {@link #getADQLName()}. */
@Deprecated
private boolean isInitiallyQualified;
/** Name that this table have in the database.
* <i>Note: If NULL, {@link #getDBName()} returns what {@link #getADQLName()} returns.</i> */
private String dbName = null;
/** The schema which owns this table.
* <i>Note: It is NULL only at the construction.
* Then, this attribute is automatically set by a {@link TAPSchema} when adding this table inside it
* with {@link TAPSchema#addTable(TAPTable)}.</i> */
private TAPSchema schema = null;
/** Type of this table.
* <i>Note: Standard TAP table field ; CAN NOT be NULL ; by default, it is "table".</i> */
private TableType type = TableType.table;
/** Descriptive, human-interpretable name of the table.
* <i>Note: Standard TAP table field ; MAY be NULL.</i>
* @since 2.0 */
private String title = null;
/** Description of this table.
* <i>Note: Standard TAP table field ; MAY be NULL.</i> */
private String description = null;
/** UType associating this table with a data-model.
* <i>Note: Standard TAP table field ; MAY be NULL.</i> */
private String utype = null;
/** Ordering index of this table inside its schema.
* <i>Note: Standard TAP table field since TAP 1.1.</i>
* @since 2.1 */
private int index = -1;
/** List of columns composing this table.
* <i>Note: all columns of this list are linked to this table from the moment they are added inside it.</i> */
protected final Map<String,TAPColumn> columns;
/** List of all foreign keys linking this table to others. */
protected final ArrayList<TAPForeignKey> foreignKeys;
/** Let add some information in addition of the ones of the TAP protocol.
* <i>Note: This object can be anything: an {@link Integer}, a {@link String}, a {@link Map}, a {@link List}, ...
* Its content is totally free and never used or checked.</i> */
protected Object otherData = null;
/**
* <p>Build a {@link TAPTable} instance with the given ADQL name.</p>
*
* <p><i>Note 1:
* The DB name is set by default to NULL so that {@link #getDBName()} returns exactly what {@link #getADQLName()} returns.
* To set a specific DB name, you MUST call {@link #setDBName(String)}.
* </i></p>
*
* <p><i>Note 2:
* The table type is set by default to "table".
* </i></p>
*
* <p><b>Important notes on the given ADQL name:</b></p>
* <ul>
* <li>Any leading or trailing space is immediately deleted.</li>
* <li>
* If the table name is prefixed by its schema name, this prefix is removed by {@link #getADQLName()}
* but will be still here when using {@link #getRawName()}. To work, the schema name must be exactly the same
* as what the function {@link TAPSchema#getRawName()} of the set schema returns.
* </li>
* <li>
* Double quotes may surround the single table name. They will be removed by {@link #getADQLName()} but will
* still appear in the result of {@link #getRawName()}.
* </li>
* </ul>
*
* @param tableName Name that this table MUST have in ADQL queries.
* <i>CAN'T be NULL ; this name can never be changed after initialization.</i>
*
* @throws NullPointerException If the given name is <code>null</code>,
* or if the given string is empty after simplification
* (i.e. without the surrounding double quotes).
*/
public TAPTable(String tableName) throws NullPointerException{
if (tableName == null)
throw new NullPointerException("Missing table name!");
adqlName = tableName.trim();
simplificationNeeded = (adqlName.indexOf('.') > 0 || adqlName.indexOf('"') >= 0);
isInitiallyQualified = (adqlName.indexOf('.') > 0);
if (getADQLName().length() == 0)
throw new NullPointerException("Missing table name!");
dbName = null;
columns = new LinkedHashMap<String,TAPColumn>();
foreignKeys = new ArrayList<TAPForeignKey>();
}
/**
* <p>Build a {@link TAPTable} instance with the given ADQL name and table type.</p>
*
* <p><i>Note 1:
* The DB name is set by default to NULL so that {@link #getDBName()} returns exactly what {@link #getADQLName()} returns.
* To set a specific DB name, you MUST call {@link #setDBName(String)}.
* </i></p>
*
* <p><i>Note 2:
* The table type is set by default to "table".
* </i></p>
*
* <p><b>Important notes on the given ADQL name:</b></p>
* <ul>
* <li>Any leading or trailing space is immediately deleted.</li>
* <li>
* If the table name is prefixed by its schema name, this prefix is removed by {@link #getADQLName()}
* but will be still here when using {@link #getRawName()}. To work, the schema name must be exactly the same
* as what the function {@link TAPSchema#getRawName()} of the set schema returns.
* </li>
* <li>
* Double quotes may surround the single table name. They will be removed by {@link #getADQLName()} but will
* still appear in the result of {@link #getRawName()}.
* </li>
* </ul>
*
* @param tableName Name that this table MUST have in ADQL queries.
* <i>CAN'T be NULL ; this name can never be changed after initialization.</i>
* @param tableType Type of this table. <i>If NULL, "table" will be the type of this table.</i>
*
* @throws NullPointerException If the given name is <code>null</code>,
* or if the given string is empty after simplification
* (i.e. without the surrounding double quotes).
*
* @see #setType(TableType)
*/
public TAPTable(String tableName, TableType tableType) throws NullPointerException{
this(tableName);
setType(tableType);
}
/**
* <p>Build a {@link TAPTable} instance with the given ADQL name, table type, description and UType.</p>
*
* <p><i>Note 1:
* The DB name is set by default to NULL so that {@link #getDBName()} returns exactly what {@link #getADQLName()} returns.
* To set a specific DB name, you MUST call {@link #setDBName(String)}.
* </i></p>
*
* <p><i>Note 2:
* The table type is set by default to "table".
* </i></p>
*
* <p><b>Important notes on the given ADQL name:</b></p>
* <ul>
* <li>Any leading or trailing space is immediately deleted.</li>
* <li>
* If the table name is prefixed by its schema name, this prefix is removed by {@link #getADQLName()}
* but will be still here when using {@link #getRawName()}. To work, the schema name must be exactly the same
* as what the function {@link TAPSchema#getRawName()} of the set schema returns.
* </li>
* <li>
* Double quotes may surround the single table name. They will be removed by {@link #getADQLName()} but will
* still appear in the result of {@link #getRawName()}.
* </li>
* </ul>
*
* @param tableName Name that this table MUST have in ADQL queries.
* <i>CAN'T be NULL ; this name can never be changed after initialization.</i>
* @param tableType Type of this table. <i>If NULL, "table" will be the type of this table.</i>
* @param description Description of this table. <i>MAY be NULL.</i>
* @param utype UType associating this table with a data-model. <i>MAY be NULL</i>
*
* @throws NullPointerException If the given name is <code>null</code>,
* or if the given string is empty after simplification
* (i.e. without the surrounding double quotes).
*
* @see #setType(TableType)
*/
public TAPTable(String tableName, TableType tableType, String description, String utype) throws NullPointerException{
this(tableName, tableType);
this.description = description;
this.utype = utype;
}
/**
* <p>Get the qualified name of this table.</p>
*
* <p><i><b>Warning:</b>
* The part of the returned full name won't be double quoted!
* </i></p>
*
* <p><i>Note:
* If this table is not attached to a schema, this function will just return
* the ADQL name of this table.
* </i></p>
*
* @return Qualified ADQL name of this table.
*/
public final String getFullName(){
if (schema != null)
return schema.getADQLName() + "." + getADQLName();
else
return adqlName;
}
/**
* Get the ADQL name (the name this table MUST have in ADQL queries).
*
* @return Its ADQL name.
* @see #getADQLName()
* @deprecated Does not do anything special: just call {@link #getADQLName()}.
*/
@Deprecated
public final String getName(){
return getADQLName();
}
@Override
public final String getADQLName(){
if (simplificationNeeded){
String tmp = adqlName;
// Remove the schema prefix if any:
if (schema != null && tmp.startsWith(schema.getRawName() + "."))
tmp = tmp.substring((schema.getRawName() + ".").length()).trim();
// Remove the surrounding double-quotes if any:
if (tmp.matches("\"[^\"]*\""))
tmp = tmp.substring(1, tmp.length() - 1);
// Finally, return the result:
return tmp;
}else
return adqlName;
}
/**
* Get the full ADQL name of this table, as it has been provided at initialization.
*
* @return Get the original ADQL name.
*
* @since 2.1
*/
public final String getRawName(){
return adqlName;
}
/**
* <p>Tells whether the ADQL name of this table must be qualified in the "table_name" column of TAP_SCHEMA.tables
* and in the /schema/table/name field of the resource /tables.</p>
*
* <p><i>Note: this value is set automatically by the constructor: "true" if the table name was qualified,
* "false" otherwise. It can be changed with the function {@link #setInitiallyQualifed(boolean)}, BUT by doing so
* you may generate a mismatch between the table name of TAP_SCHEMA.tables and the one of /tables.</i></p>
*
* @return <i>true</i> if the table name must be qualified in TAP_SCHEMA.tables and in /tables, <i>false</i> otherwise.
*
* @since 2.0
* @deprecated To get name of the table as it should be used: {@link #getRawName()}.
* To get just the table name (with no prefix and surrounding double quotes): {@link #getADQLName()}.
*/
@Deprecated
public final boolean isInitiallyQualified(){
return isInitiallyQualified;
}
/**
* <p>Let specifying whether the table name must be qualified in TAP_SCHEMA.tables and in the resource /tables.</p>
*
* <p><b>WARNING: Calling this function may generate a mismatch between the table name of TAP_SCHEMA.tables and
* the one of the resource /tables. So, be sure to change this flag before setting the content of TAP_SCHEMA.tables
* using {@link tap.db.JDBCConnection#setTAPSchema(TAPMetadata)}.</b></p>
*
* @param mustBeQualified <i>true</i> if the table name in TAP_SCHEMA.tables and in the resource /tables must be qualified by the schema name,
* <i>false</i> otherwise.
*
* @since 2.0
* @deprecated To get name of the table as it should be used: {@link #getRawName()}.
* To get just the table name (with no prefix and surrounding double quotes): {@link #getADQLName()}.
*/
@Deprecated
public final void setInitiallyQualifed(final boolean mustBeQualified){
isInitiallyQualified = mustBeQualified;
}
@Override
public final String getDBName(){
return (dbName == null) ? getADQLName() : dbName;
}
/**
* <p>Change the name that this table MUST have in the database (i.e. in SQL queries).</p>
*
* <p><i>Note:
* If the given value is NULL or an empty string, {@link #getDBName()} will return exactly what {@link #getADQLName()} returns.
* </i></p>
*
* @param name The new database name of this table.
*/
public final void setDBName(String name){
name = (name != null) ? name.trim() : name;
if (name != null && name.length() > 0)
dbName = name;
else
dbName = null;
}
@Override
public String getADQLCatalogName(){
return null;
}
@Override
public String getDBCatalogName(){
return null;
}
@Override
public final String getADQLSchemaName(){
return schema == null ? null : schema.getADQLName();
}
@Override
public final String getDBSchemaName(){
return schema == null ? null : schema.getDBName();
}
/**
* Get the schema that owns this table.
*
* @return Its schema. <i>MAY be NULL</i>
*/
public final TAPSchema getSchema(){
return schema;
}
/**
* <p>Set the schema in which this schema is.</p>
*
* <p><i><b>Warning:</b>
* For consistency reasons, this function SHOULD be called only by the {@link TAPSchema}
* that owns this table.
* </i></p>
*
* <p><i><b>Important note:</b>
* If this table was already linked with another {@link TAPSchema} object, the previous link is removed
* here, but also in the schema (by calling {@link TAPSchema#removeTable(String)}).
* </i></p>
*
* @param schema The schema that owns this table.
*/
protected final void setSchema(final TAPSchema schema){
if (this.schema != null && (schema == null || !schema.equals(this.schema)))
this.schema.removeTable(adqlName);
this.schema = schema;
}
/**
* Get the type of this table.
*
* @return Its type.
*/
public final TableType getType(){
return type;
}
/**
* <p>Set the type of this table.</p>
*
* <p><i>Note:
* If the given type is NULL, nothing will be done ; the type of this table won't be changed.
* </i></p>
*
* @param type Its new type.
*/
public final void setType(TableType type){
if (type != null)
this.type = type;
}
/**
* Get the title of this table.
*
* @return Its title. <i>MAY be NULL</i>
*
* @since 2.0
*/
public final String getTitle(){
return title;
}
/**
* Set the title of this table.
*
* @param title Its new title. <i>MAY be NULL</i>
*
* @since 2.0
*/
public final void setTitle(final String title){
this.title = title;
}
/**
* Get the description of this table.
*
* @return Its description. <i>MAY be NULL</i>
*/
public final String getDescription(){
return description;
}
/**
* Set the description of this table.
*
* @param description Its new description. <i>MAY be NULL</i>
*/
public final void setDescription(String description){
this.description = description;
}
/**
* Get the UType associating this table with a data-model.
*
* @return Its UType. <i>MAY be NULL</i>
*/
public final String getUtype(){
return utype;
}
/**
* Set the UType associating this table with a data-model.
*
* @param utype Its new UType. <i>MAY be NULL</i>
*/
public final void setUtype(String utype){
this.utype = utype;
}
/**
* Get the ordering index of this table inside its schema.
*
* @return Its ordering index.
*
* @since 2.1
*/
public final int getIndex(){
return index;
}
/**
* Set the ordering index of this table inside its schema.
*
* @param tableIndex Its new ordering index.
*
* @since 2.1
*/
public final void setIndex(int tableIndex){
this.index = tableIndex;
}
/**
* <p>Get the other (piece of) information associated with this table.</p>
*
* <p><i>Note:
* By default, NULL is returned, but it may be any kind of value ({@link Integer},
* {@link String}, {@link Map}, {@link List}, ...).
* </i></p>
*
* @return The other (piece of) information. <i>MAY be NULL</i>
*/
public Object getOtherData(){
return otherData;
}
/**
* Set the other (piece of) information associated with this table.
*
* @param data Another information about this table. <i>MAY be NULL</i>
*/
public void setOtherData(Object data){
otherData = data;
}
/**
* <p>Add a column to this table.</p>
*
* <p><i>Note:
* If the given column is NULL, nothing will be done.
* </i></p>
*
* <p><i><b>Important note:</b>
* By adding the given column inside this table, it
* will be linked with this table using {@link TAPColumn#setTable(DBTable)}.
* In this function, if the column was already linked with another {@link TAPTable},
* the former link is removed using {@link TAPTable#removeColumn(String)}.
* </i></p>
*
* @param newColumn Column to add inside this table.
*/
public final void addColumn(final TAPColumn newColumn){
if (newColumn != null && newColumn.getADQLName() != null){
newColumn.setTable(this);
columns.put(newColumn.getADQLName(), newColumn);
}
}
/**
* <p>Build a {@link TAPColumn} object whose the ADQL and DB name will the given one.
* Then, add this column inside this table.</p>
*
* <p><i>Note:
* The built {@link TAPColumn} object is returned, so that being modified afterwards if needed.
* </i></p>
*
* @param columnName ADQL name (and indirectly also the DB name) of the column to create and add.
*
* @return The created and added {@link TAPColumn} object,
* or NULL if the given name is NULL or an empty string.
*
* @see TAPColumn#TAPColumn(String)
* @see #addColumn(TAPColumn)
*/
public final TAPColumn addColumn(String columnName){
if (columnName == null || columnName.trim().length() <= 0)
return null;
TAPColumn c = new TAPColumn(columnName);
addColumn(c);
return c;
}
/**
* <p>Build a {@link TAPColumn} object whose the ADQL and DB name will the given one.
* Then, add this column inside this table.</p>
*
* <p><i>Note:
* The built {@link TAPColumn} object is returned, so that being modified afterwards if needed.
* </i></p>
*
* @param columnName ADQL name (and indirectly also the DB name) of the column to create and add.
* @param datatype Type of the new column's values. <i>If NULL, VARCHAR will be the type of the created column.</i>
* @param description Description of the new column. <i>MAY be NULL</i>
* @param unit Unit of the new column's values. <i>MAY be NULL</i>
* @param ucd UCD describing the scientific content of the new column. <i>MAY be NULL</i>
* @param utype UType associating the new column with a data-model. <i>MAY be NULL</i>
*
* @return The created and added {@link TAPColumn} object,
* or NULL if the given name is NULL or an empty string.
*
* @see TAPColumn#TAPColumn(String, DBType, String, String, String, String)
* @see #addColumn(TAPColumn)
*/
public TAPColumn addColumn(String columnName, DBType datatype, String description, String unit, String ucd, String utype){
if (columnName == null || columnName.trim().length() <= 0)
return null;
TAPColumn c = new TAPColumn(columnName, datatype, description, unit, ucd, utype);
addColumn(c);
return c;
}
/**
* <p>Build a {@link TAPColumn} object whose the ADQL and DB name will the given one.
* Then, add this column inside this table.</p>
*
* <p><i>Note:
* The built {@link TAPColumn} object is returned, so that being modified afterwards if needed.
* </i></p>
*
* @param columnName ADQL name (and indirectly also the DB name) of the column to create and add.
* @param datatype Type of the new column's values. <i>If NULL, VARCHAR will be the type of the created column.</i>
* @param description Description of the new column. <i>MAY be NULL</i>
* @param unit Unit of the new column's values. <i>MAY be NULL</i>
* @param ucd UCD describing the scientific content of the new column. <i>MAY be NULL</i>
* @param utype UType associating the new column with a data-model. <i>MAY be NULL</i>
* @param principal <i>true</i> if the new column should be returned by default, <i>false</i> otherwise.
* @param indexed <i>true</i> if the new column is indexed, <i>false</i> otherwise.
* @param std <i>true</i> if the new column is defined by a standard, <i>false</i> otherwise.
*
* @return The created and added {@link TAPColumn} object,
* or NULL if the given name is NULL or an empty string.
*
* @see TAPColumn#TAPColumn(String, DBType, String, String, String, String)
* @see TAPColumn#setPrincipal(boolean)
* @see TAPColumn#setIndexed(boolean)
* @see TAPColumn#setStd(boolean)
* @see #addColumn(TAPColumn)
*/
public TAPColumn addColumn(String columnName, DBType datatype, String description, String unit, String ucd, String utype, boolean principal, boolean indexed, boolean std){
if (columnName == null || columnName.trim().length() <= 0)
return null;
TAPColumn c = new TAPColumn(columnName, datatype, description, unit, ucd, utype);
c.setPrincipal(principal);
c.setIndexed(indexed);
c.setStd(std);
addColumn(c);
return c;
}
/**
* <p>Tell whether this table contains a column with the given ADQL name.</p>
*
* <p><i><b>Important note:</b>
* This function is case sensitive.
* </i></p>
*
* @param columnName ADQL name (case sensitive) of the column whose the existence must be checked.
*
* @return <i>true</i> if a column having the given ADQL name exists in this table, <i>false</i> otherwise.
*/
public final boolean hasColumn(String columnName){
if (columnName == null)
return false;
else
return columns.containsKey(columnName);
}
/**
* Get the list of all columns contained in this table.
*
* @return An iterator over the list of this table's columns.
*/
public Iterator<TAPColumn> getColumns(){
return columns.values().iterator();
}
@Override
public DBColumn getColumn(String colName, boolean byAdqlName){
if (byAdqlName)
return getColumn(colName);
else{
if (colName != null && colName.length() > 0){
Collection<TAPColumn> collColumns = columns.values();
for(TAPColumn column : collColumns){
if (column.getDBName().equals(colName))
return column;
}
}
return null;
}
}
/**
* <p>Search a column inside this table having the given ADQL name.</p>
*
* <p><i><b>Important note:</b>
* This function is case sensitive.
* </i></p>
*
* @param columnName ADQL name of the column to search.
*
* @return The matching column,
* or NULL if no column with this ADQL name has been found.
*/
public final TAPColumn getColumn(String columnName){
if (columnName == null)
return null;
else
return columns.get(columnName);
}
/**
* <p>Tell whether this table contains a column with the given ADQL or DB name.</p>
*
* <p><i>Note:
* This functions is just calling {@link #getColumn(String, boolean)} and compare its result
* with NULL in order to check the existence of the specified column.
* </i></p>
*
* @param colName ADQL or DB name that the column to search must have.
* @param byAdqlName <i>true</i> to search the column by ADQL name, <i>false</i> to search by DB name.
*
* @return <i>true</i> if a column has been found inside this table with the given ADQL or DB name,
* <i>false</i> otherwise.
*
* @see #getColumn(String, boolean)
*/
public boolean hasColumn(String colName, boolean byAdqlName){
return (getColumn(colName, byAdqlName) != null);
}
/**
* Get the number of columns composing this table.
*
* @return Number of its columns.
*/
public final int getNbColumns(){
return columns.size();
}
/**
* Tell whether this table contains no column.
*
* @return <i>true</i> if this table is empty (no column),
* <i>false</i> if it contains at least one column.
*/
public final boolean isEmpty(){
return columns.isEmpty();
}
/**
* <p>Remove the specified column.</p>
*
* <p><i><b>Important note:</b>
* This function is case sensitive!
* </i></p>
*
* <p><i>Note:
* If some foreign keys were associating the column to remove,
* they will be also deleted.
* </i></p>
*
* @param columnName ADQL name of the column to remove.
*
* @return The removed column,
* or NULL if no column with the given ADQL name has been found.
*
* @see #deleteColumnRelations(TAPColumn)
*/
public final TAPColumn removeColumn(String columnName){
if (columnName == null)
return null;
TAPColumn removedColumn = columns.remove(columnName);
if (removedColumn != null)
deleteColumnRelations(removedColumn);
return removedColumn;
}
/**
* Delete all foreign keys having the given column in the sources or the targets list.
*
* @param col A column.
*/
protected final void deleteColumnRelations(TAPColumn col){
// Remove the relation between the column and this table:
col.setTable(null);
// Remove the relations between the column and other tables/columns:
Iterator<TAPForeignKey> it = col.getTargets();
while(it.hasNext())
removeForeignKey(it.next());
it = col.getSources();
while(it.hasNext()){
TAPForeignKey key = it.next();
key.getFromTable().removeForeignKey(key);
}
}
/**
* Remove all columns composing this table.
* Foreign keys will also be deleted.
*/
public final void removeAllColumns(){
Iterator<Map.Entry<String,TAPColumn>> it = columns.entrySet().iterator();
while(it.hasNext()){
Map.Entry<String,TAPColumn> entry = it.next();
it.remove();
deleteColumnRelations(entry.getValue());
}
}
/**
* <p>Add the given foreign key to this table.</p>
*
* <p><i>Note:
* This function will do nothing if the given foreign key is NULL.
* </i></p>
*
* <p><i><b>WARNING:</b>
* The source table ({@link TAPForeignKey#getFromTable()}) of the given foreign key MUST be this table
* and the foreign key MUST be completely defined.
* If not, an exception will be thrown and the key won't be added.
* </i></p>
*
* <p><i>Note:
* If the given foreign key is added to this table, all the columns of this key will be
* linked to the foreign key using either {@link TAPColumn#addSource(TAPForeignKey)} or
* {@link TAPColumn#addTarget(TAPForeignKey)}.
* </i></p>
*
* @param key Foreign key (whose the FROM table is this table) to add inside this table.
*
* @throws TAPException If the source table of the given foreign key is not this table
* or if the given key is not completely defined.
*/
public final void addForeignKey(TAPForeignKey key) throws TAPException{
if (key == null)
return;
String keyId = key.getKeyId();
final String errorMsgPrefix = "Impossible to add the foreign key \"" + keyId + "\" because ";
if (key.getFromTable() == null)
throw new TAPException(errorMsgPrefix + "no source table is specified !");
if (!this.equals(key.getFromTable()))
throw new TAPException(errorMsgPrefix + "the source table is not \"" + getADQLName() + "\"");
if (key.getTargetTable() == null)
throw new TAPException(errorMsgPrefix + "no target table is specified !");
if (key.isEmpty())
throw new TAPException(errorMsgPrefix + "it defines no relation !");
if (foreignKeys.add(key)){
try{
TAPTable targetTable = key.getTargetTable();
for(Map.Entry<String,String> relation : key){
if (!hasColumn(relation.getKey()))
throw new TAPException(errorMsgPrefix + "the source column \"" + relation.getKey() + "\" doesn't exist in \"" + getADQLName() + "\" !");
else if (!targetTable.hasColumn(relation.getValue()))
throw new TAPException(errorMsgPrefix + "the target column \"" + relation.getValue() + "\" doesn't exist in \"" + targetTable.getADQLName() + "\" !");
else{
getColumn(relation.getKey()).addTarget(key);
targetTable.getColumn(relation.getValue()).addSource(key);
}
}
}catch(TAPException ex){
foreignKeys.remove(key);
throw ex;
}
}
}
/**
* <p>Build a foreign key using the ID, the target table and the given list of columns.
* Then, add the created foreign key to this table.</p>
*
* <p><i>Note:
* The source table of the created foreign key ({@link TAPForeignKey#getFromTable()}) will be this table.
* </i></p>
*
* <p><i>Note:
* If the given foreign key is added to this table, all the columns of this key will be
* linked to the foreign key using either {@link TAPColumn#addSource(TAPForeignKey)} or
* {@link TAPColumn#addTarget(TAPForeignKey)}.
* </i></p>
*
* @return The created and added foreign key.
*
* @throws TAPException If the specified key is not completely or correctly defined.
*
* @see TAPForeignKey#TAPForeignKey(String, TAPTable, TAPTable, Map)
*/
public TAPForeignKey addForeignKey(String keyId, TAPTable targetTable, Map<String,String> columns) throws TAPException{
TAPForeignKey key = new TAPForeignKey(keyId, this, targetTable, columns);
addForeignKey(key);
return key;
}
/**
* <p>Build a foreign key using the ID, the target table, the given list of columns, the given description and the given UType.
* Then, add the created foreign key to this table.</p>
*
* <p><i>Note:
* The source table of the created foreign key ({@link TAPForeignKey#getFromTable()}) will be this table.
* </i></p>
*
* <p><i>Note:
* If the given foreign key is added to this table, all the columns of this key will be
* linked to the foreign key using either {@link TAPColumn#addSource(TAPForeignKey)} or
* {@link TAPColumn#addTarget(TAPForeignKey)}.
* </i></p>
*
* @return The created and added foreign key.
*
* @throws TAPException If the specified key is not completely or correctly defined.
*
* @see TAPForeignKey#TAPForeignKey(String, TAPTable, TAPTable, Map, String, String)
*/
public TAPForeignKey addForeignKey(String keyId, TAPTable targetTable, Map<String,String> columns, String description, String utype) throws TAPException{
TAPForeignKey key = new TAPForeignKey(keyId, this, targetTable, columns, description, utype);
addForeignKey(key);
return key;
}
/**
* Get the list of all foreign keys associated whose the source is this table.
*
* @return An iterator over all its foreign keys.
*/
public final Iterator<TAPForeignKey> getForeignKeys(){
return foreignKeys.iterator();
}
/**
* Get the number of all foreign keys whose the source is this table
*
* @return Number of all its foreign keys.
*/
public final int getNbForeignKeys(){
return foreignKeys.size();
}
/**
* <p>Remove the given foreign key from this table.</p>
*
* <p><i>Note:
* This function will also delete the link between the columns of the foreign key
* and the foreign key, using {@link #deleteRelations(TAPForeignKey)}.
* </i></p>
*
* @param keyToRemove Foreign key to removed from this table.
*
* @return <i>true</i> if the key has been successfully removed,
* <i>false</i> otherwise.
*/
public final boolean removeForeignKey(TAPForeignKey keyToRemove){
if (foreignKeys.remove(keyToRemove)){
deleteRelations(keyToRemove);
return true;
}else
return false;
}
/**
* <p>Remove all the foreign keys whose the source is this table.</p>
*
* <p><i>Note:
* This function will also delete the link between the columns of all the removed foreign keys
* and the foreign keys, using {@link #deleteRelations(TAPForeignKey)}.
* </i></p>
*/
public final void removeAllForeignKeys(){
Iterator<TAPForeignKey> it = foreignKeys.iterator();
while(it.hasNext()){
deleteRelations(it.next());
it.remove();
}
}
/**
* Delete the link between all columns of the given foreign key
* and this foreign key. Thus, these columns won't be anymore source or target
* of this foreign key.
*
* @param key A foreign key whose links with its columns must be deleted.
*/
protected final void deleteRelations(TAPForeignKey key){
for(Map.Entry<String,String> relation : key){
TAPColumn col = key.getFromTable().getColumn(relation.getKey());
if (col != null)
col.removeTarget(key);
col = key.getTargetTable().getColumn(relation.getValue());
if (col != null)
col.removeSource(key);
}
}
@Override
public Iterator<DBColumn> iterator(){
return new Iterator<DBColumn>(){
private final Iterator<TAPColumn> it = getColumns();
@Override
public boolean hasNext(){
return it.hasNext();
}
@Override
public DBColumn next(){
return it.next();
}
@Override
public void remove(){
it.remove();
}
};
}
@Override
public String toString(){
return ((schema != null) ? (schema.getADQLName() + ".") : "") + getADQLName();
}
@Override
public DBTable copy(final String dbName, final String adqlName){
TAPTable copy = new TAPTable((adqlName == null) ? this.adqlName : adqlName);
copy.setDBName((dbName == null) ? this.getDBName() : dbName);
copy.setSchema(schema);
Collection<TAPColumn> collColumns = columns.values();
for(TAPColumn col : collColumns)
copy.addColumn((TAPColumn)col.copy(col.getDBName(), col.getADQLName(), copy));
copy.setDescription(description);
copy.setOtherData(otherData);
copy.setType(type);
copy.setUtype(utype);
return copy;
}
}