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; } }