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.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import tap.metadata.TAPTable.TableType;
/**
* <p>Represent a schema 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.schemas.
* 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>Note:
* On the contrary to {@link TAPColumn} and {@link TAPTable}, a {@link TAPSchema} object MAY have no DB name.
* But by default, at the creation the DB name is the simplified ADQL name (i.e. as it is returned by {@link #getADQLName()}).
* Once created, it is possible to set the DB name with {@link #setDBName(String)}. This DB name MAY be qualified,
* BUT MUST BE without double quotes.
* </i></p>
*
* @author Grégory Mantelet (CDS;ARI)
* @version 2.1 (07/2016)
*/
public class TAPSchema implements Iterable<TAPTable> {
/** Name that this schema 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.
* Since there is no information on the catalog name, none can be detected and so removed.</p>
* @since 2.1 */
private final boolean simplificationNeeded;
/** Name that this schema have in the database.
* <i>Note: NULL by default. When NULL, {@link #getDBName()} returns exactly what {@link #getADQLName()} returns.</i> */
private String dbName = null;
/** Descriptive, human-interpretable name of the schema.
* <i>Note: Standard TAP schema field ; MAY be NULL.</i>
* @since 2.0 */
private String title = null;
/** Description of this schema.
* <i>Note: Standard TAP schema field ; MAY be NULL.</i> */
private String description = null;
/** UType describing the scientific content of this schema.
* <i>Note: Standard TAP schema field ; MAY be NULL.</i> */
private String utype = null;
/** Ordering index of this schema inside its whole schema set.
* <i>Note: SHOULD be a standard TAP schema field in TAP 1.1, as table_index and column_index are resp. in TAP_SCHEMA.tables and TAP_SCHEMA.columns.</i>
* @since 2.1 */
private int index = -1;
/** 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;
/** List all tables contained inside this schema. */
protected final Map<String,TAPTable> tables;
/**
* <p>Build a {@link TAPSchema} instance with the given ADQL name.</p>
*
* <p><i>Note:
* The DB name is set by default to the ADQL name (as returned by {@link #getADQLName()}).
* To set the DB name, you MUST call then {@link #setDBName(String)}.
* </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>
* No catalog prefix is supported. <i>For instance: <code>myCatalog.mySchema</code> will be considered
* as the schema name instead of <code>mySchema</code>.</i>
* </li>
* <li>
* Double quotes may surround the ADQL name. They will be removed by {@link #getADQLName()} but will
* still appear in the result of {@link #getRawName()}.
* </li>
* </ul>
*
* @param schemaName Name that this schema 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 TAPSchema(String schemaName) throws NullPointerException{
if (schemaName == null)
throw new NullPointerException("Missing schema name!");
adqlName = schemaName.trim();
simplificationNeeded = adqlName.matches("\"[^\"]*\"");
if (getADQLName().length() == 0)
throw new NullPointerException("Missing schema name!");
dbName = getADQLName();
tables = new LinkedHashMap<String,TAPTable>();
}
/**
* <p>Build a {@link TAPSchema} instance with the given ADQL name and description.</p>
*
* <p><i>Note:
* The DB name is set by default to the ADQL name (as returned by {@link #getADQLName()}).
* To set the DB name, you MUST call then {@link #setDBName(String)}.
* </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>
* No catalog prefix is supported. <i>For instance: <code>myCatalog.mySchema</code> will be considered
* as the schema name instead of <code>mySchema</code>.</i>
* </li>
* <li>
* Double quotes may surround the ADQL name. They will be removed by {@link #getADQLName()} but will
* still appear in the result of {@link #getRawName()}.
* </li>
* </ul>
*
* @param schemaName Name that this schema MUST have in ADQL queries.
* <i>CAN'T be NULL ; this name can never be changed after initialization.</i>
* @param description Description of this schema. <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).
*/
public TAPSchema(String schemaName, String description) throws NullPointerException{
this(schemaName, description, null);
}
/**
* <p>Build a {@link TAPSchema} instance with the given ADQL name, description and UType.</p>
*
* <p><i>Note:
* The DB name is set by default to the ADQL name (as returned by {@link #getADQLName()}).
* To set the DB name, you MUST call then {@link #setDBName(String)}.
* </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>
* No catalog prefix is supported. <i>For instance: <code>myCatalog.mySchema</code> will be considered
* as the schema name instead of <code>mySchema</code>.</i>
* </li>
* <li>
* Double quotes may surround the ADQL name. They will be removed by {@link #getADQLName()} but will
* still appear in the result of {@link #getRawName()}.
* </li>
* </ul>
*
* @param schemaName Name that this schema MUST have in ADQL queries.
* <i>CAN'T be NULL ; this name can never be changed after initialization.</i>
* @param description Description of this schema. <i>MAY be NULL</i>
* @param utype UType associating this schema 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).
*/
public TAPSchema(String schemaName, String description, String utype) throws NullPointerException{
this(schemaName);
this.description = description;
this.utype = utype;
}
/**
* Get the ADQL name (the name this schema 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();
}
/**
* Get the name of this schema.
*
* <p><i>Note:
* If a simplification is needed, the double quotes surrounding the ADQL name, will be removed.
* </i></p>
*
* @return Its ADQL name. <i>CAN'T be NULL</i>
*/
public final String getADQLName(){
return simplificationNeeded ? adqlName.substring(1, adqlName.length() - 1) : adqlName;
}
/**
* Get the full ADQL name of this schema, as it has been provided at initialization.
*
* @return Get the original ADQL name.
*
* @since 2.1
*/
public final String getRawName(){
return adqlName;
}
/**
* Get the name this schema MUST have in the database.
*
* @return Its DB name. <i>MAY be NULL</i>
*/
public final String getDBName(){
return dbName;
}
/**
* Set the name this schema MUST have in the database.
*
* <p>Notes:</p>
* <ul>
* <li>The given name may be NULL. In such case {@link #getDBName()} will then return NULL.</li>
* <li>It may be prefixed by a catalog name.</li>
* <li>
* It MUST be NON delimited/double-quoted. Otherwise an SQL error will be raised when
* querying any item of this schema because the library double-quotes systematically the
* DB name of schemas, tables and columns.
* </li>
* </ul>
*
* @param name Its new DB name. <i>MAY be NULL</i>
*/
public final void setDBName(String name){
name = (name != null) ? name.trim() : name;
dbName = name;
}
/**
* Get the title of this schema.
*
* @return Its title. <i>MAY be NULL</i>
*
* @since 2.0
*/
public final String getTitle(){
return title;
}
/**
* Set the title of this schema.
*
* @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 schema.
*
* @return Its description. <i>MAY be NULL</i>
*/
public final String getDescription(){
return description;
}
/**
* Set the description of this schema.
*
* @param description Its new description. <i>MAY be NULL</i>
*/
public final void setDescription(String description){
this.description = description;
}
/**
* Get the UType associating this schema with a data-model.
*
* @return Its UType. <i>MAY be NULL</i>
*/
public final String getUtype(){
return utype;
}
/**
* Set the UType associating this schema 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 schema inside its whole schema set.
*
* @return Its ordering index.
*
* @since 2.1
*/
public final int getIndex(){
return index;
}
/**
* Set the ordering index of this schema inside its whole schema set.
*
* @param schemaIndex Its new ordering index.
*
* @since 2.1
*/
public final void setIndex(int schemaIndex){
this.index = schemaIndex;
}
/**
* <p>Get the other (piece of) information associated with this schema.</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 schema.
*
* @param data Another information about this schema. <i>MAY be NULL</i>
*/
public void setOtherData(Object data){
otherData = data;
}
/**
* <p>Add the given table inside this schema.</p>
*
* <p><i>Note:
* If the given table is NULL, nothing will be done.
* </i></p>
*
* <p><i><b>Important note:</b>
* By adding the given table inside this schema, it
* will be linked with this schema using {@link TAPTable#setSchema(TAPSchema)}.
* In this function, if the table was already linked with another {@link TAPSchema},
* the former link is removed using {@link TAPSchema#removeTable(String)}.
* </i></p>
*
* @param newTable Table to add inside this schema.
*/
public final void addTable(TAPTable newTable){
if (newTable != null && newTable.getADQLName() != null){
newTable.setSchema(this);
tables.put(newTable.getADQLName(), newTable);
}
}
/**
* <p>Build a {@link TAPTable} object whose the ADQL and DB name will the given one.
* Then, add this table inside this schema.</p>
*
* <p><i>Note:
* The built {@link TAPTable} object is returned, so that being modified afterwards if needed.
* </i></p>
*
* @param tableName ADQL name (and indirectly also the DB name) of the table to create and add.
*
* @return The created and added {@link TAPTable} object,
* or NULL if the given name is NULL or an empty string.
*
* @see TAPTable#TAPTable(String)
* @see #addTable(TAPTable)
*/
public TAPTable addTable(String tableName){
if (tableName == null)
return null;
TAPTable t = new TAPTable(tableName);
addTable(t);
return t;
}
/**
* <p>Build a {@link TAPTable} object whose the ADQL and DB name will the given one.
* Then, add this table inside this schema.</p>
*
* <p><i>Note:
* The built {@link TAPTable} object is returned, so that being modified afterwards if needed.
* </i></p>
*
* @param tableName ADQL name (and indirectly also the DB name) of the table to create and add.
* @param tableType Type of the new table. <i>If NULL, "table" will be the type of the created table.</i>
* @param description Description of the new table. <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 TAPTable} object,
* or NULL if the given name is NULL or an empty string.
*
* @see TAPTable#TAPTable(String, TableType, String, String)
* @see #addTable(TAPTable)
*/
public TAPTable addTable(String tableName, TableType tableType, String description, String utype){
if (tableName == null)
return null;
TAPTable t = new TAPTable(tableName, tableType, description, utype);
addTable(t);
return t;
}
/**
* <p>Tell whether this schema contains a table having the given ADQL name.</p>
*
* <p><i><b>Important note:</b>
* This function is case sensitive!
* </i></p>
*
* @param tableName Name of the table whose the existence in this schema must be checked.
*
* @return <i>true</i> if a table with the given ADQL name exists, <i>false</i> otherwise.
*/
public final boolean hasTable(String tableName){
if (tableName == null)
return false;
else
return tables.containsKey(tableName);
}
/**
* <p>Search for a table having the given ADQL name.</p>
*
* <p><i><b>Important note:</b>
* This function is case sensitive!
* </i></p>
*
* @param tableName ADQL name of the table to search.
*
* @return The table having the given ADQL name,
* or NULL if no such table can be found.
*/
public final TAPTable getTable(String tableName){
if (tableName == null)
return null;
else
return tables.get(tableName);
}
/**
* Get the number of all tables contained inside this schema.
*
* @return Number of its tables.
*/
public final int getNbTables(){
return tables.size();
}
/**
* Tell whether this schema contains no table.
*
* @return <i>true</i> if this schema contains no table,
* <i>false</i> if it has at least one table.
*/
public final boolean isEmpty(){
return tables.isEmpty();
}
/**
* <p>Remove the table having the given ADQL name.</p>
*
* <p><i><b>Important note:</b>
* This function is case sensitive!
* </i></p>
*
* <p><i>Note:
* If the specified table is removed, its schema link is also deleted.
* </i></p>
*
* <p><i><b>WARNING:</b>
* If the goal of this function's call is to delete definitely the specified table
* from the metadata, you SHOULD also call {@link TAPTable#removeAllForeignKeys()}.
* Indeed, foreign keys of the table would still link the removed table with other tables
* AND columns of the whole metadata set.
* </i></p>
*
* @param tableName ADQL name of the table to remove from this schema.
*
* @return The removed table,
* or NULL if no table with the given ADQL name can be found.
*/
public final TAPTable removeTable(String tableName){
if (tableName == null)
return null;
TAPTable removedTable = tables.remove(tableName);
if (removedTable != null)
removedTable.setSchema(null);
return removedTable;
}
/**
* <p>Remove all the tables contained inside this schema.</p>
*
* <p><i>Note:
* When a table is removed, its schema link is also deleted.
* </i></p>
*
* <p><b>CAUTION:
* If the goal of this function's call is to delete definitely all the tables of this schema
* from the metadata, you SHOULD also call {@link TAPTable#removeAllForeignKeys()}
* on all tables before calling this function.
* Indeed, foreign keys of the tables would still link the removed tables with other tables
* AND columns of the whole metadata set.
* </b></p>
*/
public final void removeAllTables(){
Iterator<Map.Entry<String,TAPTable>> it = tables.entrySet().iterator();
while(it.hasNext()){
Map.Entry<String,TAPTable> entry = it.next();
it.remove();
entry.getValue().setSchema(null);
}
}
@Override
public Iterator<TAPTable> iterator(){
return tables.values().iterator();
}
@Override
public String toString(){
return getADQLName();
}
}