META-INF/ 0000755 0001750 0001750 00000000000 13245022474 010707 5 ustar oles oles META-INF/MANIFEST.MF 0000644 0001750 0001750 00000000150 13245022472 012333 0 ustar oles oles Manifest-Version: 1.0
Ant-Version: Apache Ant 1.9.6
Created-By: 1.7.0_151-b01 (Oracle Corporation)
src/ 0000755 0001750 0001750 00000000000 13244760406 010341 5 ustar oles oles src/COPYING.LESSER 0000644 0001750 0001750 00000016743 13177122310 012372 0 ustar oles oles GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
A TextualSearchList is an {@link ArrayList} with a textual search capability.
** The interest of this class lies in the fact that objects can be searched with * or without case sensitivity on their textual key thanks to {@link #get(String, boolean)}. *
** The textual key is extracted by an object implementing the {@link KeyExtractor} instance. * If no {@link KeyExtractor} instance is given at initialization, the string returned * by the {@link Object#toString() toString()} function will be used as key. *
*WARNING: The extracted key MUST be CASE-SENSITIVE and UNIQUE !
* * @paramBuilds an empty TextualSearchList.
* *Note: * the key of inserted objects will be the string returned by their {@link Object#toString() toString()} function. *
* * @see #TextualSearchList(KeyExtractor) */ public TextualSearchList(){ this(new DefaultKeyExtractorBuilds an empty TextualSearchList with an initial capacity.
* *Note: * the key of inserted objects will be the string returned by their {@link Object#toString() toString()} function. *
* * @param initialCapacity Initial capacity of this list. * * @see #TextualSearchList(int, KeyExtractor) */ public TextualSearchList(int initialCapacity){ this(initialCapacity, new DefaultKeyExtractorBuilds a TextualSearchList filled with the objects of the given collection.
* *Note: * the key of inserted objects will be the string returned by their {@link Object#toString() toString()} function. *
* * @param c Collection to copy into this list. */ public TextualSearchList(Collection extends E> c){ this(c, new DefaultKeyExtractornull
.
*/
public final Listnull
.
* @throws IllegalArgumentException If the extracted key is already used by another object in this list.
*/
private final String getKey(final E value) throws NullPointerException, IllegalArgumentException{
String key = keyExtractor.getKey(value);
if (key == null)
throw new NullPointerException("Null keys are not allowed in a TextualSearchList !");
return key;
}
/**
* Adds the given object in the two maps with the given key.
*
* @param key The key with which the given object must be associated.
* @param value The object to add.
*/
private final void putIntoMaps(final String key, final E value){
// update the case-sensitive map:
putIntoMap(csMap, key, value);
// update the case-INsensitive map:
putIntoMap(ncsMap, key.toLowerCase(), value);
}
/**
* Adds the given object in the given map with the given key.
*
* @param map The map in which the given value must be added.
* @param key The key with which the given object must be associated.
* @param value The object to add.
*
* @param null
.
* @throws IllegalArgumentException If the extracted key is already used by another object in this list.
*
* @see java.util.ArrayList#add(java.lang.Object)
*/
@Override
public boolean add(E obj) throws NullPointerException, IllegalArgumentException{
if (obj == null)
throw new NullPointerException("Null objects are not allowed in a TextualSearchList !");
String key = getKey(obj);
if (key == null)
return false;
if (super.add(obj)){
putIntoMaps(key, obj);
return true;
}else
return false;
}
/**
* Adds the given object at the given position in this list.
*
* @param index Index at which the given object must be added.
* @param obj Object to add (different from NULL).
*
* @throws NullPointerException If the given object or its extracted key is null
.
* @throws IllegalArgumentException If the extracted key is already used by another object in this list.
* @throws IndexOutOfBoundsException If the given index is negative or greater than the size of this list.
*
* @see java.util.ArrayList#add(int, java.lang.Object)
*/
@Override
public void add(int index, E obj) throws NullPointerException, IllegalArgumentException, IndexOutOfBoundsException{
if (obj == null)
throw new NullPointerException("Null objects are not allowed in a TextualSearchList !");
String key = getKey(obj);
if (key == null)
return;
super.add(index, obj);
if (get(index).equals(obj))
putIntoMaps(key, obj);
}
/**
* Appends all the objects of the given collection in this list.
*
* @param c Collection of objects to add.
*
* @return true
if this list changed as a result of the call, false
otherwise.
*
* @throws NullPointerException If an object to add or its extracted key is null
.
* @throws IllegalArgumentException If the extracted key is already used by another object in this list.
*
* @see java.util.ArrayList#addAll(java.util.Collection)
* @see #add(Object)
*/
@Override
public boolean addAll(Collection extends E> c) throws NullPointerException, IllegalArgumentException{
if (c == null)
return false;
boolean modified = false;
for(E obj : c)
modified = add(obj) || modified;
return modified;
}
/**
* Appends all the objects of the given collection in this list after the given position.
*
* @param index Position from which objects of the given collection must be added.
* @param c Collection of objects to add.
*
* @return true
if this list changed as a result of the call, false
otherwise.
*
* @throws NullPointerException If an object to add or its extracted key is null
.
* @throws IllegalArgumentException If the extracted key is already used by another object in this list.
* @throws IndexOutOfBoundsException If the given index is negative or greater than the size of this list.
*
* @see java.util.ArrayList#addAll(int, java.util.Collection)
* @see #add(int, Object)
*/
@Override
public boolean addAll(int index, Collection extends E> c) throws NullPointerException, IllegalArgumentException, IndexOutOfBoundsException{
if (c == null)
return false;
boolean modified = false;
int ind = index;
for(E obj : c){
add(ind++, obj);
modified = get(ind).equals(obj);
}
return modified;
}
/**
* Replaces the element at the specified position in this list with the specified element.
*
* @param index Position of the object to replace.
* @param obj Object to be stored at the given position (different from NULL).
*
* @return Replaced object.
*
* @throws NullPointerException If the object to add or its extracted key is null
.
* @throws IllegalArgumentException If the extracted key is already used by another object in this list.
* @throws IndexOutOfBoundsException If the given index is negative or greater than the size of this list.
*
* @see java.util.ArrayList#set(int, java.lang.Object)
*/
@Override
public E set(int index, E obj) throws NullPointerException, IllegalArgumentException{
if (obj == null)
throw new NullPointerException("Null objects are not allowed in a TextualSearchList !");
if (get(index).equals(obj))
return obj;
String key = getKey(obj);
E old = super.set(index, obj);
// Removes the old object from the index:
String oldKey = keyExtractor.getKey(old);
removeFromMaps(oldKey, old);
// Adds the new object into the index:
putIntoMaps(key, obj);
return old;
}
@Override
public void clear(){
super.clear();
csMap.clear();
ncsMap.clear();
}
/**
* Removes the given object associated with the given key from the two maps.
*
* @param key The key associated with the given object.
* @param value The object to remove.
*/
private final void removeFromMaps(final String key, final E value){
// update the case-sensitive map:
removeFromMap(csMap, key, value);
// update the case-insensitive map:
removeFromMap(ncsMap, key.toLowerCase(), value);
}
/**
* Removes the given object associated with the given key from the given map.
*
* @param map The map from which the given object must be removed.
* @param key The key associated with the given object.
* @param value The object to remove.
*
* @param UNKNOWN COLUMN
* *Builds the exception with an {@link ADQLColumn} which does not exist.
* * @param c The unresolved {@link ADQLColumn}. */ public UnresolvedColumnException(ADQLColumn c){ super(buildMessage("Unknown column", c)); initPosition(c); columnName = (c != null) ? c.getColumnName() : null; } /** *AMBIGUOUS COLUMN NAME
* ** Builds the exception with an {@link ADQLColumn} which does not have a table reference AND which may come from more than one table * OR with an {@link ADQLColumn} which may reference more than one column in the table. *
* * @param c The ambiguous {@link ADQLColumn}. * @param col1 First possibility. * @param col2 A second possibility. */ public UnresolvedColumnException(ADQLColumn c, String col1, String col2){ super(buildMessage("Ambiguous column name", c, col1, col2)); initPosition(c); columnName = (c != null) ? c.getColumnName() : null; } protected final void initPosition(final ADQLColumn c){ position = c.getPosition(); } public final String getColumnName(){ return columnName; } private static final String buildMessage(String msgStart, ADQLColumn c){ StringBuffer msg = new StringBuffer(); msg.append(msgStart).append(" \"").append(c.getFullColumnName()).append("\" !"); return msg.toString(); } private static final String buildMessage(String msgStart, ADQLColumn c, String col1, String col2){ if (col1 != null && col2 != null){ StringBuffer msg = new StringBuffer(buildMessage(msgStart, c)); msg.append(" It may be (at least) \"").append(col1).append("\" or \"").append(col2).append("\"."); return msg.toString(); }else return buildMessage(msgStart, c); } } src/adql/db/exception/UnresolvedJoinException.java 0000644 0001750 0001750 00000004164 13177122310 021331 0 ustar oles oles package adql.db.exception; /* * This file is part of ADQLLibrary. * * ADQLLibrary 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. * * ADQLLibrary 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 ADQLLibrary. If not, see* This exception is thrown by {@link DBChecker} when several columns or tables do not exist. * It lists several {@link ParseException} (either {@link UnresolvedColumnException} or {@link UnresolvedTableException}). *
** Its message only tells the number of unresolved identifiers. * If you want to have more details about the position and the exact message of each exception, you just have to iterate * on this {@link UnresolvedIdentifiersException} (method {@link #iterator()}). *
* * @author Grégory Mantelet (CDS;ARI) * @version 1.4 (06/2015) * * @see DBChecker */ public class UnresolvedIdentifiersException extends ParseException implements IterableGet the signature of the function given in parameter.
* ** In this signature, just the name and the type of all the parameters are written. * The return type is never part of a function signature. *
* *Note 1: * A parameter type can be either "NUMERIC", "STRING" or "GEOMETRY". In order to be the most generic has possible, * no more precision about a type is returned here. If the parameter is none of these type kinds, "param" suffixed * by the parameter index (e.g. "param1") is returned. *
* *Note 2: * If the given object is NULL, an empty string is returned. *
* * @param fct Function whose the signature must be returned. * * @return The corresponding signature. */ public static String getFctSignature(final ADQLFunction fct){ if (fct == null) return ""; StringBuffer buf = new StringBuffer(fct.getName().toLowerCase()); buf.append('('); for(int i = 0; i < fct.getNbParameters(); i++){ if (fct.getParameter(i).isNumeric() && fct.getParameter(i).isString() && fct.getParameter(i).isGeometry()) buf.append("param").append(i + 1); else if (fct.getParameter(i).isNumeric()) buf.append("NUMERIC"); else if (fct.getParameter(i).isString()) buf.append("STRING"); else if (fct.getParameter(i).isGeometry()) buf.append("GEOMETRY"); else buf.append("param").append(i + 1); if ((i + 1) < fct.getNbParameters()) buf.append(", "); } buf.append(')'); return buf.toString(); } } src/adql/db/exception/UnresolvedTableException.java 0000644 0001750 0001750 00000012557 13177122310 021466 0 ustar oles oles package adql.db.exception; /* * This file is part of ADQLLibrary. * * ADQLLibrary 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. * * ADQLLibrary 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 ADQLLibrary. If not, seeUNKNOWN TABLE
* *Builds the exception with an {@link ADQLTable} which does not exist.
* * @param table The unresolved {@link ADQLTable}. */ public UnresolvedTableException(ADQLTable table){ super(buildMessage("Unknown table", table)); initPosition(table); tableName = (table != null) ? table.getTableName() : null; } /** *AMBIGUOUS TABLE NAME
* ** Builds the exception with an {@link ADQLTable} which does not have a schema reference AND which may come from more than one schema. * The two given schema names are schemas which contain a table with the same name as the given one. *
* * @param table The ambiguous {@link ADQLTable} (no schema reference). * @param t1 First possibility. * @param t2 A second possibility. */ public UnresolvedTableException(ADQLTable table, String t1, String t2){ super(buildMessage("Ambiguous table name", table, t1, t2)); initPosition(table); tableName = (table != null) ? table.getTableName() : null; } /** * Initializes the position at which this exception occurs. * * @param table The unresolved table. */ protected final void initPosition(ADQLTable table){ position = table.getPosition(); } /** *UNKNOWN TABLE REFERENCE
* *Builds the exception with an {@link ADQLColumn} whose the table reference is unknown.
* * @param column The {@link ADQLColumn} whose the table reference is unresolved. */ public UnresolvedTableException(ADQLColumn column){ super(buildMessage("Unknown table reference", column)); initPosition(column); tableName = (column != null) ? column.getTableName() : null; } /** *AMBIGUOUS TABLE REFERENCE
* ** Builds the exception with an {@link ADQLColumn} which has an ambiguous table reference. * The two given table correspond to tables which match with the table reference of the given {@link ADQLColumn}. *
* * @param column The {@link ADQLColumn} whose the table reference is ambiguous. * @param table1 A table whose the name match with the table reference of the column. * @param table2 Another table whose the name match with the table reference of the column. */ public UnresolvedTableException(ADQLColumn column, String table1, String table2){ super(buildMessage("Ambiguous table reference", column, table1, table2)); initPosition(column); tableName = (column != null) ? column.getTableName() : null; } protected final void initPosition(ADQLColumn column){ position = column.getPosition(); } private static final String buildMessage(String msgStart, ADQLTable t){ StringBuffer msg = new StringBuffer(); msg.append(msgStart).append(" \""); if (t.isSubQuery()) msg.append(t.getAlias()).append("\" !"); else msg.append(t.getFullTableName()).append("\"").append(t.hasAlias() ? (" (alias " + t.getAlias() + ")") : "").append(" !"); return msg.toString(); } private static final String buildMessage(String msgStart, ADQLTable t, String t1, String t2){ if (t1 != null && t2 != null){ StringBuffer msg = new StringBuffer(buildMessage(msgStart, t)); msg.append(" It may be (at least) \"").append(t1).append("\" or \"").append(t2).append("\"."); return msg.toString(); }else return buildMessage(msgStart, t); } private static final String buildMessage(String msgStart, ADQLColumn c){ StringBuffer msg = new StringBuffer(); msg.append(msgStart); msg.append(" \"").append(c.getFullColumnPrefix()).append("\" in \"").append(c.getFullColumnName()).append("\" !"); return msg.toString(); } private static final String buildMessage(String msgStart, ADQLColumn c, String table1, String table2){ if (table1 != null && table2 != null){ StringBuffer msg = new StringBuffer(buildMessage(msgStart, c)); msg.append(" It may come (at least) from \"").append(table1).append("\" or from \"").append(table2).append("\"."); return msg.toString(); }else return buildMessage(msgStart, c); } public final String getTableName(){ return tableName; } } src/adql/db/STCS.java 0000644 0001750 0001750 00000174514 13177122310 013271 0 ustar oles oles package adql.db; /* * This file is part of ADQLLibrary. * * ADQLLibrary 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. * * ADQLLibrary 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 ADQLLibrary. If not, seeThis class helps dealing with the subset of STC-S expressions described by the section "6 Use of STC-S in TAP (informative)" * of the TAP Recommendation 1.0 (27th March 2010). This subset is limited to the most common coordinate systems and regions.
* *Note: * No instance of this class can be created. Its usage is only limited to its static functions and classes. *
* ** The function {@link #parseCoordSys(String)} is able to parse a string containing only the STC-S expression of a coordinate system * (or an empty string or null which would be interpreted as the default coordinate system - UNKNOWNFRAME UNKNOWNREFPOS SPHERICAL2). * When successful, this parsing returns an object representation of the coordinate system: {@link CoordSys}. *
** To serialize into STC-S a coordinate system, you have to create a {@link CoordSys} instance with the desired values * and to call the function {@link CoordSys#toSTCS()}. The static function {@link #toSTCS(CoordSys)} is just calling the * {@link CoordSys#toSTCS()} on the given coordinate system. *
* ** As for the coordinate system, there is a static function to parse the STC-S representation of a geometrical region: {@link #parseRegion(String)}. * Here again, when the parsing is successful an object representation is returned: {@link Region}. *
** This class lets also serializing into STC-S a region. The procedure is the same as with a coordinate system: create a {@link Region} and then * call {@link Region#toString()}. *
** The class {@link Region} lets also dealing with the {@link ADQLFunction} implementing a region. It is then possible to create a {@link Region} * object from a such {@link ADQLFunction} and to get the corresponding STC-S representation. The static function {@link #toSTCS(GeometryFunction)} * is a helpful function which do these both actions in once. *
*Note: * The conversion from {@link ADQLFunction} to {@link Region} or STC-S is possible only if the {@link ADQLFunction} contains constants as parameter. * Thus, a such function using a column, a concatenation, a math operation or using another function can not be converted into STC-S using this class. *
* * @author Grégory Mantelet (ARI) * @version 1.4 (04/2017) * @since 1.3 */ public final class STCS { /** * Empty private constructor ; in order to prevent any instance creation. */ private STCS(){} /* ***************** */ /* COORDINATE SYSTEM */ /* ***************** */ /** Regular expression for a STC-S representation of a coordinate system. It takes into account the fact that each part of * a coordinate system is optional and so that a full coordinate system expression can be reduced to an empty string. */ private final static String coordSysRegExp = Frame.regexp + "?\\s*" + RefPos.regexp + "?\\s*" + Flavor.regexp + "?"; /** Regular expression of an expression exclusively limited to a coordinate system. */ private final static String onlyCoordSysRegExp = "^\\s*" + coordSysRegExp + "\\s*$"; /** Regular expression of a default coordinate system: either an empty string or a string containing only default values. */ private final static String defaultCoordSysRegExp = "^\\s*" + Frame.DEFAULT + "?\\s*" + RefPos.DEFAULT + "?\\s*" + Flavor.DEFAULT + "?\\s*$"; /** Regular expression of a pattern describing a set of allowed coordinate systems. See {@link #buildAllowedRegExp(String)} for more details. */ /* With this regular expression, we get the following matching groups: * 0: All the expression * 1+(6*N): The N-th part of the coordinate system (N is an unsigned integer between 0 and 2 (included) ; it is reduced to '*' if the two following groups are NULL * 2+(6*N): A single value for the N-th part * 3+(6*N): A list of values for the N-th part * 4+(6*N): First value of the list for the N-th part * 5+(6*N): All the other values (starting with a |) of the list for the N-th part * 6+(6*N): Last value of the list for the N-th part. */ private final static String allowedCoordSysRegExp = "^\\s*" + buildAllowedRegExp(Frame.regexp) + "\\s+" + buildAllowedRegExp(RefPos.regexp) + "\\s+" + buildAllowedRegExp(Flavor.regexp) + "\\s*$"; /** Pattern of an allowed coordinate system pattern. This object has been compiled with {@link #allowedCoordSysRegExp}. */ private final static Pattern allowedCoordSysPattern = Pattern.compile(allowedCoordSysRegExp); /** Human description of the syntax of a full coordinate system expression. */ private final static String COORD_SYS_SYNTAX = "\"[" + Frame.regexp + "] [" + RefPos.regexp + "] [" + Flavor.regexp + "]\" ; an empty string is also allowed and will be interpreted as the coordinate system locally used"; /** * Build the regular expression of a string defining the allowed values for one part of the whole coordinate system. * * @param rootRegExp All allowed part values. * * @return The corresponding regular expression. */ private static String buildAllowedRegExp(final String rootRegExp){ return "(" + rootRegExp + "|\\*|(\\(\\s*" + rootRegExp + "\\s*(\\|\\s*" + rootRegExp + "\\s*)*\\)))"; } /** *List of all possible frames in an STC expression.
* ** When no value is specified, the default one is {@link #UNKNOWNFRAME}. * The default value is also accessible through the attribute {@link #DEFAULT} * and it is possible to test whether a frame is the default with the function {@link #isDefault()}. *
* *Note: * The possible values listed in this enumeration are limited to the subset of STC-S described by the section "6 Use of STC-S in TAP (informative)" * of the TAP Recommendation 1.0 (27th March 2010). *
* * @author Grégory Mantelet (ARI) * @version 1.4 (04/2017) * @since 1.3 */ public static enum Frame{ ECLIPTIC, FK4, FK5, J2000, GALACTIC, ICRS, UNKNOWNFRAME; /** Default value for a frame: {@link #UNKNOWNFRAME}. */ public static final Frame DEFAULT = UNKNOWNFRAME; /** Regular expression to test whether a string is a valid frame or not. This regular expression does not take into account * the case of an empty string (which means "default frame"). */ public static final String regexp = buildRegexp(Frame.class); /** * Tell whether this frame is the default one. * * @return true if this is the default frame, false */ public final boolean isDefault(){ return this == DEFAULT; } } /** *List of all possible reference positions in an STC expression.
* ** When no value is specified, the default one is {@link #UNKNOWNREFPOS}. * The default value is also accessible through the attribute {@link #DEFAULT} * and it is possible to test whether a reference position is the default with the function {@link #isDefault()}. *
* *Note: * The possible values listed in this enumeration are limited to the subset of STC-S described by the section "6 Use of STC-S in TAP (informative)" * of the TAP Recommendation 1.0 (27th March 2010). *
* * @author Grégory Mantelet (ARI) * @version 1.3 (10/2014) * @since 1.3 */ public static enum RefPos{ BARYCENTER, GEOCENTER, HELIOCENTER, LSR, TOPOCENTER, RELOCATABLE, UNKNOWNREFPOS; /** Default value for a reference position: {@link #UNKNOWNREFPOS}. */ public static final RefPos DEFAULT = UNKNOWNREFPOS; /** Regular expression to test whether a string is a valid reference position or not. This regular expression does not take into account * the case of an empty string (which means "default reference position"). */ public static final String regexp = buildRegexp(RefPos.class); /** * Tell whether this reference position is the default one. * * @return true if this is the default reference position, false */ public final boolean isDefault(){ return this == DEFAULT; } } /** *List of all possible flavors in an STC expression.
* ** When no value is specified, the default one is {@link #SPHERICAL2}. * The default value is also accessible through the attribute {@link #DEFAULT} * and it is possible to test whether a flavor is the default with the function {@link #isDefault()}. *
* *Note: * The possible values listed in this enumeration are limited to the subset of STC-S described by the section "6 Use of STC-S in TAP (informative)" * of the TAP Recommendation 1.0 (27th March 2010). *
* * @author Grégory Mantelet (ARI) * @version 1.3 (10/2014) * @since 1.3 */ public static enum Flavor{ CARTESIAN2, CARTESIAN3, SPHERICAL2; /** Default value for a flavor: {@link #SPHERICAL2}. */ public static final Flavor DEFAULT = SPHERICAL2; /** Regular expression to test whether a string is a valid flavor or not. This regular expression does not take into account * the case of an empty string (which means "default flavor"). */ public static final String regexp = buildRegexp(Flavor.class); /** * Tell whether this flavor is the default one. * * @return true if this is the default flavor, false */ public final boolean isDefault(){ return this == DEFAULT; } } /** * Build a regular expression covering all possible values of the given enumeration. * * @param enumType Class of an enumeration type. * * @return The build regular expression or "\s*" if the given enumeration contains no constants/values. * * @throws IllegalArgumentException If the given class is not an enumeration type. */ private static String buildRegexp(final Class> enumType) throws IllegalArgumentException{ // The given class must be an enumeration type: if (!enumType.isEnum()) throw new IllegalArgumentException("An enum class was expected, but a " + enumType.getName() + " has been given!"); // Get the enumeration constants/values: Object[] constants = enumType.getEnumConstants(); if (constants == null || constants.length == 0) return "\\s*"; // Concatenate all constants with pipe to build a choice regular expression: StringBuffer buf = new StringBuffer("("); for(int i = 0; i < constants.length; i++){ buf.append(constants[i]); if ((i + 1) < constants.length) buf.append('|'); } return buf.append(')').toString(); } /** *Object representation of an STC coordinate system.
* ** A coordinate system is composed of three parts: a frame ({@link #frame}), * a reference position ({@link #refpos}) and a flavor ({@link #flavor}). *
* ** The default value - also corresponding to an empty string - should be: * {@link Frame#UNKNOWNFRAME} {@link RefPos#UNKNOWNREFPOS} {@link Flavor#SPHERICAL2}. * Once built, it is possible to know whether the coordinate system is the default one * or not thanks to function {@link #isDefault()}. *
* ** An instance of this class can be easily serialized into STC-S using {@link #toSTCS()}, {@link #toFullSTCS()} * or {@link #toString()}. {@link #toFullSTCS()} will display default values explicitly * on the contrary to {@link #toSTCS()} which will replace them by empty strings. *
* *Important note: * The flavors CARTESIAN2 and CARTESIAN3 can not be used with other frame and reference position than * UNKNOWNFRAME and UNKNOWNREFPOS. In the contrary case an {@link IllegalArgumentException} is throw. *
* * @author Grégory Mantelet (ARI) * @version 1.3 (10/2014) * @since 1.3 */ public static class CoordSys { /** First item of a coordinate system expression: the frame. */ public final Frame frame; /** Second item of a coordinate system expression: the reference position. */ public final RefPos refpos; /** Third and last item of a coordinate system expression: the flavor. */ public final Flavor flavor; /** Indicate whether all parts of the coordinate system are set to their default value. */ private final boolean isDefault; /** STC-S representation of this coordinate system. Default items are not written (that's to say, they are replaced by an empty string). */ private final String stcs; /** STC-S representation of this coordinate system. Default items are explicitly written. */ private final String fullStcs; /** * Build a default coordinate system (UNKNOWNFRAME UNKNOWNREFPOS SPHERICAL2). */ public CoordSys(){ this(null, null, null); } /** * Build a coordinate system with the given parts. * * @param fr Frame part. * @param rp Reference position part. * @param fl Flavor part. * * @throws IllegalArgumentException If a cartesian flavor is used with a frame and reference position other than UNKNOWNFRAME and UNKNOWNREFPOS. */ public CoordSys(final Frame fr, final RefPos rp, final Flavor fl) throws IllegalArgumentException{ frame = (fr == null) ? Frame.DEFAULT : fr; refpos = (rp == null) ? RefPos.DEFAULT : rp; flavor = (fl == null) ? Flavor.DEFAULT : fl; if (flavor != Flavor.SPHERICAL2 && (frame != Frame.UNKNOWNFRAME || refpos != RefPos.UNKNOWNREFPOS)) throw new IllegalArgumentException("a coordinate system expressed with a cartesian flavor MUST have an UNKNOWNFRAME and UNKNOWNREFPOS!"); isDefault = frame.isDefault() && refpos.isDefault() && flavor.isDefault(); stcs = ((!frame.isDefault() ? frame + " " : "") + (!refpos.isDefault() ? refpos + " " : "") + (!flavor.isDefault() ? flavor : "")).trim(); fullStcs = frame + " " + refpos + " " + flavor; } /** * Build a coordinate system by parsing the given STC-S expression. * * @param coordsys STC-S expression representing a coordinate system. Empty string and NULL are allowed values ; they correspond to a default coordinate system. * * @throws ParseException If the syntax of the given STC-S expression is wrong or if it is not a coordinate system only. */ public CoordSys(final String coordsys) throws ParseException{ CoordSys tmp = new STCSParser().parseCoordSys(coordsys); frame = tmp.frame; refpos = tmp.refpos; flavor = tmp.flavor; isDefault = tmp.isDefault; stcs = tmp.stcs; fullStcs = tmp.fullStcs; } /** * Tell whether this is the default coordinate system (UNKNOWNFRAME UNKNOWNREFPOS SPHERICAL2). * * @return true if it is the default coordinate system, false otherwise. */ public final boolean isDefault(){ return isDefault; } /** * Get the STC-S expression of this coordinate system, * in which default values are not written (they are replaced by empty strings). * * @return STC-S representation of this coordinate system. */ public String toSTCS(){ return stcs; } /** * Get the STC-S expression of this coordinate system, * in which default values are explicitly written. * * @return STC-S representation of this coordinate system. */ public String toFullSTCS(){ return fullStcs; } /** * Convert this coordinate system into a STC-S expression. * * @see java.lang.Object#toString() * @see #toSTCS() */ @Override public String toString(){ return stcs; } } /** * Parse the given STC-S representation of a coordinate system. * * @param stcs STC-S expression of a coordinate system. Note: a NULL or empty string will be interpreted as a default coordinate system. * * @return The object representation of the specified coordinate system. * * @throws ParseException If the given expression has a wrong STC-S syntax. */ public static CoordSys parseCoordSys(final String stcs) throws ParseException{ return (new STCSParser().parseCoordSys(stcs)); } /** *Convert an object representation of a coordinate system into an STC-S expression.
* *Note: * A NULL object will be interpreted as the default coordinate system and so an empty string will be returned. * Otherwise, this function is equivalent to {@link CoordSys#toSTCS()} (in which default values for each * coordinate system part is not displayed). *
* * @param coordSys The object representation of the coordinate system to convert into STC-S. * * @return The corresponding STC-S expression. * * @see CoordSys#toSTCS() * @see CoordSys#toFullSTCS() */ public static String toSTCS(final CoordSys coordSys){ if (coordSys == null) return ""; else return coordSys.toSTCS(); } /** *Build a big regular expression gathering all of the given coordinate system syntaxes.
* *
* Each item of the given list must respect a strict syntax. Each part of the coordinate system
* may be a single value, a list of values or a '*' (meaning all values are allowed).
* A list of values must have the following syntax: ({value1}|{value2}|...)
.
* An empty string is NOT here accepted.
*
Example:
* (ICRS|FK4|FK5) * SPHERICAL2
is OK,
* but (ICRS|FK4|FK5) *
is not valid because the flavor value is not defined.
*
* Since the default value of each part of a coordinate system should always be possible, * this function ensure these default values are always possible in the returned regular expression. * Thus, if some values except the default one are specified, the default value is automatically appended. *
* *Note: * If the given array is NULL, all coordinate systems are allowed. * But if the given array is empty, none except an empty string or the default value will be allowed. *
* * @param allowedCoordSys List of all coordinate systems that are allowed. * * @return The corresponding regular expression. * * @throws ParseException If the syntax of one of the given allowed coordinate system is wrong. */ public static String buildCoordSysRegExp(final String[] allowedCoordSys) throws ParseException{ // NULL array => all coordinate systems are allowed: if (allowedCoordSys == null) return onlyCoordSysRegExp; // Empty array => no coordinate system (except the default one) is allowed: else if (allowedCoordSys.length == 0) return defaultCoordSysRegExp; // The final regular expression must be reduced to a coordinate system and nothing else before: StringBuffer finalRegExp = new StringBuffer("^\\s*("); // For each allowed coordinate system: Matcher m; int nbCoordSys = 0; for(int i = 0; i < allowedCoordSys.length; i++){ // NULL item => skipped! if (allowedCoordSys[i] == null) continue; else{ if (nbCoordSys > 0) finalRegExp.append('|'); nbCoordSys++; } // Check its syntax and identify all of its parts: m = allowedCoordSysPattern.matcher(allowedCoordSys[i].toUpperCase()); if (m.matches()){ finalRegExp.append('('); for(int g = 0; g < 3; g++){ // See the comment after the Javadoc of #allowedCoordSysRegExp for a complete list of available groups returned by the pattern. // SINGLE VALUE: if (m.group(2 + (6 * g)) != null) finalRegExp.append('(').append(defaultChoice(g, m.group(2 + (6 * g)))).append(m.group(2 + (6 * g))).append(')'); // LIST OF VALUES: else if (m.group(3 + (6 * g)) != null) finalRegExp.append('(').append(defaultChoice(g, m.group(3 + (6 * g)))).append(m.group(3 + (6 * g)).replaceAll("\\s", "").substring(1)); // JOKER (*): else{ switch(g){ case 0: finalRegExp.append(Frame.regexp); break; case 1: finalRegExp.append(RefPos.regexp); break; case 2: finalRegExp.append(Flavor.regexp); break; } finalRegExp.append('?'); } finalRegExp.append("\\s*"); } finalRegExp.append(')'); }else throw new ParseException("Wrong allowed coordinate system syntax for the " + (i + 1) + "-th item: \"" + allowedCoordSys[i] + "\"! Expected: \"frameRegExp refposRegExp flavorRegExp\" ; where each xxxRegExp = (xxx | '*' | '('xxx ('|' xxx)*')'), frame=\"" + Frame.regexp + "\", refpos=\"" + RefPos.regexp + "\" and flavor=\"" + Flavor.regexp + "\" ; an empty string is also allowed and will be interpreted as '*' (so all possible values)."); } // The final regular expression must be reduced to a coordinate system and nothing else after: finalRegExp.append(")\\s*$"); return (nbCoordSys > 0) ? finalRegExp.toString() : defaultCoordSysRegExp; } /** * Get the default value appended by a '|' character, ONLY IF the given value does not already contain the default value. * * @param g Index of the coordinate system part (0: Frame, 1: RefPos, 2: Flavor, another value will return an empty string). * @param value Value in which the default value must prefix. * * @return A prefix for the given value (the default value and a '|' if the default value is not already in the given value, "" otherwise). */ private static String defaultChoice(final int g, final String value){ switch(g){ case 0: return value.contains(Frame.DEFAULT.toString()) ? "" : Frame.DEFAULT + "|"; case 1: return value.contains(RefPos.DEFAULT.toString()) ? "" : RefPos.DEFAULT + "|"; case 2: return value.contains(Flavor.DEFAULT.toString()) ? "" : Flavor.DEFAULT + "|"; default: return ""; } } /* ****** */ /* REGION */ /* ****** */ /** *List all possible region types allowed in an STC-S expression.
* *Note: * The possible values listed in this enumeration are limited to the subset of STC-S described by the section "6 Use of STC-S in TAP (informative)" * of the TAP Recommendation 1.0 (27th March 2010). *
* * @author Grégory Mantelet (ARI) * @version 1.3 (10/2014) * @since 1.3 */ public static enum RegionType{ POSITION, CIRCLE, BOX, POLYGON, UNION, INTERSECTION, NOT; } /** *Object representation of an STC region.
* ** This class contains a field for each possible parameter of a region. Depending of the region type * some are not used. In such case, these unused fields are set to NULL. *
* ** An instance of this class can be easily serialized into STC-S using {@link #toSTCS()}, {@link #toFullSTCS()} * or {@link #toString()}. {@link #toFullSTCS()} will display default value explicit * on the contrary to {@link #toSTCS()} which will replace them by empty strings. *
* * @author Grégory Mantelet (ARI) * @version 1.3 (10/2014) * @since 1.3 */ public static class Region { /** Type of the region. */ public final RegionType type; /** Coordinate system used by this region. * Note: only the NOT region does not declare a coordinate system ; so only for this region this field is NULL. */ public final CoordSys coordSys; /** List of coordinates' pairs. The second dimension of this array represents a pair of coordinates ; it is then an array of two elements. * Note: this field is used by POINT, BOX, CIRCLE and POLYGON. */ public final double[][] coordinates; /** Width of the BOX region. */ public final double width; /** Height of the BOX region. */ public final double height; /** Radius of the CIRCLE region. */ public final double radius; /** List of regions unified (UNION), intersected (INTERSECTION) or avoided (NOT). */ public final Region[] regions; /** STC-S representation of this region, in which default values of the coordinate system (if any) are not written (they are replaced by empty strings). * Note: This attribute is NULL until the first call of the function {@link #toSTCS()} where it is built. */ private String stcs = null; /** STC-S representation of this region, in which default values of the coordinate system (if any) are explicitly written. * Note: This attribute is NULL until the first call of the function {@link #toFullSTCS()} where it is built. */ private String fullStcs = null; /** The ADQL function object representing this region. * Note: this attribute is NULL until the first call of the function {@link #toGeometry()} or {@link #toGeometry(ADQLQueryFactory)}. */ private GeometryFunction geometry = null; /** *Constructor for a POINT/POSITION region.
* *Important note: * The array of coordinates is used like that. No copy is done. *
* * @param coordSys Coordinate system. note: It MAY BE null ; if so, the default coordinate system will be chosen * @param coordinates A pair of coordinates ; coordinates[0] and coordinates[1]. */ public Region(final CoordSys coordSys, final double[] coordinates){ this(coordSys, new double[][]{coordinates}); } /** *Constructor for a POINT/POSITION or a POLYGON region.
* *Whether it is a polygon or a point depends on the number of given coordinates:
*Important note: * The array of coordinates is used like that. No copy is done. *
* * @param coordSys Coordinate system. note: It MAY BE null ; if so, the default coordinate system will be chosen * @param coordinates List of coordinates' pairs ; coordinates[n] = 1 pair = 2 items (coordinates[n][0] and coordinates[n][1]) ; if 1 pair, it is a POINT/POSITION, but if more, it is a POLYGON. */ public Region(final CoordSys coordSys, final double[][] coordinates){ // Check roughly the coordinates: if (coordinates == null || coordinates.length == 0) throw new NullPointerException("Missing coordinates!"); else if (coordinates[0].length != 2) throw new IllegalArgumentException("Wrong number of coordinates! Expected at least 2 pairs of coordinates (so coordinates[0], coordinates[1] and coordinates[n].length = 2)."); // Decide of the region type in function of the number of coordinates' pairs: type = (coordinates.length > 1) ? RegionType.POLYGON : RegionType.POSITION; // Set the coordinate system (if NULL, choose the default one): this.coordSys = (coordSys == null ? new CoordSys() : coordSys); // Set the coordinates: this.coordinates = coordinates; // Set the other fields as not used: width = Double.NaN; height = Double.NaN; radius = Double.NaN; regions = null; } /** *Constructor for a CIRCLE region.
* *Important note: * The array of coordinates is used like that. No copy is done. *
* * @param coordSys Coordinate system. note: It MAY BE null ; if so, the default coordinate system will be chosen * @param coordinates A pair of coordinates ; coordinates[0] and coordinates[1]. * @param radius The circle radius. */ public Region(final CoordSys coordSys, final double[] coordinates, final double radius){ // Check roughly the coordinates: if (coordinates == null || coordinates.length == 0) throw new NullPointerException("Missing coordinates!"); else if (coordinates.length != 2) throw new IllegalArgumentException("Wrong number of coordinates! Expected exactly 2 values."); // Set the region type: type = RegionType.CIRCLE; // Set the coordinate system (if NULL, choose the default one): this.coordSys = (coordSys == null ? new CoordSys() : coordSys); // Set the coordinates: this.coordinates = new double[][]{coordinates}; // Set the radius: this.radius = radius; // Set the other fields as not used: width = Double.NaN; height = Double.NaN; regions = null; } /** *Constructor for a BOX region.
* *Important note: * The array of coordinates is used like that. No copy is done. *
* * @param coordSys Coordinate system. note: It MAY BE null ; if so, the default coordinate system will be chosen * @param coordinates A pair of coordinates ; coordinates[0] and coordinates[1]. * @param width Width of the box. * @param height Height of the box. */ public Region(final CoordSys coordSys, final double[] coordinates, final double width, final double height){ // Check roughly the coordinates: if (coordinates == null || coordinates.length == 0) throw new NullPointerException("Missing coordinates!"); else if (coordinates.length != 2) throw new IllegalArgumentException("Wrong number of coordinates! Expected exactly 2 values."); // Set the region type: type = RegionType.BOX; // Set the coordinate system (if NULL, choose the default one): this.coordSys = (coordSys == null ? new CoordSys() : coordSys); // Set the coordinates: this.coordinates = new double[][]{coordinates}; // Set the size of the box: this.width = width; this.height = height; // Set the other fields as not used: radius = Double.NaN; regions = null; } /** *Constructor for a UNION or INTERSECTION region.
* *Important note: * The array of regions is used like that. No copy is done. *
* * @param unionOrIntersection Type of the region to create. Note: It can be ONLY a UNION or INTERSECTION. Another value will throw an IllegalArgumentException). * @param coordSys Coordinate system. note: It MAY BE null ; if so, the default coordinate system will be chosen * @param regions Regions to unite or to intersect. Note: At least two regions must be provided. */ public Region(final RegionType unionOrIntersection, final CoordSys coordSys, final Region[] regions){ // Check the type: if (unionOrIntersection == null) throw new NullPointerException("Missing type of region (UNION or INTERSECTION here)!"); else if (unionOrIntersection != RegionType.UNION && unionOrIntersection != RegionType.INTERSECTION) throw new IllegalArgumentException("Wrong region type: \"" + unionOrIntersection + "\"! This constructor lets create only an UNION or INTERSECTION region."); // Check the list of regions: if (regions == null || regions.length == 0) throw new NullPointerException("Missing regions to " + (unionOrIntersection == RegionType.UNION ? "unite" : "intersect") + "!"); else if (regions.length < 2) throw new IllegalArgumentException("Wrong number of regions! Expected at least 2 regions."); // Set the region type: type = unionOrIntersection; // Set the coordinate system (if NULL, choose the default one): this.coordSys = (coordSys == null ? new CoordSys() : coordSys); // Set the regions: this.regions = regions; // Set the other fields as not used: coordinates = null; radius = Double.NaN; width = Double.NaN; height = Double.NaN; } /** * Constructor for a NOT region. * * @param region Any region to not select. */ public Region(final Region region){ // Check the region parameter: if (region == null) throw new NullPointerException("Missing region to NOT select!"); // Set the region type: type = RegionType.NOT; // Set the regions: this.regions = new Region[]{region}; // Set the other fields as not used: coordSys = null; coordinates = null; radius = Double.NaN; width = Double.NaN; height = Double.NaN; } /** *Build a Region from the given ADQL representation.
* *Note: * Only {@link PointFunction}, {@link CircleFunction}, {@link BoxFunction}, {@link PolygonFunction} and {@link RegionFunction} * are accepted here. Other extensions of {@link GeometryFunction} will throw an {@link IllegalArgumentException}. *
* * @param geometry The ADQL representation of the region to create here. * * @throws IllegalArgumentException If the given geometry is neither of {@link PointFunction}, {@link BoxFunction}, {@link PolygonFunction} and {@link RegionFunction}. * @throws ParseException If the declared coordinate system, the coordinates or the STC-S definition has a wrong syntax. */ public Region(final GeometryFunction geometry) throws IllegalArgumentException, ParseException{ if (geometry == null) throw new NullPointerException("Missing geometry to convert into STCS.Region!"); if (geometry instanceof PointFunction){ type = RegionType.POSITION; coordSys = STCS.parseCoordSys(extractString(geometry.getCoordinateSystem())); coordinates = new double[][]{{extractNumeric(((PointFunction)geometry).getCoord1()),extractNumeric(((PointFunction)geometry).getCoord2())}}; width = Double.NaN; height = Double.NaN; radius = Double.NaN; regions = null; }else if (geometry instanceof CircleFunction){ type = RegionType.CIRCLE; coordSys = STCS.parseCoordSys(extractString(geometry.getCoordinateSystem())); coordinates = new double[][]{{extractNumeric(((CircleFunction)geometry).getCoord1()),extractNumeric(((CircleFunction)geometry).getCoord2())}}; radius = extractNumeric(((CircleFunction)geometry).getRadius()); width = Double.NaN; height = Double.NaN; regions = null; }else if (geometry instanceof BoxFunction){ type = RegionType.BOX; coordSys = STCS.parseCoordSys(extractString(geometry.getCoordinateSystem())); coordinates = new double[][]{{extractNumeric(((BoxFunction)geometry).getCoord1()),extractNumeric(((BoxFunction)geometry).getCoord2())}}; width = extractNumeric(((BoxFunction)geometry).getWidth()); height = extractNumeric(((BoxFunction)geometry).getHeight()); radius = Double.NaN; regions = null; }else if (geometry instanceof PolygonFunction){ PolygonFunction poly = (PolygonFunction)geometry; type = RegionType.POLYGON; coordSys = STCS.parseCoordSys(extractString(poly.getCoordinateSystem())); coordinates = new double[(poly.getNbParameters() - 1) / 2][2]; for(int i = 0; i < coordinates.length; i++) coordinates[i] = new double[]{extractNumeric(poly.getParameter(1 + i * 2)),extractNumeric(poly.getParameter(2 + i * 2))}; width = Double.NaN; height = Double.NaN; radius = Double.NaN; regions = null; }else if (geometry instanceof RegionFunction){ Region r = STCS.parseRegion(extractString(((RegionFunction)geometry).getParameter(0))); type = r.type; coordSys = r.coordSys; coordinates = r.coordinates; width = r.width; height = r.height; radius = r.radius; regions = r.regions; }else throw new IllegalArgumentException("Unknown region type! Only geometrical function PointFunction, CircleFunction, BoxFunction, PolygonFunction and RegionFunction are allowed."); } /** * Extract a string value from the given {@link ADQLOperand} * which is expected to be a {@link StringConstant} instance. * * @param op A string operand. * * @return The string value embedded in the given operand. * * @throws ParseException If the given operand is not an instance of {@link StringConstant}. */ private static String extractString(final ADQLOperand op) throws ParseException{ if (op == null) throw new NullPointerException("Missing operand!"); else if (op instanceof StringConstant) return ((StringConstant)op).getValue(); else throw new ParseException("Can not convert into STC-S a non string argument (including ADQLColumn and Concatenation)!"); } /** * Extract a numeric value from the given {@link ADQLOperand} * which is expected to be a {@link NumericConstant} instance * or a {@link NegativeOperand} embedding a {@link NumericConstant}. * * @param op A numeric operand. * * @return The numeric value embedded in the given operand. * * @throws ParseException If the given operand is not an instance of {@link NumericConstant} or a {@link NegativeOperand}. */ private static double extractNumeric(final ADQLOperand op) throws ParseException{ if (op == null) throw new NullPointerException("Missing operand!"); else if (op instanceof NumericConstant) return Double.parseDouble(((NumericConstant)op).getValue()); else if (op instanceof NegativeOperand) return extractNumeric(((NegativeOperand)op).getOperand()) * -1; else throw new ParseException("Can not convert into STC-S a non numeric argument (including ADQLColumn and Operation)!"); } /** *Get the STC-S representation of this region (in which default values * of the coordinate system are not written ; they are replaced by empty strings).
* *Note: * This function build the STC-S just once and store it in a class attribute. * The value of this attribute is then returned at next calls of this function. *
* * @return Its STC-S representation. */ public String toSTCS(){ if (stcs != null) return stcs; else{ // Write the region type: StringBuffer buf = new StringBuffer(type.toString()); // Write the coordinate system (except for NOT): if (type != RegionType.NOT){ String coordSysStr = coordSys.toSTCS(); if (coordSysStr != null && coordSysStr.length() > 0) buf.append(' ').append(coordSysStr); buf.append(' '); } // Write the other parameters (coordinates, regions, ...): switch(type){ case POSITION: case POLYGON: appendCoordinates(buf, coordinates); break; case CIRCLE: appendCoordinates(buf, coordinates); buf.append(' ').append(radius); break; case BOX: appendCoordinates(buf, coordinates); buf.append(' ').append(width).append(' ').append(height); break; case UNION: case INTERSECTION: case NOT: buf.append('('); appendRegions(buf, regions, false); buf.append(')'); break; } // Return the built STC-S: return (stcs = buf.toString()); } } /** *Get the STC-S representation of this region (in which default values * of the coordinate system are explicitly written).
* *Note: * This function build the STC-S just once and store it in a class attribute. * The value of this attribute is then returned at next calls of this function. *
* * @return Its STC-S representation. */ public String toFullSTCS(){ if (fullStcs != null) return fullStcs; else{ // Write the region type: StringBuffer buf = new StringBuffer(type.toString()); // Write the coordinate system (except for NOT): if (type != RegionType.NOT){ String coordSysStr = coordSys.toFullSTCS(); if (coordSysStr != null && coordSysStr.length() > 0) buf.append(' ').append(coordSysStr); buf.append(' '); } // Write the other parameters (coordinates, regions, ...): switch(type){ case POSITION: case POLYGON: appendCoordinates(buf, coordinates); break; case CIRCLE: appendCoordinates(buf, coordinates); buf.append(' ').append(radius); break; case BOX: appendCoordinates(buf, coordinates); buf.append(' ').append(width).append(' ').append(height); break; case UNION: case INTERSECTION: case NOT: buf.append('('); appendRegions(buf, regions, true); buf.append(')'); break; } // Return the built STC-S: return (fullStcs = buf.toString()); } } /** * Append all the given coordinates to the given buffer. * * @param buf Buffer in which coordinates must be appended. * @param coords Coordinates to append. */ private static void appendCoordinates(final StringBuffer buf, final double[][] coords){ for(int i = 0; i < coords.length; i++){ if (i > 0) buf.append(' '); buf.append(coords[i][0]).append(' ').append(coords[i][1]); } } /** * Append all the given regions in the given buffer. * * @param buf Buffer in which regions must be appended. * @param regions Regions to append. * @param fullCoordSys Indicate whether the coordinate system of the regions must explicitly display the default values. */ private static void appendRegions(final StringBuffer buf, final Region[] regions, final boolean fullCoordSys){ for(int i = 0; i < regions.length; i++){ if (i > 0) buf.append(' '); if (fullCoordSys) buf.append(regions[i].toFullSTCS()); else buf.append(regions[i].toSTCS()); } } @Override public String toString(){ return toSTCS(); } /** *Convert this region into its corresponding ADQL representation.
* *Note: * This function is using the default ADQL factory, built using {@link ADQLQueryFactory#ADQLQueryFactory()}. *
* * @return The corresponding ADQL representation. * * @see #toGeometry(ADQLQueryFactory) */ public GeometryFunction toGeometry(){ return toGeometry(null); } /** *Convert this region into its corresponding ADQL representation.
* *Note: * This function build the ADQL representation just once and store it in a class attribute. * The value of this attribute is then returned at next calls of this function. *
* * @param factory The factory of ADQL objects to use. * * @return The corresponding ADQL representation. */ public GeometryFunction toGeometry(ADQLQueryFactory factory){ if (factory == null) factory = new ADQLQueryFactory(); try{ if (geometry != null) return geometry; else{ StringConstant coordSysObj = factory.createStringConstant(coordSys == null ? "" : coordSys.toString()); switch(type){ case POSITION: return (geometry = factory.createPoint(coordSysObj, toNumericObj(coordinates[0][0], factory), toNumericObj(coordinates[0][1], factory))); case CIRCLE: return (geometry = factory.createCircle(coordSysObj, toNumericObj(coordinates[0][0], factory), toNumericObj(coordinates[0][1], factory), toNumericObj(radius, factory))); case BOX: return (geometry = factory.createBox(coordSysObj, toNumericObj(coordinates[0][0], factory), toNumericObj(coordinates[0][1], factory), toNumericObj(width, factory), toNumericObj(height, factory))); case POLYGON: ArrayListConvert a numeric value into an ADQL representation:
* *Convert into STC-S the given ADQL representation of a geometrical function.
* *Important note: * Only {@link PointFunction}, {@link CircleFunction}, {@link BoxFunction}, {@link PolygonFunction} * and {@link RegionFunction} are accepted here. Other extensions of {@link GeometryFunction} will * throw an {@link IllegalArgumentException}. *
* * @param region ADQL representation of the region to convert into STC-S. * * @return The corresponding STC-S expression. * * @throws ParseException If the given object is NULL or not of the good type. */ public static String toSTCS(final GeometryFunction region) throws ParseException{ if (region == null) throw new NullPointerException("Missing region to serialize into STC-S!"); return (new Region(region)).toSTCS(); } /* *************************** */ /* PARSER OF STC-S EXPRESSIONS */ /* *************************** */ /** * Let parse any STC-S expression. * * @author Grégory Mantelet (ARI) * @version 1.3 (11/2014) * @since 1.3 */ private static class STCSParser { /** Regular expression of a numerical value. */ private final static String numericRegExp = "(\\+|-)?(\\d+(\\.\\d*)?|\\.\\d+)([Ee](\\+|-)?\\d+)?"; /** Position of the next characters to read in the STC-S expression to parse. */ private int pos; /** Full STC-S expression to parse. */ private String stcs; /** Last read token (can be a numeric, a string, a region type, ...). */ private String token; /** Buffer used to read tokens. */ private StringBuffer buffer; /** * Exception sent when the end of the expression * (EOE = End Of Expression) is reached. * * @author Grégory Mantelet (ARI) * @version 1.3 (10/2014) * @since 1.3 */ private static class EOEException extends ParseException { private static final long serialVersionUID = 1L; /** Build a simple EOEException. */ public EOEException(){ super("Unexpected End Of Expression!"); } } /** * Build the STC-S parser. */ public STCSParser(){} /** * Parse the given STC-S expression, expected as a coordinate system. * * @param stcs The STC-S expression to parse. * * @return The corresponding object representation of the specified coordinate system. * * @throws ParseException If the syntax of the given STC-S expression is wrong or if it is not a coordinate system. */ public CoordSys parseCoordSys(final String stcs) throws ParseException{ init(stcs); CoordSys coordsys = null; try{ coordsys = coordSys(); end(COORD_SYS_SYNTAX); return coordsys; }catch(EOEException ex){ ex.printStackTrace(); return new CoordSys(); } } /** * Parse the given STC-S expression, expected as a geometrical region. * * @param stcs The STC-S expression to parse. * * @return The corresponding object representation of the specified geometrical region. * * @throws ParseException If the syntax of the given STC-S expression is wrong or if it is not a geometrical region. */ public Region parseRegion(final String stcs) throws ParseException{ init(stcs); Region region = region(); end("\"POSITIONGet the next meaningful word. This word can be a numeric, any string constant or a region type.
* ** In case the end of the expression is reached before getting any meaningful character, an {@link EOEException} is thrown. *
* * @return The full read word/token. * * @throws EOEException If the end of the STC-S expression is reached before getting any meaningful character. */ private String nextToken() throws EOEException{ // Skip all spaces: skipSpaces(); // Fetch all characters until word separator (a space or a open/close parenthesis): while(pos < stcs.length() && !Character.isWhitespace(stcs.charAt(pos)) && stcs.charAt(pos) != '(' && stcs.charAt(pos) != ')') buffer.append(stcs.charAt(pos++)); // If no character has been fetched while at least one was expected, throw an exception: if (buffer.length() == 0) throw new EOEException(); // Save the read token and reset the buffer: token = buffer.toString(); buffer.delete(0, token.length()); return token; } /** * Read the next token as a numeric. * If not a numeric, a {@link ParseException} is thrown. * * @return The read numerical value. * * @throws ParseException If the next token is not a numerical expression. */ private double numeric() throws ParseException{ if (nextToken().matches(numericRegExp)) return Double.parseDouble(token); else throw new ParseException("a numeric was expected!", new TextPosition(1, pos - token.length(), 1, pos)); } /** * Read the next 2 tokens as a coordinate pairs (so as 2 numerical values). * If not 2 numeric, a {@link ParseException} is thrown. * * @return The read coordinate pairs. * * @throws ParseException If the next 2 tokens are not 2 numerical expressions. */ private double[] coordPair() throws ParseException{ skipSpaces(); int startPos = pos; try{ return new double[]{numeric(),numeric()}; }catch(ParseException pe){ if (pe instanceof EOEException) throw pe; else throw new ParseException("a coordinates pair (2 numerics separated by one or more spaces) was expected!", new TextPosition(1, startPos, 1, pos)); } } /** * Read and parse the next tokens as a coordinate system expression. * If they do not match, a {@link ParseException} is thrown. * * @return The object representation of the read coordinate system. * * @throws ParseException If the next tokens are not representing a valid coordinate system. */ private CoordSys coordSys() throws ParseException{ // Skip all spaces: skipSpaces(); // Backup the current position: /* (because every parts of a coordinate system are optional ; * like this, it will be possible to go back in the expression * to parse if optional parts are not written) */ String oldToken = token; int startPos = pos; Frame fr = null; RefPos rp = null; Flavor fl = null; try{ // Read the token: nextToken(); // Try to parse it as a frame: if ((fr = frame()) != null){ // if success, go the next token: startPos = pos; oldToken = token; nextToken(); } // Try to parse the last read token as a reference position: if ((rp = refpos()) != null){ // if success, go the next token: startPos = pos; oldToken = token; nextToken(); } // Try to parse the last read token as a flavor: if ((fl = flavor()) == null){ // if NOT a success, go back "in time" (go back to the position before reading the token): pos = startPos; token = oldToken; } }catch(EOEException ex){ /* End Of Expression may happen here since all parts of a coordinate system are optional. * So, there is no need to treat the error. */ } // Build the object representation of the read coordinate system: /* Note: if nothing has been read for one or all parts of the coordinate system, * the NULL value will be replaced automatically in the constructor * by the default value of the corresponding part(s). */ try{ return new CoordSys(fr, rp, fl); }catch(IllegalArgumentException iae){ throw new ParseException(iae.getMessage(), new TextPosition(1, startPos, 1, pos)); } } /** * Parse the last read token as FRAME. * * @return The corresponding enumeration item, or NULL if the last token is not a valid FRAME item. */ private Frame frame(){ try{ return Frame.valueOf(token.toUpperCase()); }catch(IllegalArgumentException iae){ return null; } } /** * Parse the last read token as REFERENCE POSITION. * * @return The corresponding enumeration item, or NULL if the last token is not a valid REFERENCE POSITION item. */ private RefPos refpos(){ try{ return RefPos.valueOf(token.toUpperCase()); }catch(IllegalArgumentException iae){ return null; } } /** * Parse the last read token as FLAVOR. * * @return The corresponding enumeration item, or NULL if the last token is not a valid FLAVOR item. */ private Flavor flavor(){ try{ return Flavor.valueOf(token.toUpperCase()); }catch(IllegalArgumentException iae){ return null; } } /** * Read and parse the next tokens as a geometrical region. * If they do not match, a {@link ParseException} is thrown. * * @return The object representation of the read geometrical region. * * @throws ParseException If the next tokens are not representing a valid geometrical region. */ private Region region() throws ParseException{ // Skip all spaces: skipSpaces(); // Read the next token (it should be the region type): int startPos = pos; token = nextToken().toUpperCase(); /* Identify the region type, next the expected parameters and finally build the corresponding object representation */ // POSITION case: if (token.equals("POSITION")){ try{ CoordSys coordSys = coordSys(); double[] coords = coordPair(); return new Region(coordSys, coords); }catch(Exception e){ throw buildException(e, "\"POSITIONDefinition of a valid target table.
* ** This table can be used in an ADQL query with its ADQL name ({@link #getADQLName()}) * and corresponds to a real table in the "database" with its DB name ({@link #getDBName()}). *
* * @author Grégory Mantelet (CDS;ARI) * @version 1.4 (07/2016) */ public interface DBTable extends IterableMakes a copy of this instance of {@link DBTable}, with the possibility to change the DB and ADQL names.
* *IMPORTANT:
* The given DB and ADQL name may be NULL. If NULL, the copy will contain exactly the same full name (DB and/or ADQL).
* And they may be qualified (that's to say: prefixed by the schema name or by the catalog and schema name). It means that it is possible to
* change the catalog, schema and table name in the copy.
* For instance:
*
* Describe a full column type as it is described in the IVOA document of TAP.
* Thus, this object contains 2 attributes: type
(or datatype) and length
(or size).
*
The length/size may be not defined ; in this case, its value is set to {@link #NO_LENGTH} or is negative or null.
* *All datatypes declared in the IVOA recommendation document of TAP are listed in an enumeration type: {@link DBDatatype}. * It is used to set the attribute type/datatype of this class.
* * @author Grégory Mantelet (ARI) * @version 1.4 (07/2016) * @since 1.3 */ public class DBType { /** * List of all datatypes declared in the IVOA recommendation of TAP (in the section UPLOAD). * * @author Grégory Mantelet (ARI) * @version 1.4 (07/2016) * @since 1.3 */ public static enum DBDatatype{ SMALLINT, INTEGER, BIGINT, REAL, DOUBLE, BINARY, VARBINARY, CHAR, VARCHAR, BLOB, CLOB, TIMESTAMP, POINT, REGION, /** Type to use when the precise datatype is unknown. * @since 1.4 */ UNKNOWN, /**Type to use when the type is known as numeric but there is no precise datatype * (e.g. double, float, integer, ...).
*It is particularly used when creating a {@link DefaultDBColumn} from an ADQL function * or operation while listing resulting columns of a sub-query.
*This type is similar to {@link #UNKNOWN}.
* @since 1.4 */ UNKNOWN_NUMERIC; /** String to return when {@link #toString()} is called. * @since 1.4*/ private String strExp = this.name(); @Override public String toString(){ return strExp; } /** *This function lets define the name of the type as provided * ONLY FOR {@link #UNKNOWN} and {@link #UNKNOWN_NUMERIC} {@link DBDatatype}s.
* *Important: * If this {@link DBDatatype} is not {@link #UNKNOWN} or {@link #UNKNOWN_NUMERIC} this function has no effect. * But if the given name is NULL or empty, no custom type will be set ; instead the default value (i.e. name of * the unknown enum item) will be returned. *
* * @param typeName User type name. * * @since 1.4 */ public void setCustomType(final String typeName){ if ((this == UNKNOWN || this == UNKNOWN_NUMERIC)){ if (typeName != null && typeName.trim().length() > 0) strExp = "?" + typeName.trim() + "?"; else strExp = this.name(); } } } /** Special value in case no length/size is specified. */ public static final int NO_LENGTH = -1; /** Datatype of a column. */ public final DBDatatype type; /** The length parameter (only few datatypes need this parameter: char, varchar, binary and varbinary). */ public final int length; /** * Build a TAP column type by specifying a datatype. * * @param datatype Column datatype. */ public DBType(final DBDatatype datatype){ this(datatype, NO_LENGTH); } /** * Build a TAP column type by specifying a datatype and a length (needed only for datatypes like char, varchar, binary and varbinary). * * @param datatype Column datatype. * @param length Length of the column value (needed only for datatypes like char, varchar, binary and varbinary). */ public DBType(final DBDatatype datatype, final int length){ if (datatype == null) throw new NullPointerException("Missing TAP column datatype !"); this.type = datatype; this.length = length; } /** *Tells whether this type is a numeric.
* *Concerned types: * {@link DBDatatype#SMALLINT SMALLINT}, {@link DBDatatype#INTEGER INTEGER}, {@link DBDatatype#BIGINT BIGINT}, * {@link DBDatatype#REAL REAL}, {@link DBDatatype#DOUBLE DOUBLE}, {@link DBDatatype#BINARY BINARY}, * {@link DBDatatype#VARBINARY VARBINARY} and {@link DBDatatype#BLOB BLOB}. *
* *Important note:
* Since {@link DBDatatype#UNKNOWN UNKNOWN} is an unresolved type, it can potentially be anything.
* But, in order to avoid incorrect operation while expecting a numeric although the type is unknown
* and is in fact not really a numeric, this function will return false
if the type is
* {@link DBDatatype#UNKNOWN UNKNOWN} BUT true
if
* {@link DBDatatype#UNKNOWN_NUMERIC UNKNOWN_NUMERIC}.
*
true
if this type is a numeric, false
otherwise.
*/
public boolean isNumeric(){
switch(type){
case SMALLINT:
case INTEGER:
case BIGINT:
case REAL:
case DOUBLE:
/* Note: binaries are also included here because they can also be considered as Numeric,
* but not for JOINs. */
case BINARY:
case VARBINARY:
case BLOB:
case UNKNOWN_NUMERIC:
return true;
default:
return false;
}
}
/**
* Tells whether this type is a list of bytes.
* *Concerned types: * {@link DBDatatype#BINARY BINARY}, {@link DBDatatype#VARBINARY VARBINARY} and {@link DBDatatype#BLOB BLOB}. *
* *Important note:
* Since {@link DBDatatype#UNKNOWN UNKNOWN} is an unresolved type, it can potentially be anything.
* But, in order to avoid incorrect operation while expecting a binary although the type is unknown
* and is in fact not really a binary, this function will return false
if the type is
* {@link DBDatatype#UNKNOWN UNKNOWN} or {@link DBDatatype#UNKNOWN_NUMERIC UNKNOWN_NUMERIC}.
*
true
if this type is a binary, false
otherwise.
*/
public boolean isBinary(){
switch(type){
case BINARY:
case VARBINARY:
case BLOB:
return true;
default:
return false;
}
}
/**
* Tells whether this type is about characters.
* *Concerned types: * {@link DBDatatype#CHAR CHAR}, {@link DBDatatype#VARCHAR VARCHAR}, {@link DBDatatype#CLOB CLOB} * and {@link DBDatatype#TIMESTAMP TIMESTAMP}. *
* *Important note:
* Since {@link DBDatatype#UNKNOWN UNKNOWN} is an unresolved type, it can potentially be anything.
* But, in order to avoid incorrect operation while expecting a string although the type is unknown
* and is in fact not really a string, this function will return false
if the type is
* {@link DBDatatype#UNKNOWN UNKNOWN} or {@link DBDatatype#UNKNOWN_NUMERIC UNKNOWN_NUMERIC}
*
true
if this type is a string, false
otherwise.
*/
public boolean isString(){
switch(type){
case CHAR:
case VARCHAR:
case CLOB:
case TIMESTAMP:
return true;
default:
return false;
}
}
/**
* Tells whether this type is a geometrical region.
* *Concerned types: * {@link DBDatatype#POINT POINT} and {@link DBDatatype#REGION REGION}. *
* *Important note:
* Since {@link DBDatatype#UNKNOWN UNKNOWN} is an unresolved type, it can potentially be anything.
* But, in order to avoid incorrect operation while expecting a geometry although the type is unknown
* and is in fact not really a geometry, this function will return false
if the type is
* {@link DBDatatype#UNKNOWN UNKNOWN} or {@link DBDatatype#UNKNOWN_NUMERIC UNKNOWN_NUMERIC}.
*
true
if this type is a geometry, false
otherwise.
*/
public boolean isGeometry(){
return (type == DBDatatype.POINT || type == DBDatatype.REGION);
}
/**
* Tell whether this type has been resolved or not.
* *Concerned types: * {@link DBDatatype#UNKNOWN UNKNOWN} and {@link DBDatatype#UNKNOWN_NUMERIC UNKNOWN_NUMERIC}. *
* * @returntrue
if this type has NOT been resolved, false
otherwise.
*
* @since 1.4
*/
public boolean isUnknown(){
return type == DBDatatype.UNKNOWN || type == DBDatatype.UNKNOWN_NUMERIC;
}
/**
* Tell whether this {@link DBType} is compatible with the given one.
* *
* Two {@link DBType}s are said compatible if they are both binary, numeric, geometric or string.
* If one of the two types is {@link DBDatatype#UNKNOWN unknown} or {@link DBDatatype#UNKNOWN_NUMERIC unknown_numeric},
* this function will consider them as compatible and will return true
.
*
true
if this type is compatible with the given one, false
otherwise.
*/
public boolean isCompatible(final DBType t){
if (t == null)
return false;
else if (isUnknown() || t.isUnknown())
return true;
else if (isBinary() == t.isBinary())
return (type == DBDatatype.BLOB && t.type == DBDatatype.BLOB) || (type != DBDatatype.BLOB && t.type != DBDatatype.BLOB);
else if (isNumeric() == t.isNumeric())
return true;
else if (isGeometry() == t.isGeometry())
return (type == t.type);
else if (isString())
return (type == DBDatatype.CLOB && t.type == DBDatatype.CLOB) || (type != DBDatatype.CLOB && t.type != DBDatatype.CLOB);
else
return (type == t.type);
}
@Override
public String toString(){
if (length > 0)
return type + "(" + length + ")";
else
return type.toString();
}
}
src/adql/db/SearchColumnList.java 0000644 0001750 0001750 00000033132 13177122310 015722 0 ustar oles oles package adql.db;
/*
* This file is part of ADQLLibrary.
*
* ADQLLibrary 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.
*
* ADQLLibrary 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 ADQLLibrary. If not, see A list of {@link DBColumn} elements ordered by their ADQL name in an ascending manner.
* ** In addition to an ADQL name, {@link DBColumn} elements can be searched by specifying their table, schema and catalog. * These last information will be used only if the ADQL column name is ambiguous, otherwise all matching elements are returned. *
* ** Note: * Table aliases can be listed here with their corresponding table name. Consequently, a table alias can be given as table name in the search parameters. *
* * @author Grégory Mantelet (CDS;ARI) * @version 1.4 (09/2017) */ public class SearchColumnList extends TextualSearchListSet the type of this column.
* *Note 1: * The given type should be as closed as possible from a type listed by the IVOA in the TAP protocol description into the section UPLOAD. *
* *Note 2: * there is no default value. Consequently if this parameter is NULL, * the type should be considered as unknown. It means that any comparison with * any type will always return 'true'. *
* * @param type New type of this column. * * @since 1.3 */ public final void setDatatype(final DBType type){ this.type = type; } @Override public final String getDBName(){ return dbName; } @Override public final DBTable getTable(){ return table; } public final void setTable(final DBTable table){ this.table = table; } @Override public DBColumn copy(final String dbName, final String adqlName, final DBTable dbTable){ return new DefaultDBColumn(dbName, adqlName, type, dbTable); } } src/adql/db/SearchTableList.java 0000644 0001750 0001750 00000014721 13177122310 015517 0 ustar oles oles package adql.db; /* * This file is part of ADQLLibrary. * * ADQLLibrary 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. * * ADQLLibrary 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 ADQLLibrary. If not, seeA list of {@link DBTable} elements ordered by their ADQL name in an ascending manner.
* ** In addition to an ADQL name, {@link DBTable} elements can be searched by specifying their schema and catalog. * These last information will be used only if the ADQL table name is ambiguous, otherwise all matching elements are returned. *
* * @author Grégory Mantelet (CDS;ARI) * @version 1.4 (09/2017) */ public class SearchTableList extends TextualSearchList* This wrapper aims to represent in the metadata the aliasing of a table. * This table should not be part of any schema, in ADQL but also in SQL...it is * just an alias of an existing table. *
* ** All columns of the origin table are completely copied into this * {@link DBTable} thanks to {@link DBColumn#copy(String, String, DBTable)}, * with the same ADQL and DB name but a different parent table (this one is * used of the original one). *
* *Note: * The origin table is still available thanks to the function * {@link #getOriginTable()}. *
* * @author Grégory Mantelet (ARI) * @version 1.4 (11/2017) * @since 1.4 */ public class DBTableAlias extends DefaultDBTable { /** Wrapped table. */ protected final DBTable originTable; /** * Wrap the given table under the given ADQL/DB name. * * @param originTable The table to wrap/alias. * @param tableAlias The alias name. */ public DBTableAlias(final DBTable originTable, final String tableAlias){ super(null, null, tableAlias); this.originTable = originTable; for(DBColumn col : originTable) addColumn(col.copy(col.getDBName(), col.getADQLName(), this)); } /** * Get the aliased/wrapped table. * * @return The aliased table. */ public DBTable getOriginTable(){ return originTable; } } src/adql/db/DBChecker.java 0000644 0001750 0001750 00000206403 13226136374 014272 0 ustar oles oles package adql.db; /* * This file is part of ADQLLibrary. * * ADQLLibrary 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. * * ADQLLibrary 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 ADQLLibrary. If not, see* In addition to check the existence of tables and columns referenced in the query, * this checked will also attach database metadata on these references ({@link ADQLTable} * and {@link ADQLColumn} instances when they are resolved. *
* *These information are:
*Note: * Knowing DB metadata of {@link ADQLTable} and {@link ADQLColumn} is particularly useful for the translation of the ADQL query to SQL, * because the ADQL name of columns and tables can be replaced in SQL by their DB name, if different. This mapping is done automatically * by {@link adql.translator.JDBCTranslator}. *
* * @author Grégory Mantelet (CDS;ARI) * @version 1.4 (11/2017) */ public class DBChecker implements QueryChecker { /** List of all available tables ({@link DBTable}). */ protected SearchTableApi lstTables; /**List of all allowed geometrical functions (i.e. CONTAINS, REGION, POINT, COORD2, ...).
** If this list is NULL, all geometrical functions are allowed. * However, if not, all items of this list must be the only allowed geometrical functions. * So, if the list is empty, no such function is allowed. *
* @since 1.3 */ protected String[] allowedGeo = null; /**List of all allowed coordinate systems.
** Each item of this list must be of the form: "{frame} {refpos} {flavor}". * Each of these 3 items can be either of value, a list of values expressed with the syntax "({value1}|{value2}|...)" * or a '*' to mean all possible values. *
*Note: since a default value (corresponding to the empty string - '') should always be possible for each part of a coordinate system, * the checker will always add the default value (UNKNOWNFRAME, UNKNOWNREFPOS or SPHERICAL2) into the given list of possible values for each coord. sys. part.
** If this list is NULL, all coordinates systems are allowed. * However, if not, all items of this list must be the only allowed coordinate systems. * So, if the list is empty, none is allowed. *
* @since 1.3 */ protected String[] allowedCoordSys = null; /**A regular expression built using the list of allowed coordinate systems. * With this regex, it is possible to known whether a coordinate system expression is allowed or not.
*If NULL, all coordinate systems are allowed.
* @since 1.3 */ protected String coordSysRegExp = null; /**List of all allowed User Defined Functions (UDFs).
** If this list is NULL, any encountered UDF will be allowed. * However, if not, all items of this list must be the only allowed UDFs. * So, if the list is empty, no UDF is allowed. *
* @since 1.3 */ protected FunctionDef[] allowedUdfs = null; /* ************ */ /* CONSTRUCTORS */ /* ************ */ /** *Builds a {@link DBChecker} with an empty list of tables.
* *Verifications done by this object after creation:
*Builds a {@link DBChecker} with the given list of known tables.
* *Verifications done by this object after creation:
*Builds a {@link DBChecker} with the given list of known tables and with a restricted list of user defined functions.
* *Verifications done by this object after creation:
*Builds a {@link DBChecker} with the given list of known tables and with a restricted list of user defined functions.
* *Verifications done by this object after creation:
*Builds a {@link DBChecker}.
* *Verifications done by this object after creation:
*Sets the list of all available tables.
* *Note: * Only if the given collection is NOT an implementation of * {@link SearchTableApi}, the collection will be copied inside a new * {@link SearchTableList}, otherwise it is used as provided. *
* * @param tables List of {@link DBTable}s. */ public final void setTables(final Collection extends DBTable> tables){ if (tables == null) lstTables = new SearchTableList(); else if (tables instanceof SearchTableApi) lstTables = (SearchTableApi)tables; else lstTables = new SearchTableList(tables); } /* ************* */ /* CHECK METHODS */ /* ************* */ /** *Check all the columns, tables and UDFs references inside the given query.
* ** Note: This query has already been parsed ; thus it is already syntactically correct. * Only the consistency with the published tables, columns and all the defined UDFs must be checked. *
* * @param query The query to check. * * @throws ParseException An {@link UnresolvedIdentifiersException} if some tables or columns can not be resolved. * * @see #check(ADQLQuery, Stack) */ @Override public final void check(final ADQLQuery query) throws ParseException{ check(query, null); } /** *Process several (semantic) verifications in the given ADQL query.
* *Main verifications done in this function:
*Check DB items (tables and columns) used in the given ADQL query.
* *Operations done in this function:
** If a table is not a DB table reference but a sub-query, this latter is * first checked (using {@link #check(ADQLQuery, Stack)} ; but the father * list must not contain tables of the given query, because on the same * level) and then corresponding table metadata are generated (using * {@link #generateDBTable(ADQLQuery, String)}) and attached to it. *
* * Management of "{table}.*" in the SELECT clause ** For each of this SELECT item, this function tries to resolve the table * name. If only one match is found, the corresponding ADQL table object * is got from the list of resolved tables and attached to this SELECT item * (thus, the joker item will also have the good metadata, particularly if * the referenced table is a sub-query). *
* * Table alias ** When a simple table (i.e. not a sub-query) is aliased, the metadata of * this table will be wrapped inside a {@link DBTableAlias} in order to * keep the original metadata but still declare use the table with the * alias instead of its original name. The original name will be used * only when translating the corresponding FROM item ; the rest of the time * (i.e. for references when using a column), the alias name must be used. *
** In order to avoid unpredictable behavior at execution of the SQL query, * the alias will be put in lower case if not defined between double * quotes. *
* * @param query Query in which the existence of tables must be * checked. * @param fathersList List of all columns available in the father queries * and that should be accessed in sub-queries. * Each item of this stack is a list of columns * available in each father-level query. * Note: this parameter is NULL if this function is * called with the root/father query as parameter. * @param errors List of errors to complete in this function each * time an unknown table or column is encountered. * * @return An associative map of all the resolved tables. */ protected MapSearch all column references inside the given query, resolve them thanks to the given tables' metadata, * and if there is only one match, attach the matching metadata to them.
* * Management of selected columns' references ** A column reference is not only a direct reference to a table column using a column name. * It can also be a reference to an item of the SELECT clause (which will then call a "selected column"). * That kind of reference can be either an index (an unsigned integer starting from 1 to N, where N is the * number selected columns), or the name/alias of the column. *
** These references are also checked, in a second step, in this function. Thus, column metadata are * also attached to them, as common columns. *
* * @param query Query in which the existence of tables must be checked. * @param fathersList List of all columns available in the father queries and that should be accessed in sub-queries. * Each item of this stack is a list of columns available in each father-level query. * Note: this parameter is NULL if this function is called with the root/father query as parameter. * @param mapTables List of all resolved tables. * @param list List of column metadata to complete in this function each time a column reference is resolved. * @param errors List of errors to complete in this function each time an unknown table or column is encountered. */ protected void resolveColumns(final ADQLQuery query, final StackResolve the given column, that's to say search for the corresponding {@link DBColumn}.
* ** The third parameter is used only if this function is called inside a sub-query. In this case, * the column is tried to be resolved with the first list (dbColumns). If no match is found, * the resolution is tried with the father columns list (fathersList). *
* * @param column The column to resolve. * @param dbColumns List of all available {@link DBColumn}s. * @param fathersList List of all columns available in the father queries and that should be accessed in sub-queries. * Each item of this stack is a list of columns available in each father-level query. * Note: this parameter is NULL if this function is called with the root/father query as parameter. * * @return The corresponding {@link DBColumn} if found. Otherwise an exception is thrown. * * @throws ParseException An {@link UnresolvedColumnException} if the given column can't be resolved * or an {@link UnresolvedTableException} if its table reference can't be resolved. */ protected DBColumn resolveColumn(final ADQLColumn column, final SearchColumnList dbColumns, StackSearch all UDFs (User Defined Functions) inside the given query, and then * check their signature against the list of allowed UDFs.
* *Note: * When more than one allowed function match, the function is considered as correct * and no error is added. * However, in case of multiple matches, the return type of matching functions could * be different and in this case, there would be an error while checking later * the types. In such case, throwing an error could make sense, but the user would * then need to cast some parameters to help the parser identifying the right function. * But the type-casting ability is not yet possible in ADQL. *
* * @param query Query in which UDFs must be checked. * @param errors List of errors to complete in this function each time a UDF does not match to any of the allowed UDFs. * * @since 1.3 */ protected void checkUDFs(final ADQLQuery query, final UnresolvedIdentifiersException errors){ // 1. Search all UDFs: ISearchHandler sHandler = new SearchUDFHandler(); sHandler.search(query); // If no UDF are allowed, throw immediately an error: if (allowedUdfs.length == 0){ for(ADQLObject result : sHandler) errors.addException(new UnresolvedFunctionException((UserDefinedFunction)result)); } // 2. Try to resolve all of them: else{ ArrayListTell whether the type of all parameters of the given ADQL function * is resolved.
* *A parameter type may not be resolved for 2 main reasons:
*Check all geometries.
* *Operations done in this function:
*Check whether the specified geometrical function is allowed by this implementation.
* *Note: * If the list of allowed geometrical functions is empty, this function will always add an errors to the given list. * Indeed, it means that no geometrical function is allowed and so that the specified function is automatically not supported. *
* * @param fctName Name of the geometrical function to test. * @param fct The function instance being or containing the geometrical function to check. Note: this function can be the function to test or a function embedding the function under test (i.e. RegionFunction). * @param binSearch The object to use in order to search a function name inside the list of allowed functions. * It is able to perform a binary search inside a sorted array of String objects. The interest of * this object is its compare function which must be overridden and tells how to compare the item * to search and the items of the array (basically, a non-case-sensitive comparison between 2 strings). * @param errors List of errors to complete in this function each time a geometrical function is not supported. * * @since 1.3 */ protected void checkGeometryFunction(final String fctName, final ADQLFunction fct, final BinarySearchSearch all explicit coordinate system declarations, check their syntax and whether they are allowed by this implementation.
* *Note: * "explicit" means here that all {@link StringConstant} instances. Only coordinate systems expressed as string can * be parsed and so checked. So if a coordinate system is specified by a column, no check can be done at this stage... * it will be possible to perform such test only at the execution. *
* * @param query Query in which coordinate systems must be checked. * @param errors List of errors to complete in this function each time a coordinate system has a wrong syntax or is not supported. * * @see #checkCoordinateSystem(StringConstant, UnresolvedIdentifiersException) * * @since 1.3 */ protected void resolveCoordinateSystems(final ADQLQuery query, final UnresolvedIdentifiersException errors){ ISearchHandler sHandler = new SearchCoordSysHandler(); sHandler.search(query); for(ADQLObject result : sHandler) checkCoordinateSystem((StringConstant)result, errors); } /** * Parse and then check the coordinate system contained in the given {@link StringConstant} instance. * * @param adqlCoordSys The {@link StringConstant} object containing the coordinate system to check. * @param errors List of errors to complete in this function each time a coordinate system has a wrong syntax or is not supported. * * @see STCS#parseCoordSys(String) * @see #checkCoordinateSystem(adql.db.STCS.CoordSys, ADQLOperand, UnresolvedIdentifiersException) * * @since 1.3 */ protected void checkCoordinateSystem(final StringConstant adqlCoordSys, final UnresolvedIdentifiersException errors){ String coordSysStr = adqlCoordSys.getValue(); try{ checkCoordinateSystem(STCS.parseCoordSys(coordSysStr), adqlCoordSys, errors); }catch(ParseException pe){ errors.addException(new ParseException(pe.getMessage(), adqlCoordSys.getPosition())); } } /** * Check whether the given coordinate system is allowed by this implementation. * * @param coordSys Coordinate system to test. * @param operand The operand representing or containing the coordinate system under test. * @param errors List of errors to complete in this function each time a coordinate system is not supported. * * @since 1.3 */ protected void checkCoordinateSystem(final CoordSys coordSys, final ADQLOperand operand, final UnresolvedIdentifiersException errors){ if (coordSysRegExp != null && coordSys != null && !coordSys.toFullSTCS().matches(coordSysRegExp)){ StringBuffer buf = new StringBuffer(); if (allowedCoordSys != null){ for(String cs : allowedCoordSys){ if (buf.length() > 0) buf.append(", "); buf.append(cs); } } if (buf.length() == 0) buf.append("No coordinate system is allowed!"); else buf.insert(0, "Allowed coordinate systems are: "); errors.addException(new ParseException("Coordinate system \"" + ((operand instanceof StringConstant) ? ((StringConstant)operand).getValue() : coordSys.toString()) + "\" (= \"" + coordSys.toFullSTCS() + "\") not allowed in this implementation. " + buf.toString(), operand.getPosition())); } } /** *Search all STC-S expressions inside the given query, parse them (and so check their syntax) and then determine * whether the declared coordinate system and the expressed region are allowed in this implementation.
* *Note: * In the current ADQL language definition, STC-S expressions can be found only as only parameter of the REGION function. *
* * @param query Query in which STC-S expressions must be checked. * @param binSearch The object to use in order to search a region name inside the list of allowed functions/regions. * It is able to perform a binary search inside a sorted array of String objects. The interest of * this object is its compare function which must be overridden and tells how to compare the item * to search and the items of the array (basically, a non-case-sensitive comparison between 2 strings). * @param errors List of errors to complete in this function each time the STC-S syntax is wrong or each time the declared coordinate system or region is not supported. * * @see STCS#parseRegion(String) * @see #checkRegion(adql.db.STCS.Region, RegionFunction, BinarySearch, UnresolvedIdentifiersException) * * @since 1.3 */ protected void resolveSTCSExpressions(final ADQLQuery query, final BinarySearchCheck the given region.
* *The following points are checked in this function:
*Search all operands whose the type is not yet known and try to resolve it now * and to check whether it matches the type expected by the syntactic parser.
* ** Only two operands may have an unresolved type: columns and user defined functions. * Indeed, their type can be resolved only if the list of available columns and UDFs is known, * and if columns and UDFs used in the query are resolved successfully. *
* ** When an operand type is still unknown, they will own the three kinds of type and * so this function won't raise an error: it is thus automatically on the expected type. * This behavior is perfectly correct because if the type is not resolved * that means the item/operand has not been resolved in the previous steps and so that * an error about this item has already been raised. *
* *Important note: * This function does not check the types exactly, but just roughly by considering only three categories: * string, numeric and geometry. *
* * @param query Query in which unknown types must be resolved and checked. * @param errors List of errors to complete in this function each time a types does not match to the expected one. * * @see UnknownType * * @since 1.3 */ protected void checkTypes(final ADQLQuery query, final UnresolvedIdentifiersException errors){ // Search all unknown types: ISearchHandler sHandler = new SearchUnknownTypeHandler(); sHandler.search(query); // Check whether their type matches the expected one: UnknownType unknown; for(ADQLObject result : sHandler){ unknown = (UnknownType)result; switch(unknown.getExpectedType()){ case 'G': case 'g': if (!unknown.isGeometry()) errors.addException(new ParseException("Type mismatch! A geometry was expected instead of \"" + unknown.toADQL() + "\".", result.getPosition())); break; case 'N': case 'n': if (!unknown.isNumeric()) errors.addException(new ParseException("Type mismatch! A numeric value was expected instead of \"" + unknown.toADQL() + "\".", result.getPosition())); break; case 'S': case 's': if (!unknown.isString()) errors.addException(new ParseException("Type mismatch! A string value was expected instead of \"" + unknown.toADQL() + "\".", result.getPosition())); break; } } } /* ******************************** */ /* METHODS CHECKING THE SUB-QUERIES */ /* ******************************** */ /** *Search all sub-queries found in the given query but not in the clause FROM. * These sub-queries are then checked using {@link #check(ADQLQuery, Stack)}.
* * Fathers stack ** Each time a sub-query must be checked with {@link #check(ADQLQuery, Stack)}, * the list of all columns available in each of its father queries must be provided. * This function is composing itself this stack by adding the given list of available * columns (= all columns resolved in the given query) at the end of the given stack. * If this stack is given empty, then a new stack is created. *
** This modification of the given stack is just the execution time of this function. * Before returning, this function removes the last item of the stack. *
* * * @param query Query in which sub-queries must be checked. * @param fathersList List of all columns available in the father queries and that should be accessed in sub-queries. * Each item of this stack is a list of columns available in each father-level query. * Note: this parameter is NULL if this function is called with the root/father query as parameter. * @param availableColumns List of all columns resolved in the given query. * @param errors List of errors to complete in this function each time a semantic error is encountered. * * @since 1.3 */ protected void checkSubQueries(final ADQLQuery query, Stack* {@link ADQLColumn}s of the GROUP BY may be aliases and so, they can not be checked * exactly as a normal column. *
* * @author Grégory Mantelet (ARI) * @version 1.4 (05/2017) * @since 1.4 */ private static class SearchColumnOutsideGroupByHandler extends SearchColumnHandler { @Override protected boolean goInto(final ADQLObject obj){ return !(obj instanceof ClauseADQL> && ((ClauseADQL>)obj).getName() != null && ((ClauseADQL>)obj).getName().equalsIgnoreCase("GROUP BY")) && super.goInto(obj); } } /** * Lets searching all tables. * * @author Grégory Mantelet (CDS) * @version 1.0 (07/2011) */ private static class SearchTableHandler extends SimpleSearchHandler { @Override public boolean match(final ADQLObject obj){ return obj instanceof ADQLTable; } } /** * Lets searching all wildcards. * * @author Grégory Mantelet (CDS) * @version 1.0 (09/2011) */ private static class SearchWildCardHandler extends SimpleSearchHandler { @Override public boolean match(final ADQLObject obj){ return (obj instanceof SelectAllColumns) && (((SelectAllColumns)obj).getAdqlTable() != null); } } /** * Lets searching column references. * * @author Grégory Mantelet (CDS) * @version 1.0 (11/2011) */ private static class SearchColReferenceHandler extends SimpleSearchHandler { @Override public boolean match(final ADQLObject obj){ return (obj instanceof ColumnReference); } } /** *Lets searching subqueries in every clause except the FROM one (hence the modification of the {@link #goInto(ADQLObject)}.
* ** Note: The function {@link #addMatch(ADQLObject, ADQLIterator)} has been modified in order to * not have the root search object (here: the main query) in the list of results. *
* * @author Grégory Mantelet (ARI) * @version 1.2 (12/2013) * @since 1.2 */ private static class SearchSubQueryHandler extends SimpleSearchHandler { @Override protected void addMatch(ADQLObject matchObj, ADQLIterator it){ if (it != null) super.addMatch(matchObj, it); } @Override protected boolean goInto(ADQLObject obj){ return super.goInto(obj) && !(obj instanceof FromContent); } @Override protected boolean match(ADQLObject obj){ return (obj instanceof ADQLQuery); } } /** * Let searching user defined functions. * * @author Grégory Mantelet (ARI) * @version 1.3 (10/2014) * @since 1.3 */ private static class SearchUDFHandler extends SimpleSearchHandler { @Override protected boolean match(ADQLObject obj){ return (obj instanceof UserDefinedFunction); } } /** *Let replacing every {@link DefaultUDF}s whose a {@link FunctionDef} is set by their corresponding {@link UserDefinedFunction} class.
* *Important note: * If the replacer can not be created using the class returned by {@link FunctionDef#getUDFClass()}, no replacement is performed. *
* * @author Grégory Mantelet (ARI) * @version 1.3 (02/2015) * @since 1.3 */ private static class ReplaceDefaultUDFHandler extends SimpleReplaceHandler { private final UnresolvedIdentifiersException errors; public ReplaceDefaultUDFHandler(final UnresolvedIdentifiersException errorsContainer){ errors = errorsContainer; } @Override protected boolean match(ADQLObject obj){ return (obj.getClass().getName().equals(DefaultUDF.class.getName())) && (((DefaultUDF)obj).getDefinition() != null) && (((DefaultUDF)obj).getDefinition().getUDFClass() != null); /* Note: detection of DefaultUDF is done on the exact class name rather than using "instanceof" in order to have only direct instances of DefaultUDF, * and not extensions of it. Indeed, DefaultUDFs are generally created automatically by the ADQLQueryFactory ; so, extensions of it can only be custom * UserDefinedFunctions. */ } @Override protected ADQLObject getReplacer(ADQLObject objToReplace) throws UnsupportedOperationException{ try{ // get the associated UDF class: Class extends UserDefinedFunction> udfClass = ((DefaultUDF)objToReplace).getDefinition().getUDFClass(); // get the constructor with a single parameter of type ADQLOperand[]: Constructor extends UserDefinedFunction> constructor = udfClass.getConstructor(ADQLOperand[].class); // create a new instance of this UDF class with the operands stored in the object to replace: return constructor.newInstance((Object)(((DefaultUDF)objToReplace).getParameters())); /* note: without this class, each item of the given array will be considered as a single parameter. */ }catch(Exception ex){ // IF NO INSTANCE CAN BE CREATED... // ...keep the error for further report: errors.addException(new UnresolvedFunctionException("Impossible to represent the function \"" + ((DefaultUDF)objToReplace).getName() + "\": the following error occured while creating this representation: \"" + ((ex instanceof InvocationTargetException) ? "[" + ex.getCause().getClass().getSimpleName() + "] " + ex.getCause().getMessage() : ex.getMessage()) + "\"", (DefaultUDF)objToReplace)); // ...keep the same object (i.e. no replacement): return objToReplace; } } } /** * Let searching geometrical functions. * * @author Grégory Mantelet (ARI) * @version 1.3 (10/2014) * @since 1.3 */ private static class SearchGeometryHandler extends SimpleSearchHandler { @Override protected boolean match(ADQLObject obj){ return (obj instanceof GeometryFunction); } } /** *Let searching all ADQL objects whose the type was not known while checking the syntax of the ADQL query. * These objects are {@link ADQLColumn}s and {@link UserDefinedFunction}s.
* *Important note: * Only {@link UnknownType} instances having an expected type equals to 'S' (or 's' ; for string) or 'N' (or 'n' ; for numeric) * are kept by this handler. Others are ignored. *
* * @author Grégory Mantelet (ARI) * @version 1.3 (10/2014) * @since 1.3 */ private static class SearchUnknownTypeHandler extends SimpleSearchHandler { @Override protected boolean match(ADQLObject obj){ if (obj instanceof UnknownType){ char expected = ((UnknownType)obj).getExpectedType(); return (expected == 'G' || expected == 'g' || expected == 'S' || expected == 's' || expected == 'N' || expected == 'n'); }else return false; } } /** * Let searching all explicit declaration of coordinate systems. * So, only {@link StringConstant} objects will be returned. * * @author Grégory Mantelet (ARI) * @version 1.3 (10/2014) * @since 1.3 */ private static class SearchCoordSysHandler extends SimpleSearchHandler { @Override protected boolean match(ADQLObject obj){ if (obj instanceof PointFunction || obj instanceof BoxFunction || obj instanceof CircleFunction || obj instanceof PolygonFunction) return (((GeometryFunction)obj).getCoordinateSystem() instanceof StringConstant); else return false; } @Override protected void addMatch(ADQLObject matchObj, ADQLIterator it){ results.add(((GeometryFunction)matchObj).getCoordinateSystem()); } } /** * Let searching all {@link RegionFunction}s. * * @author Grégory Mantelet (ARI) * @version 1.3 (10/2014) * @since 1.3 */ private static class SearchRegionHandler extends SimpleSearchHandler { @Override protected boolean match(ADQLObject obj){ if (obj instanceof RegionFunction) return (((RegionFunction)obj).getParameter(0) instanceof StringConstant); else return false; } } /** *Implement the binary search algorithm over a sorted array.
* ** The only difference with the standard implementation of Java is * that this object lets perform research with a different type * of object than the types of array items. *
* ** For that reason, the "compare" function must always be implemented. *
* * @author Grégory Mantelet (ARI) * @version 1.3 (10/2014) * * @paramSearch the given item in the given array.
* ** In case the given object matches to several items of the array, * this function will return the smallest index, pointing thus to the first * of all matches. *
* * @param searchItem Object for which a corresponding array item must be searched. * @param array Array in which the given object must be searched. * * @return The array index of the first item of all matches. */ public int search(final S searchItem, final T[] array){ s = 0; e = array.length - 1; while(s < e){ // middle of the sorted array: m = s + ((e - s) / 2); // compare the fct with the middle item of the array: comp = compare(searchItem, array[m]); // if the fct is after, trigger the inspection of the right part of the array: if (comp > 0) s = m + 1; // otherwise, the left part: else e = m; } if (s != e || compare(searchItem, array[s]) != 0) return -1; else return s; } /** * Compare the search item and the array item. * * @param searchItem Item whose a corresponding value must be found in the array. * @param arrayItem An item of the array. * * @return Negative value if searchItem is less than arrayItem, 0 if they are equals, or a positive value if searchItem is greater. */ protected abstract int compare(final S searchItem, final T arrayItem); } } src/adql/db/DBColumn.java 0000644 0001750 0001750 00000004472 13177122310 014153 0 ustar oles oles package adql.db; /* * This file is part of ADQLLibrary. * * ADQLLibrary 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. * * ADQLLibrary 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 ADQLLibrary. If not, seeDefinition of a valid target column.
* ** This column can be used in an ADQL query with its ADQL name ({@link #getADQLName()}) * and corresponds to a real column in the "database" with its DB name ({@link #getDBName()}). *
* * @author Grégory Mantelet (CDS;ARI) * @version 1.4 (07/2016) */ public interface DBColumn { /** * Gets the name of this column (without any prefix and double-quotes). * * @return Its ADQL name. */ public String getADQLName(); /** * Gets the name of this column in the "database". * * @return Its DB name. */ public String getDBName(); /** *Get the type of this column (as closed as possible from the "database" type).
* *Note: * The returned type should be as closed as possible from a type listed by the IVOA in the TAP protocol description into the section UPLOAD. *
* * @return Its type. * * @since 1.3 */ public DBType getDatatype(); /** * Gets the table which contains this {@link DBColumn}. * * @return Its table or null if no table is specified. */ public DBTable getTable(); /** * Makes a copy of this instance of {@link DBColumn}. * * @param dbName Its new DB name. * @param adqlName Its new ADQL name. * @param dbTable Its new table. * * @return A modified copy of this {@link DBColumn}. */ public DBColumn copy(final String dbName, final String adqlName, final DBTable dbTable); } src/adql/db/FunctionDef.java 0000644 0001750 0001750 00000065425 13177122310 014721 0 ustar oles oles package adql.db; /* * This file is part of ADQLLibrary. * * ADQLLibrary 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. * * ADQLLibrary 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 ADQLLibrary. If not, seeDefinition of any function that could be used in ADQL queries.
* ** A such definition can be built manually thanks to the different constructors of this class, * or by parsing a string function definition form using the static function {@link #parse(String)}. *
* ** The syntax of the expression expected by {@link #parse(String)} is the same as the one used to build * the string returned by {@link #toString()}. Here is this syntax: *
*{fctName}([{param1Name} {param1Type}, ...])[ -> {returnType}]* *
* A description of this function may be set thanks to the public class attribute {@link #description}. *
* * @author Grégory Mantelet (ARI) * @version 1.4 (08/2015) * * @since 1.3 */ public class FunctionDef implements ComparableNote:
* If true
, {@link #isString}, {@link #isNumeric}
* and {@link #isGeometry} are false
. Otherwise,
* at least one of these attributes is set to true
.
*
String representation of this function.
*The syntax of this representation is the following (items between brackets are optional):
*{fctName}([{param1Name} {param1Type}, ...])[ -> {returnType}]*/ private final String serializedForm; /**
String representation of this function dedicated to comparison with any function signature.
*This form is different from the serialized form on the following points:
*So the syntax of this form is the following (items between brackets are optional ; xxx is a string of 3 characters, each being either 0 or 1):
*{fctName}([xxx, ...])*/ private final String compareForm; /** *
Class of the {@link UserDefinedFunction} which must represent the UDF defined by this {@link FunctionDef} in the ADQL tree.
*This class MUST have a constructor with a single parameter of type {@link ADQLOperand}[].
*If this {@link FunctionDef} is defining an ordinary ADQL function, this attribute must be NULL. It is used only for user defined functions.
*/ private Class extends UserDefinedFunction> udfClass = null; /** *Definition of a function parameter.
* *This definition is composed of two items: the name and the type of the parameter.
* * @author Grégory Mantelet (ARI) * @version 1.4 (07/2015) * @since 1.3 */ public static final class FunctionParam { /** Parameter name. Ensured not null */ public final String name; /** Parameter type. Ensured not null */ public final DBType type; /** * Create a function parameter. * * @param paramName Name of the parameter to create. MUST NOT be NULL * @param paramType Type of the parameter to create. If NULL, an {@link DBDatatype#UNKNOWN UNKNOWN} type will be created and set instead. */ public FunctionParam(final String paramName, final DBType paramType){ if (paramName == null) throw new NullPointerException("Missing name! The function parameter can not be created."); this.name = paramName; this.type = (paramType == null) ? new DBType(DBDatatype.UNKNOWN) : paramType; } } /** *Create a function definition.
* *The created function will have no return type and no parameter.
* * @param fctName Name of the function. */ public FunctionDef(final String fctName){ this(fctName, null, null); } /** *Create a function definition.
* *The created function will have a return type (if the provided one is not null) and no parameter.
* * @param fctName Name of the function. * @param returnType Return type of the function. If NULL, this function will have no return type */ public FunctionDef(final String fctName, final DBType returnType){ this(fctName, returnType, null); } /** *Create a function definition.
* *The created function will have no return type and some parameters (except if the given array is NULL or empty).
* * @param fctName Name of the function. * @param params Parameters of this function. If NULL or empty, this function will have no parameter. */ public FunctionDef(final String fctName, final FunctionParam[] params){ this(fctName, null, params); } public FunctionDef(final String fctName, final DBType returnType, final FunctionParam[] params){ // Set the name: if (fctName == null) throw new NullPointerException("Missing name! Can not create this function definition."); this.name = fctName; // Set the parameters: this.params = (params == null || params.length == 0) ? null : params; this.nbParams = (params == null) ? 0 : params.length; // Set the return type; this.returnType = (returnType != null) ? returnType : new DBType(DBDatatype.UNKNOWN); isUnknown = this.returnType.isUnknown(); isNumeric = this.returnType.isNumeric(); isString = this.returnType.isString(); isGeometry = this.returnType.isGeometry(); // Serialize in Strings (serializedForm and compareForm) this function definition: StringBuffer bufSer = new StringBuffer(name), bufCmp = new StringBuffer(name.toLowerCase()); bufSer.append('('); for(int i = 0; i < nbParams; i++){ bufSer.append(params[i].name).append(' ').append(params[i].type); bufCmp.append(params[i].type.isNumeric() ? '1' : '0').append(params[i].type.isString() ? '1' : '0').append(params[i].type.isGeometry() ? '1' : '0'); if (i + 1 < nbParams) bufSer.append(", "); } bufSer.append(')'); if (returnType != null) bufSer.append(" -> ").append(returnType); serializedForm = bufSer.toString(); compareForm = bufCmp.toString(); } /** * Tell whether this function returns a numeric. * * @return true if this function returns a numeric, false otherwise. */ public final boolean isNumeric(){ return isNumeric; } /** * Tell whether this function returns a string. * * @return true if this function returns a string, false otherwise. */ public final boolean isString(){ return isString; } /** * Tell whether this function returns a geometry. * * @return true if this function returns a geometry, false otherwise. */ public final boolean isGeometry(){ return isGeometry; } /** *Tell whether this function returns an unknown type.
* *
* If this function returns true
, {@link #isNumeric()}, {@link #isString()} and {@link #isGeometry()}
* MUST ALL return false
. Otherwise, one of these 3 last functions MUST return true
.
*
Get the class of the {@link UserDefinedFunction} able to represent the function defined here in an ADQL tree.
* *Note:
* This getter should return always NULL if the function defined here is not a user defined function.
*
* However, if this {@link FunctionDef} is defining a user defined function and this function returns NULL,
* the library will create on the fly a {@link DefaultUDF} corresponding to this definition when needed.
* Indeed this UDF class is useful only if the translation from ADQL (to SQL for instance) of the defined
* function has a different signature (e.g. a different name) in the target language (e.g. SQL).
*
Set the class of the {@link UserDefinedFunction} able to represent the function defined here in an ADQL tree.
* *Note:
* If this {@link FunctionDef} defines an ordinary ADQL function - and not a user defined function - no class should be set here.
*
* However, if it defines a user defined function, there is no obligation to set a UDF class. It is useful only if the translation
* from ADQL (to SQL for instance) of the function has a different signature (e.g. a different name) in the target language (e.g. SQL).
* If the signature is the same, there is no need to set a UDF class ; a {@link DefaultUDF} will be created on the fly by the library
* when needed if it turns out that no UDF class is set.
*
Let parsing the serialized form of a function definition.
* *The expected syntax is (items between brackets are optional):
*{fctName}([{param1Name} {param1Type}, ...])[ -> {returnType}]* *
* This function must be able to parse functions as defined by TAPRegExt (section 2.3). * Hence, allowed parameter types and return types should be one of the types listed by the UPLOAD section of the TAP recommendation document. * These types are listed in the enumeration object {@link DBDatatype}. * However, other types should be accepted like the common database types...but it should be better to not rely on that * since the conversion of those types to TAP types should not be exactly what is expected (because depending from the used DBMS); * a default interpretation of database types is nevertheless processed by this parser. *
* * @param strDefinition Serialized function definition to parse. * * @return The object representation of the given string definition. * * @throws ParseException If the given string has a wrong syntax or uses unknown types. */ public static FunctionDef parse(final String strDefinition) throws ParseException{ if (strDefinition == null) throw new NullPointerException("Missing string definition to build a FunctionDef!"); // Check the global syntax of the function definition: Matcher m = fctPattern.matcher(strDefinition); if (m.matches()){ // Get the function name: String fctName = m.group(1); // Parse and get the return type: DBType returnType = null; if (m.group(3) != null){ returnType = parseType(m.group(5), (m.group(7) == null) ? DBType.NO_LENGTH : Integer.parseInt(m.group(7))); if (returnType == null){ returnType = new DBType(DBDatatype.UNKNOWN); returnType.type.setCustomType(m.group(4)); } } // Get the parameters, if any: String paramsList = m.group(2); FunctionParam[] params = null; if (paramsList != null && paramsList.trim().length() > 0){ // Check the syntax of the parameters' list: if (!paramsList.matches(fctParamsRegExp)) throw new ParseException("Wrong parameters syntax! Expected syntax: \"(Compare this function definition with the given ADQL function item.
* ** The comparison is done only on the function name and on rough type of the parameters. * "Rough type" means here that just the kind of type is tested: numeric, string or geometry. * Anyway, the return type is never tested by this function, since such information is usually * not part of a function signature. *
* *The notions of "greater" and "less" are defined here according to the three following test steps:
*Note:
* If one of the tested types (i.e. parameters types) is unknown, the match should return 0 (i.e. equality).
* The notion of "unknown" is different in function of the tested item. A {@link DBType} is unknown if its function
* {@link DBType#isUnknown()} returns true
; thus, its other functions such as {@link DBType#isNumeric()} will
* return false
. On the contrary, an {@link ADQLOperand} does not have any isUnknown()
* function. However, when the type of a such is unknown, all its functions isNumeric(), isString() and isGeometry() return
* true
.
*
Builds a default {@link DBTable} with the given DB name.
* *With this constructor: ADQL name = DB name.
* *Note: The table name can be prefixed by a schema and a catalog: t1 or schema1.t1 or cat1.schema1.t2
* * @param dbName Database name (it will be also used as ADQL table name). * * @see #DefaultDBTable(String, String) */ public DefaultDBTable(final String dbName){ this(dbName, null); } /** *Builds a default {@link DBTable} with the given DB and ADQL names.
* *Note: The table names can be prefixed by a schema and a catalog: t1 or schema1.t1 or cat1.schema1.t2
* * @param dbName Database name. * @param adqlName Name used in ADQL queries. */ public DefaultDBTable(final String dbName, final String adqlName){ // DB names: String[] names = splitTableName(dbName); if (names[2] == null || names[2].length() == 0) throw new NullPointerException("Missing DB name !"); else this.dbName = names[2]; this.dbSchemaName = names[1]; this.dbCatalogName = names[0]; // ADQL names: names = splitTableName(adqlName); if (names[2] == null || names[2].length() == 0){ this.adqlName = this.dbName; this.adqlSchemaName = this.dbSchemaName; this.adqlCatalogName = this.dbCatalogName; }else{ this.adqlName = names[2]; this.adqlSchemaName = names[1]; this.adqlCatalogName = names[0]; } } /** * Builds default {@link DBTable} with a DB catalog, schema and table names. * * @param dbCatName Database catalog name (it will be also used as ADQL catalog name). * @param dbSchemName Database schema name (it will be also used as ADQL schema name). * @param dbName Database table name (it will be also used as ADQL table name). * * @see #DefaultDBTable(String, String, String, String, String, String) */ public DefaultDBTable(final String dbCatName, final String dbSchemName, final String dbName){ this(dbCatName, null, dbSchemName, null, dbName, null); } /** * Builds default {@link DBTable} with the DB and ADQL names for the catalog, schema and table. * * @param dbCatName Database catalog name. * @param adqlCatName Catalog name used in ADQL queries. * If NULL, it will be set to dbCatName. * @param dbSchemName Database schema name. * @param adqlSchemName Schema name used in ADQL queries. * If NULL, it will be set to dbSchemName. * @param dbName Database table name. * @param adqlName Table name used in ADQL queries. * If NULL, it will be set to dbName. */ public DefaultDBTable(final String dbCatName, final String adqlCatName, final String dbSchemName, final String adqlSchemName, final String dbName, final String adqlName){ if (dbName == null || dbName.length() == 0) throw new NullPointerException("Missing DB name !"); this.dbName = dbName; this.adqlName = (adqlName == null) ? dbName : adqlName; dbSchemaName = dbSchemName; adqlSchemaName = (adqlSchemName == null) ? dbSchemName : adqlSchemName; dbCatalogName = dbCatName; adqlCatalogName = (adqlCatName == null) ? dbCatName : adqlCatName; } @Override public final String getDBName(){ return dbName; } @Override public final String getDBSchemaName(){ return dbSchemaName; } @Override public final String getDBCatalogName(){ return dbCatalogName; } @Override public final String getADQLName(){ return adqlName; } public void setADQLName(final String name){ adqlName = (name != null) ? name : dbName; } @Override public final String getADQLSchemaName(){ return adqlSchemaName; } public void setADQLSchemaName(final String name){ adqlSchemaName = (name != null) ? name : dbSchemaName; } @Override public final String getADQLCatalogName(){ return adqlCatalogName; } public void setADQLCatalogName(final String name){ adqlName = (name != null) ? null : dbName; } /** *Case sensitive !
*Research optimized for researches by ADQL name.
* * @see adql.db.DBTable#getColumn(java.lang.String, boolean) */ @Override public DBColumn getColumn(String colName, boolean byAdqlName){ if (byAdqlName) return columns.get(colName); else{ for(DBColumn col : columns.values()){ if (col.getDBName().equals(colName)) return col; } return null; } } public boolean hasColumn(String colName, boolean byAdqlName){ return (getColumn(colName, byAdqlName) != null); } @Override public IteratorJoin the last 3 items of the given string array with a dot ('.'). * These three parts should be: [0]=catalog name, [1]=schema name, [2]=table name.
* ** If the array contains less than 3 items, all the given items will be though joined. * However, if it contains more than 3 items, only the three last items will be. *
* *A null item will be written as an empty string (string of length 0 ; "").
* ** In the case the first and the third items are not null, but the second is null, the final string will contain in the middle two dots. * Example: if the array is {"cat", NULL, "table"}, then the joined string will be: "cat..table". *
* * @param nameParts String items to join. * * @return A string joining the 3 last string items of the given array, * or an empty string if the given array is NULL. * * @since 1.3 */ public static final String joinTableName(final String[] nameParts){ if (nameParts == null) return ""; StringBuffer str = new StringBuffer(); boolean empty = true; for(int i = (nameParts.length <= 3) ? 0 : (nameParts.length - 3); i < nameParts.length; i++){ if (!empty) str.append('.'); String part = (nameParts[i] == null) ? null : nameParts[i].trim(); if (part != null && part.length() > 0){ str.append(part); empty = false; } } return str.toString(); } @Override public DBTable copy(String dbName, String adqlName){ dbName = (dbName == null) ? joinTableName(new String[]{dbCatalogName,dbSchemaName,this.dbName}) : dbName; adqlName = (adqlName == null) ? joinTableName(new String[]{adqlCatalogName,adqlSchemaName,this.adqlName}) : adqlName; DefaultDBTable copy = new DefaultDBTable(dbName, adqlName); for(DBColumn col : this){ if (col instanceof DBCommonColumn) copy.addColumn(new DBCommonColumn((DBCommonColumn)col, col.getDBName(), col.getADQLName())); else copy.addColumn(col.copy(col.getDBName(), col.getADQLName(), copy)); } return copy; } } src/adql/db/SearchTableApi.java 0000644 0001750 0001750 00000002562 13177122310 015315 0 ustar oles oles package adql.db; /* * This file is part of ADQLLibrary. * * ADQLLibrary 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. * * ADQLLibrary 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 ADQLLibrary. If not, see* It overwrites the translation of mathematical functions whose some have * a different name or signature. Besides, it is also implementing the * translation of the geometrical functions. However, it does not really * translate them. It is just returning the ADQL expression (by calling * {@link #getDefaultADQLFunction(ADQLFunction)}). And so, of course, the * execution of a SQL query containing geometrical functions and translated * using this translator will not work. It is just a default implementation in * case there is no interest of these geometrical functions. *
* *Important: * The geometrical functions are translated exactly as in ADQL. * You will probably need to extend this translator to correctly manage the * geometrical functions. An extension is already available for PgSphere: * {@link PgSphereTranslator}. *
* * @author Grégory Mantelet (CDS;ARI) * @version 1.4 (08/2016) * * @see PgSphereTranslator */ public class PostgreSQLTranslator extends JDBCTranslator { /**Indicate the case sensitivity to apply to each SQL identifier (only SCHEMA, TABLE and COLUMN).
* *Note: * In this implementation, this field is set by the constructor and never modified elsewhere. * It would be better to never modify it after the construction in order to keep a certain consistency. *
*/ protected byte caseSensitivity = 0x00; /** * Builds a PostgreSQLTranslator which always translates in SQL all identifiers (schema, table and column) in a case sensitive manner ; * in other words, schema, table and column names will be surrounded by double quotes in the SQL translation. */ public PostgreSQLTranslator(){ caseSensitivity = 0x0F; } /** * Builds a PostgreSQLTranslator which always translates in SQL all identifiers (schema, table and column) in the specified case sensitivity ; * in other words, schema, table and column names will all be surrounded or not by double quotes in the SQL translation. * * @param allCaseSensitive true to translate all identifiers in a case sensitive manner (surrounded by double quotes), false for case insensitivity. */ public PostgreSQLTranslator(final boolean allCaseSensitive){ caseSensitivity = allCaseSensitive ? (byte)0x0F : (byte)0x00; } /** * Builds a PostgreSQLTranslator which will always translate in SQL identifiers with the defined case sensitivity. * * @param catalog true to translate catalog names with double quotes (case sensitive in the DBMS), false otherwise. * @param schema true to translate schema names with double quotes (case sensitive in the DBMS), false otherwise. * @param table true to translate table names with double quotes (case sensitive in the DBMS), false otherwise. * @param column true to translate column names with double quotes (case sensitive in the DBMS), false otherwise. */ public PostgreSQLTranslator(final boolean catalog, final boolean schema, final boolean table, final boolean column){ caseSensitivity = IdentifierField.CATALOG.setCaseSensitive(caseSensitivity, catalog); caseSensitivity = IdentifierField.SCHEMA.setCaseSensitive(caseSensitivity, schema); caseSensitivity = IdentifierField.TABLE.setCaseSensitive(caseSensitivity, table); caseSensitivity = IdentifierField.COLUMN.setCaseSensitive(caseSensitivity, column); } @Override public boolean isCaseSensitive(final IdentifierField field){ return field == null ? false : field.isCaseSensitive(caseSensitivity); } @Override public String translate(StringConstant strConst) throws TranslationException{ // Deal with the special escaping syntax of Postgres: /* A string containing characters to escape must be prefixed by an E. * Without this prefix, Potsgres does not escape the concerned characters and * consider backslashes as normal characters. * For instance: E'foo\tfoo2'. */ if (strConst.getValue() != null && strConst.getValue().contains("\\")) return "E'" + strConst.getValue() + "'"; else return super.translate(strConst); } @Override public String translate(MathFunction fct) throws TranslationException{ switch(fct.getType()){ case LOG: return "ln(" + ((fct.getNbParameters() >= 1) ? "CAST(" + translate(fct.getParameter(0)) + " AS numeric)" : "") + ")"; case LOG10: return "log(10, " + ((fct.getNbParameters() >= 1) ? "CAST(" + translate(fct.getParameter(0)) + " AS numeric)" : "") + ")"; case RAND: return "random()"; case TRUNCATE: if (fct.getNbParameters() >= 2) return "trunc(CAST(" + translate(fct.getParameter(0)) + " AS numeric), " + translate(fct.getParameter(1)) + ")"; else if (fct.getNbParameters() >= 1) return "trunc(CAST(" + translate(fct.getParameter(0)) + " AS numeric)" + ")"; else return "trunc()"; case ROUND: if (fct.getNbParameters() >= 2) return "round(CAST(" + translate(fct.getParameter(0)) + " AS numeric), " + translate(fct.getParameter(1)) + ")"; else if (fct.getNbParameters() >= 1) return "round(CAST(" + translate(fct.getParameter(0)) + " AS numeric))"; else return "round()"; case PI: return getDefaultADQLFunction(fct); default: String sql = fct.getName() + "("; for(int i = 0; i < fct.getNbParameters(); i++) sql += ((i == 0) ? "" : ", ") + "CAST(" + translate(fct.getParameter(i)) + " AS numeric)"; return sql + ")"; } } @Override public String translate(ExtractCoord extractCoord) throws TranslationException{ return getDefaultADQLFunction(extractCoord); } @Override public String translate(ExtractCoordSys extractCoordSys) throws TranslationException{ return getDefaultADQLFunction(extractCoordSys); } @Override public String translate(AreaFunction areaFunction) throws TranslationException{ return getDefaultADQLFunction(areaFunction); } @Override public String translate(CentroidFunction centroidFunction) throws TranslationException{ return getDefaultADQLFunction(centroidFunction); } @Override public String translate(DistanceFunction fct) throws TranslationException{ return getDefaultADQLFunction(fct); } @Override public String translate(ContainsFunction fct) throws TranslationException{ return getDefaultADQLFunction(fct); } @Override public String translate(IntersectsFunction fct) throws TranslationException{ return getDefaultADQLFunction(fct); } @Override public String translate(BoxFunction box) throws TranslationException{ return getDefaultADQLFunction(box); } @Override public String translate(CircleFunction circle) throws TranslationException{ return getDefaultADQLFunction(circle); } @Override public String translate(PointFunction point) throws TranslationException{ return getDefaultADQLFunction(point); } @Override public String translate(PolygonFunction polygon) throws TranslationException{ return getDefaultADQLFunction(polygon); } @Override public String translate(RegionFunction region) throws TranslationException{ return getDefaultADQLFunction(region); } @Override public DBType convertTypeFromDB(final int dbmsType, final String rawDbmsTypeName, String dbmsTypeName, final String[] params){ // If no type is provided return VARCHAR: if (dbmsTypeName == null || dbmsTypeName.trim().length() == 0) return null; // Put the dbmsTypeName in lower case for the following comparisons: dbmsTypeName = dbmsTypeName.toLowerCase(); // Extract the length parameter (always the first one): int lengthParam = DBType.NO_LENGTH; if (params != null && params.length > 0){ try{ lengthParam = Integer.parseInt(params[0]); }catch(NumberFormatException nfe){} } // SMALLINT if (dbmsTypeName.equals("smallint") || dbmsTypeName.equals("int2") || dbmsTypeName.equals("smallserial") || dbmsTypeName.equals("serial2") || dbmsTypeName.equals("boolean") || dbmsTypeName.equals("bool")) return new DBType(DBDatatype.SMALLINT); // INTEGER else if (dbmsTypeName.equals("integer") || dbmsTypeName.equals("int") || dbmsTypeName.equals("int4") || dbmsTypeName.equals("serial") || dbmsTypeName.equals("serial4")) return new DBType(DBDatatype.INTEGER); // BIGINT else if (dbmsTypeName.equals("bigint") || dbmsTypeName.equals("int8") || dbmsTypeName.equals("bigserial") || dbmsTypeName.equals("bigserial8")) return new DBType(DBDatatype.BIGINT); // REAL else if (dbmsTypeName.equals("real") || dbmsTypeName.equals("float4")) return new DBType(DBDatatype.REAL); // DOUBLE else if (dbmsTypeName.equals("double precision") || dbmsTypeName.equals("float8") || dbmsTypeName.equals("numeric")) return new DBType(DBDatatype.DOUBLE); // BINARY else if (dbmsTypeName.equals("bit")) return new DBType(DBDatatype.BINARY, lengthParam); // VARBINARY else if (dbmsTypeName.equals("bit varying") || dbmsTypeName.equals("varbit")) return new DBType(DBDatatype.VARBINARY, lengthParam); // CHAR else if (dbmsTypeName.equals("char") || dbmsTypeName.equals("character")) return new DBType(DBDatatype.CHAR, lengthParam); // VARCHAR else if (dbmsTypeName.equals("varchar") || dbmsTypeName.equals("character varying")) return new DBType(DBDatatype.VARCHAR, lengthParam); // BLOB else if (dbmsTypeName.equals("bytea")) return new DBType(DBDatatype.BLOB); // CLOB else if (dbmsTypeName.equals("text")) return new DBType(DBDatatype.CLOB); // TIMESTAMP else if (dbmsTypeName.equals("timestamp") || dbmsTypeName.equals("timestamptz") || dbmsTypeName.equals("time") || dbmsTypeName.equals("timetz") || dbmsTypeName.equals("date")) return new DBType(DBDatatype.TIMESTAMP); // Default: else return null; } @Override public String convertTypeToDB(final DBType type){ if (type == null) return "VARCHAR"; switch(type.type){ case SMALLINT: case INTEGER: case REAL: case BIGINT: case CHAR: case VARCHAR: case TIMESTAMP: return type.type.toString(); case DOUBLE: return "DOUBLE PRECISION"; case BINARY: case VARBINARY: return "bytea"; case BLOB: return "bytea"; case CLOB: return "TEXT"; case POINT: case REGION: default: return "VARCHAR"; } } @Override public Region translateGeometryFromDB(final Object jdbcColValue) throws ParseException{ throw new ParseException("Unsupported geometrical value! The value \"" + jdbcColValue + "\" can not be parsed as a region."); } @Override public Object translateGeometryToDB(final Region region) throws ParseException{ throw new ParseException("Geometries can not be uploaded in the database in this implementation!"); } } src/adql/translator/JDBCTranslator.java 0000644 0001750 0001750 00000104142 13226141346 017070 0 ustar oles oles package adql.translator; /* * This file is part of ADQLLibrary. * * ADQLLibrary 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. * * ADQLLibrary 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 ADQLLibrary. If not, see* It is already able to translate all SQL standard features, but lets abstract * the translation of all geometrical functions. So, this translator must be * extended as {@link PostgreSQLTranslator}, {@link PgSphereTranslator}, * {@link MySQLTranslator} and {@link SQLServerTranslator} are doing. *
* *Note: * Its default implementation of the SQL syntax has been inspired by the * PostgreSQL one. However, it should work also with other DBMS, although some * translations might be needed (as it is has been done for PostgreSQL about * the mathematical functions). *
* ** In ADQL and in SQL, it is possible to tell the parser to respect the exact * case or not of an identifier (schema, table or column name) by surrounding * it with double quotes. However ADQL identifiers and SQL ones may be * different. In that way, the case sensitivity specified in ADQL on the * different identifiers can not be kept in SQL. That's why this translator * lets specify a general rule on which types of SQL identifier must be double * quoted. This can be done by implementing the abstract function * {@link #isCaseSensitive(IdentifierField)}. The functions translating column * and table names will call this function in order to surround the identifiers * by double quotes or not. So, be careful if you want to override the * functions translating columns and tables! *
* ** The default behavior of this translator is to translate the ADQL "TOP" into * the SQL "LIMIT" at the end of the query. This is ok for some DBMS, but not * all. So, if your DBMS does not know the "LIMIT" keyword, you should override * the function translating the whole query: {@link #translate(ADQLQuery)}. * Here is its current implementation: *
** StringBuffer sql = new StringBuffer(translate(query.getSelect())); * sql.append("\nFROM ").append(translate(query.getFrom())); * if (!query.getWhere().isEmpty()) * sql.append('\n').append(translate(query.getWhere())); * if (!query.getGroupBy().isEmpty()) * sql.append('\n').append(translate(query.getGroupBy())); * if (!query.getHaving().isEmpty()) * sql.append('\n').append(translate(query.getHaving())); * if (!query.getOrderBy().isEmpty()) * sql.append('\n').append(translate(query.getOrderBy())); * if (query.getSelect().hasLimit()) * sql.append("\nLimit ").append(query.getSelect().getLimit()); * return sql.toString(); ** *
* All ADQL functions are by default not translated. Consequently, the SQL * translation is actually the ADQL expression. Generally the ADQL expression * is generic enough. However some mathematical functions may need to be * translated differently. For instance {@link PostgreSQLTranslator} is * translating differently: LOG, LOG10, RAND and TRUNC. *
* *Note: * Geometrical regions and types have not been managed here. They stay abstract * because it is obviously impossible to have a generic translation and * conversion ; it totally depends from the database system. *
* ** The FROM clause is translated into SQL as written in ADQL. There is no * differences except the identifiers that are replaced. The tables' aliases * and their case sensitivity are kept like in ADQL. *
* * @author Grégory Mantelet (ARI) * @version 1.4 (01/2018) * @since 1.4 * * @see PostgreSQLTranslator * @see PgSphereTranslator * @see MySQLTranslator * @see SQLServerTranslator */ public abstract class JDBCTranslator implements ADQLTranslator { /** *Tell whether the specified identifier MUST be translated so that being interpreted case sensitively or not. * By default, an identifier that must be translated with case sensitivity will be surrounded by double quotes. * But, if this function returns FALSE, the SQL name will be written just as given in the metadata, without double quotes.
* *WARNING: * An {@link IdentifierField} object can be a SCHEMA, TABLE, COLUMN and ALIAS. However, in this translator, * aliases are translated like in ADQL (so, with the same case sensitivity specification as in ADQL). * So, this function will never be used to know the case sensitivity to apply to an alias. It is then * useless to write a special behavior for the ALIAS value. *
* * @param field The identifier whose the case sensitive to apply is asked. * * @return true if the specified identifier must be translated case sensitivity, false otherwise (included if ALIAS or NULL). */ public abstract boolean isCaseSensitive(final IdentifierField field); /** *Get the qualified DB name of the schema containing the given table.
* *Note: * This function will, by default, add double quotes if the schema name must be case sensitive in the SQL query. * This information is provided by {@link #isCaseSensitive(IdentifierField)}. *
* * @param table A table of the schema whose the qualified DB name is asked. * * @return The qualified (with DB catalog name prefix if any, and with double quotes if needed) DB schema name, * or an empty string if there is no schema or no DB name. */ public String getQualifiedSchemaName(final DBTable table){ if (table == null || table.getDBSchemaName() == null) return ""; StringBuffer buf = new StringBuffer(); if (table.getDBCatalogName() != null) appendIdentifier(buf, table.getDBCatalogName(), IdentifierField.CATALOG).append('.'); appendIdentifier(buf, table.getDBSchemaName(), IdentifierField.SCHEMA); return buf.toString(); } /** *Get the qualified DB name of the given table.
* *Note: * This function will, by default, add double quotes if the table name must be case sensitive in the SQL query. * This information is provided by {@link #isCaseSensitive(IdentifierField)}. *
* * @param table The table whose the qualified DB name is asked. * * @return The qualified (with DB catalog and schema prefix if any, and with double quotes if needed) DB table name, * or an empty string if the given table is NULL or if there is no DB name. * * @see #getTableName(DBTable, boolean) */ public String getQualifiedTableName(final DBTable table){ return getTableName(table, true); } /** *Get the DB name of the given table. * The second parameter lets specify whether the table name must be prefixed by the qualified schema name or not.
* *Note: * This function will, by default, add double quotes if the table name must be case sensitive in the SQL query. * This information is provided by {@link #isCaseSensitive(IdentifierField)}. *
* * @param table The table whose the DB name is asked. * @param withSchema true if the qualified schema name must prefix the table name, false otherwise. * * @return The DB table name (prefixed by the qualified schema name if asked, and with double quotes if needed), * or an empty string if the given table is NULL or if there is no DB name. * * @since 2.0 */ public String getTableName(final DBTable table, final boolean withSchema){ if (table == null) return ""; StringBuffer buf = new StringBuffer(); if (withSchema){ buf.append(getQualifiedSchemaName(table)); if (buf.length() > 0) buf.append('.'); } appendIdentifier(buf, table.getDBName(), IdentifierField.TABLE); return buf.toString(); } /** *Get the DB name of the given column
* *Note: * This function will, by default, add double quotes if the column name must be case sensitive in the SQL query. * This information is provided by {@link #isCaseSensitive(IdentifierField)}. *
* *Caution: * The given column may be NULL and in this case an empty string will be returned. * But if the given column is not NULL, its DB name MUST NOT BE NULL! *
* * @param column The column whose the DB name is asked. * * @return The DB column name (with double quotes if needed), * or an empty string if the given column is NULL. */ public String getColumnName(final DBColumn column){ return (column == null) ? "" : appendIdentifier(new StringBuffer(), column.getDBName(), IdentifierField.COLUMN).toString(); } /** *Appends the given identifier in the given StringBuffer.
* *
* This function just call {@link #appendIdentifier(StringBuffer, String, boolean)}
* with the same 2 first parameters. The third one is the result of:
* {@link #isCaseSensitive(IdentifierField) isCaseSensitive(field)}
.
*
Note:
* In order to keep a consistent output of the appendIdentifier(...)
functions,
* this function can not be overwritten ; it is just a shortcut function.
*
Implementation note:
* This function just calls {@link #getDefaultADQLList(ADQLList, boolean)}
* with the given list in first parameter and true
in second
* one. In other words, this function always prefixes the list items by
* the list name.
*
Convert any type provided by the ADQL/TAP library into a type understandable by a JDBC driver.
* *Note: * The returned DBMS type may contain some parameters between brackets. *
* * @param type The ADQL/TAP library's type to convert. * * @return The corresponding DBMS type or NULL if the specified type is unknown. */ public abstract String convertTypeToDB(final DBType type); /** *Parse the given JDBC column value as a geometry object and convert it into a {@link Region}.
* *Note: * Generally the returned object will be used to get its STC-S expression. *
* *Note: * If the given column value is NULL, NULL will be returned. *
* *Important note: * This function is called ONLY for value of columns flagged as geometries by * {@link #convertTypeFromDB(int, String, String, String[])}. So the value should always * be of the expected type and format. However, if it turns out that the type is wrong * and that the conversion is finally impossible, this function SHOULD throw a * {@link ParseException}. *
* * @param jdbcColValue A JDBC column value (returned by ResultSet.getObject(int)). * * @return The corresponding {@link Region} if the given value is a geometry. * * @throws ParseException If the given object is not a geometrical object * or can not be transformed into a {@link Region} object. */ public abstract Region translateGeometryFromDB(final Object jdbcColValue) throws ParseException; /** *Convert the given STC region into a DB column value.
* *Note: * This function is used only by the UPLOAD feature, to import geometries provided as STC-S expression in * a VOTable document inside a DB column. *
* *Note: * If the given region is NULL, NULL will be returned. *
* * @param region The region to store in the DB. * * @return The corresponding DB column object. * * @throws ParseException If the given STC Region can not be converted into a DB object. */ public abstract Object translateGeometryToDB(final Region region) throws ParseException; } src/adql/translator/TranslationException.java 0000644 0001750 0001750 00000002430 13177122310 020461 0 ustar oles oles package adql.translator; /* * This file is part of ADQLLibrary. * * ADQLLibrary 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. * * ADQLLibrary 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 ADQLLibrary. If not, see* Actually only the geometrical functions and types are translated in this * class. The other functions are managed by {@link PostgreSQLTranslator}. *
* * @author Grégory Mantelet (CDS;ARI) * @version 1.4 (07/2017) */ public class PgSphereTranslator extends PostgreSQLTranslator { /** Angle between two points generated while transforming a circle into a polygon. * This angle is computed by default to get at the end a polygon of 32 points. * @see #circleToPolygon(double[], double) * @since 1.3 */ protected static double ANGLE_CIRCLE_TO_POLYGON = 2 * Math.PI / 32; /** * Builds a PgSphereTranslator which always translates in SQL all identifiers (schema, table and column) in a case sensitive manner ; * in other words, schema, table and column names will be surrounded by double quotes in the SQL translation. * * @see PostgreSQLTranslator#PostgreSQLTranslator() */ public PgSphereTranslator(){ super(); } /** * Builds a PgSphereTranslator which always translates in SQL all identifiers (schema, table and column) in the specified case sensitivity ; * in other words, schema, table and column names will all be surrounded or not by double quotes in the SQL translation. * * @param allCaseSensitive true to translate all identifiers in a case sensitive manner (surrounded by double quotes), false for case insensitivity. * * @see PostgreSQLTranslator#PostgreSQLTranslator(boolean) */ public PgSphereTranslator(boolean allCaseSensitive){ super(allCaseSensitive); } /** * Builds a PgSphereTranslator which will always translate in SQL identifiers with the defined case sensitivity. * * @param catalog true to translate catalog names with double quotes (case sensitive in the DBMS), false otherwise. * @param schema true to translate schema names with double quotes (case sensitive in the DBMS), false otherwise. * @param table true to translate table names with double quotes (case sensitive in the DBMS), false otherwise. * @param column true to translate column names with double quotes (case sensitive in the DBMS), false otherwise. * * @see PostgreSQLTranslator#PostgreSQLTranslator(boolean, boolean, boolean, boolean) */ public PgSphereTranslator(boolean catalog, boolean schema, boolean table, boolean column){ super(catalog, schema, table, column); } @Override public String translate(PointFunction point) throws TranslationException{ StringBuffer str = new StringBuffer("spoint("); str.append("radians(").append(translate(point.getCoord1())).append("),"); str.append("radians(").append(translate(point.getCoord2())).append("))"); return str.toString(); } @Override public String translate(CircleFunction circle) throws TranslationException{ StringBuffer str = new StringBuffer("scircle("); str.append("spoint(radians(").append(translate(circle.getCoord1())).append("),"); str.append("radians(").append(translate(circle.getCoord2())).append(")),"); str.append("radians(").append(translate(circle.getRadius())).append("))"); return str.toString(); } @Override public String translate(BoxFunction box) throws TranslationException{ StringBuffer str = new StringBuffer("sbox("); str.append("spoint(").append("radians(").append(translate(box.getCoord1())).append("-(").append(translate(box.getWidth())).append("/2.0)),"); str.append("radians(").append(translate(box.getCoord2())).append("-(").append(translate(box.getHeight())).append("/2.0))),"); str.append("spoint(").append("radians(").append(translate(box.getCoord1())).append("+(").append(translate(box.getWidth())).append("/2.0)),"); str.append("radians(").append(translate(box.getCoord2())).append("+(").append(translate(box.getHeight())).append("/2.0))))"); return str.toString(); } @Override public String translate(PolygonFunction polygon) throws TranslationException{ try{ StringBuffer str = new StringBuffer("spoly('{'"); if (polygon.getNbParameters() > 2){ PointFunction point = new PointFunction(polygon.getCoordinateSystem(), polygon.getParameter(1), polygon.getParameter(2)); str.append(" || ").append(translate(point)); for(int i = 3; i < polygon.getNbParameters() && i + 1 < polygon.getNbParameters(); i += 2){ point.setCoord1(polygon.getParameter(i)); point.setCoord2(polygon.getParameter(i + 1)); str.append(" || ',' || ").append(translate(point)); } } str.append(" || '}')"); return str.toString(); }catch(Exception e){ e.printStackTrace(); throw new TranslationException(e); } } @Override public String translate(ExtractCoord extractCoord) throws TranslationException{ StringBuffer str = new StringBuffer("degrees("); if (extractCoord.getName().equalsIgnoreCase("COORD1")) str.append("long("); else str.append("lat("); str.append(translate(extractCoord.getParameter(0))).append("))"); return str.toString(); } @Override public String translate(DistanceFunction fct) throws TranslationException{ StringBuffer str = new StringBuffer("degrees("); str.append(translate(fct.getP1())).append(" <-> ").append(translate(fct.getP2())).append(")"); return str.toString(); } @Override public String translate(AreaFunction areaFunction) throws TranslationException{ StringBuffer str = new StringBuffer("degrees(degrees(area("); str.append(translate(areaFunction.getParameter())).append(")))"); return str.toString(); } @Override public String translate(CentroidFunction centroidFunction) throws TranslationException{ StringBuffer str = new StringBuffer("center("); str.append(translate(centroidFunction.getParameter(0))).append(")"); return str.toString(); } @Override public String translate(ContainsFunction fct) throws TranslationException{ StringBuffer str = new StringBuffer("("); str.append(translate(fct.getLeftParam())).append(" @ ").append(translate(fct.getRightParam())).append(")"); return str.toString(); } @Override public String translate(IntersectsFunction fct) throws TranslationException{ StringBuffer str = new StringBuffer("("); str.append(translate(fct.getLeftParam())).append(" && ").append(translate(fct.getRightParam())).append(")"); return str.toString(); } @Override public String translate(Comparison comp) throws TranslationException{ if ((comp.getLeftOperand() instanceof ContainsFunction || comp.getLeftOperand() instanceof IntersectsFunction) && (comp.getOperator() == ComparisonOperator.EQUAL || comp.getOperator() == ComparisonOperator.NOT_EQUAL) && comp.getRightOperand().isNumeric()) return translate(comp.getLeftOperand()) + " " + comp.getOperator().toADQL() + " '" + translate(comp.getRightOperand()) + "'"; else if ((comp.getRightOperand() instanceof ContainsFunction || comp.getRightOperand() instanceof IntersectsFunction) && (comp.getOperator() == ComparisonOperator.EQUAL || comp.getOperator() == ComparisonOperator.NOT_EQUAL) && comp.getLeftOperand().isNumeric()) return "'" + translate(comp.getLeftOperand()) + "' " + comp.getOperator().toADQL() + " " + translate(comp.getRightOperand()); else return super.translate(comp); } @Override public DBType convertTypeFromDB(final int dbmsType, final String rawDbmsTypeName, String dbmsTypeName, final String[] params){ // If no type is provided return VARCHAR: if (dbmsTypeName == null || dbmsTypeName.trim().length() == 0) return null; // Put the dbmsTypeName in lower case for the following comparisons: dbmsTypeName = dbmsTypeName.toLowerCase(); if (dbmsTypeName.equals("spoint")) return new DBType(DBDatatype.POINT); else if (dbmsTypeName.equals("scircle") || dbmsTypeName.equals("sbox") || dbmsTypeName.equals("spoly")) return new DBType(DBDatatype.REGION); else return super.convertTypeFromDB(dbmsType, rawDbmsTypeName, dbmsTypeName, params); } @Override public String convertTypeToDB(final DBType type){ if (type != null){ if (type.type == DBDatatype.POINT) return "spoint"; else if (type.type == DBDatatype.REGION) return "spoly"; } return super.convertTypeToDB(type); } @Override public Region translateGeometryFromDB(final Object jdbcColValue) throws ParseException{ // A NULL value stays NULL: if (jdbcColValue == null) return null; // Only a special object is expected: else if (!(jdbcColValue instanceof PGobject)) throw new ParseException("Incompatible type! The column value \"" + jdbcColValue.toString() + "\" was supposed to be a geometrical object."); PGobject pgo = (PGobject)jdbcColValue; // In case one or both of the fields of the given object are NULL: if (pgo == null || pgo.getType() == null || pgo.getValue() == null || pgo.getValue().length() == 0) return null; // Extract the object type and its value: String objType = pgo.getType().toLowerCase(); String geomStr = pgo.getValue(); /* Only spoint, scircle, sbox and spoly are supported ; * these geometries are parsed and transformed in Region instances:*/ if (objType.equals("spoint")) return (new PgSphereGeometryParser()).parsePoint(geomStr); else if (objType.equals("scircle")) return (new PgSphereGeometryParser()).parseCircle(geomStr); else if (objType.equals("sbox")) return (new PgSphereGeometryParser()).parseBox(geomStr); else if (objType.equals("spoly")) return (new PgSphereGeometryParser()).parsePolygon(geomStr); else throw new ParseException("Unsupported PgSphere type: \"" + objType + "\"! Impossible to convert the column value \"" + geomStr + "\" into a Region."); } @Override public Object translateGeometryToDB(final Region region) throws ParseException{ // A NULL value stays NULL: if (region == null) return null; try{ PGobject dbRegion = new PGobject(); StringBuffer buf; // Build the PgSphere expression from the given geometry in function of its type: switch(region.type){ case POSITION: dbRegion.setType("spoint"); dbRegion.setValue("(" + region.coordinates[0][0] + "d," + region.coordinates[0][1] + "d)"); break; case POLYGON: dbRegion.setType("spoly"); buf = new StringBuffer("{"); for(int i = 0; i < region.coordinates.length; i++){ if (i > 0) buf.append(','); buf.append('(').append(region.coordinates[i][0]).append("d,").append(region.coordinates[i][1]).append("d)"); } buf.append('}'); dbRegion.setValue(buf.toString()); break; case BOX: dbRegion.setType("spoly"); buf = new StringBuffer("{"); // south west buf.append('(').append(region.coordinates[0][0] - region.width / 2).append("d,").append(region.coordinates[0][1] - region.height / 2).append("d),"); // north west buf.append('(').append(region.coordinates[0][0] - region.width / 2).append("d,").append(region.coordinates[0][1] + region.height / 2).append("d),"); // north east buf.append('(').append(region.coordinates[0][0] + region.width / 2).append("d,").append(region.coordinates[0][1] + region.height / 2).append("d),"); // south east buf.append('(').append(region.coordinates[0][0] + region.width / 2).append("d,").append(region.coordinates[0][1] - region.height / 2).append("d)"); buf.append('}'); dbRegion.setValue(buf.toString()); break; case CIRCLE: dbRegion.setType("spoly"); dbRegion.setValue(circleToPolygon(region.coordinates[0], region.radius)); break; default: throw new ParseException("Unsupported geometrical region: \"" + region.type + "\"!"); } return dbRegion; }catch(SQLException e){ /* This error could never happen! */ return null; } } /** *Convert the specified circle into a polygon. * The generated polygon is formatted using the PgSphere syntax.
* *Note: * The center coordinates and the radius are expected in degrees. *
* * @param center Center of the circle ([0]=ra and [1]=dec). * @param radius Radius of the circle. * * @return The PgSphere serialization of the corresponding polygon. * * @since 1.3 */ protected String circleToPolygon(final double[] center, final double radius){ double angle = 0, x, y; StringBuffer buf = new StringBuffer(); while(angle < 2 * Math.PI){ x = center[0] + radius * Math.cos(angle); y = center[1] + radius * Math.sin(angle); if (buf.length() > 0) buf.append(','); buf.append('(').append(x).append("d,").append(y).append("d)"); angle += ANGLE_CIRCLE_TO_POLYGON; } return "{" + buf + "}"; } /** *Let parse a geometry serialized with the PgSphere syntax.
* ** There is one function parseXxx(String) for each supported geometry. * These functions always return a {@link Region} object, * which is the object representation of an STC region. *
* *Only the following geometries are supported:
** This parser supports all the known PgSphere representations of an angle. * However, it always returns angle (coordinates, radius, width and height) in degrees. *
* * @author Grégory Mantelet (ARI) * @version 1.3 (11/2014) * @since 1.3 */ protected static class PgSphereGeometryParser { /** Position of the next characters to read in the PgSphere expression to parse. */ private int pos; /** Full PgSphere expression to parse. */ private String expr; /** Last read token (either a string/numeric or a separator). */ private String token; /** Buffer used to read tokens. */ private StringBuffer buffer; private static final char OPEN_PAR = '('; private static final char CLOSE_PAR = ')'; private static final char COMMA = ','; private static final char LESS_THAN = '<'; private static final char GREATER_THAN = '>'; private static final char OPEN_BRACE = '{'; private static final char CLOSE_BRACE = '}'; private static final char DEGREE = 'd'; private static final char HOUR = 'h'; private static final char MINUTE = 'm'; private static final char SECOND = 's'; /** * Exception sent when the end of the expression * (EOE = End Of Expression) is reached. * * @author Grégory Mantelet (ARI) * @version 1.3 (11/2014) * @since 1.3 */ private static class EOEException extends ParseException { private static final long serialVersionUID = 1L; /** Build a simple EOEException. */ public EOEException(){ super("Unexpected End Of PgSphere Expression!"); } } /** * Build the PgSphere parser. */ public PgSphereGeometryParser(){} /** * Prepare the parser in order to read the given PgSphere expression. * * @param newStcs New PgSphere expression to parse from now. */ private void init(final String newExpr){ expr = (newExpr == null) ? "" : newExpr; token = null; buffer = new StringBuffer(); pos = 0; } /** * Finalize the parsing. * No more characters (except eventually some space characters) should remain in the PgSphere expression to parse. * * @throws ParseException If other non-space characters remains. */ private void end() throws ParseException{ // Skip all spaces: skipSpaces(); // If there is still some characters, they are not expected, and so throw an exception: if (expr.length() > 0 && pos < expr.length()) throw new ParseException("Unexpected end of PgSphere region expression: \"" + expr.substring(pos) + "\" was unexpected!", new TextPosition(1, pos, 1, expr.length())); // Reset the buffer, token and the PgSphere expression to parse: buffer = null; expr = null; token = null; } /** * Tool function which skips all next space characters until the next meaningful characters. */ private void skipSpaces(){ while(pos < expr.length() && Character.isWhitespace(expr.charAt(pos))) pos++; } /** *Get the next meaningful word. This word can be a numeric, any string constant or a separator. * This function returns this token but also stores it in the class attribute {@link #token}.
* ** In case the end of the expression is reached before getting any meaningful character, * an {@link EOEException} is thrown. *
* * @return The full read word/token, or NULL if the end has been reached. */ private String nextToken() throws EOEException{ // Skip all spaces: skipSpaces(); if (pos >= expr.length()) throw new EOEException(); // Fetch all characters until word separator (a space or a open/close parenthesis): buffer.append(expr.charAt(pos++)); if (!isSyntaxSeparator(buffer.charAt(0))){ while(pos < expr.length() && !isSyntaxSeparator(expr.charAt(pos))){ // skip eventual white-spaces: if (!Character.isWhitespace(expr.charAt(pos))) buffer.append(expr.charAt(pos)); pos++; } } // Save the read token and reset the buffer: token = buffer.toString(); buffer.delete(0, token.length()); return token; } /** *Tell whether the given character is a separator defined in the syntax.
* *Here, the following characters are considered as separators/specials: * ',', 'd', 'h', 'm', 's', '(', ')', '<', '>', '{' and '}'.
* * @param c Character to test. * * @return true if the given character must be considered as a separator, false otherwise. */ private static boolean isSyntaxSeparator(final char c){ return (c == COMMA || c == DEGREE || c == HOUR || c == MINUTE || c == SECOND || c == OPEN_PAR || c == CLOSE_PAR || c == LESS_THAN || c == GREATER_THAN || c == OPEN_BRACE || c == CLOSE_BRACE); } /** * Get the next character and ensure it is the same as the character given in parameter. * If the read character is not matching the expected one, a {@link ParseException} is thrown. * * @param expected Expected character. * * @throws ParseException If the next character is not matching the given one. */ private void nextToken(final char expected) throws ParseException{ // Skip all spaces: skipSpaces(); // Test whether the end is reached: if (pos >= expr.length()) throw new EOEException(); // Fetch the next character: char t = expr.charAt(pos++); token = new String(new char[]{t}); /* Test the the fetched character with the expected one * and throw an error if they don't match: */ if (t != expected) throw new ParseException("Incorrect syntax for \"" + expr + "\"! \"" + expected + "\" was expected instead of \"" + t + "\".", new TextPosition(1, pos - 1, 1, pos)); } /** * Parse the given PgSphere geometry as a point. * * @param pgsphereExpr The PgSphere expression to parse as a point. * * @return A {@link Region} implementing a STC Position region. * * @throws ParseException If the PgSphere syntax of the given expression is wrong or does not correspond to a point. */ public Region parsePoint(final String pgsphereExpr) throws ParseException{ // Init the parser: init(pgsphereExpr); // Parse the expression: double[] coord = parsePoint(); // No more character should remain after that: end(); // Build the STC Position region: return new Region(null, coord); } /** * Internal spoint parsing function. It parses the PgSphere expression stored in this parser as a point. * * @return The ra and dec coordinates (in degrees) of the parsed point. * * @throws ParseException If the PgSphere syntax of the given expression is wrong or does not correspond to a point. * * @see #parseAngle() * @see #parsePoint(String) */ private double[] parsePoint() throws ParseException{ nextToken(OPEN_PAR); double x = parseAngle(); nextToken(COMMA); double y = parseAngle(); nextToken(CLOSE_PAR); return new double[]{x,y}; } /** * Parse the given PgSphere geometry as a circle. * * @param pgsphereExpr The PgSphere expression to parse as a circle. * * @return A {@link Region} implementing a STC Circle region. * * @throws ParseException If the PgSphere syntax of the given expression is wrong or does not correspond to a circle. */ public Region parseCircle(final String pgsphereExpr) throws ParseException{ // Init the parser: init(pgsphereExpr); // Parse the expression: nextToken(LESS_THAN); double[] center = parsePoint(); nextToken(COMMA); double radius = parseAngle(); nextToken(GREATER_THAN); // No more character should remain after that: end(); // Build the STC Circle region: return new Region(null, center, radius); } /** * Parse the given PgSphere geometry as a box. * * @param pgsphereExpr The PgSphere expression to parse as a box. * * @return A {@link Region} implementing a STC Box region. * * @throws ParseException If the PgSphere syntax of the given expression is wrong or does not correspond to a box. */ public Region parseBox(final String pgsphereExpr) throws ParseException{ // Init the parser: init(pgsphereExpr); // Parse the expression: nextToken(OPEN_PAR); double[] southwest = parsePoint(); nextToken(COMMA); double[] northeast = parsePoint(); nextToken(CLOSE_PAR); // No more character should remain after that: end(); // Build the STC Box region: double width = Math.abs(northeast[0] - southwest[0]), height = Math.abs(northeast[1] - southwest[1]); double[] center = new double[]{northeast[0] - width / 2,northeast[1] - height / 2}; return new Region(null, center, width, height); } /** * Parse the given PgSphere geometry as a point. * * @param pgsphereExpr The PgSphere expression to parse as a point. * * @return A {@link Region} implementing a STC Position region. * * @throws ParseException If the PgSphere syntax of the given expression is wrong or does not correspond to a point. */ public Region parsePolygon(final String pgsphereExpr) throws ParseException{ // Init the parser: init(pgsphereExpr); // Parse the expression: nextToken(OPEN_BRACE); ArrayListRead the next tokens as an angle expression and returns the corresponding angle in degrees.
* *This function supports the 4 following syntaxes:
*MS SQL Server translator.
* *Important: * This translator works correctly ONLY IF {@link SQLServer_ADQLQueryFactory} has been used * to create any ADQL query this translator is asked to translate. *
* * TODO See how case sensitivity is supported by MS SQL Server and modify this translator accordingly. * * TODO Extend this class for each MS SQL Server extension supporting geometry and particularly * {@link #translateGeometryFromDB(Object)}, {@link #translateGeometryToDB(adql.db.STCS.Region)} and all this other * translate(...) functions for the ADQL's geometrical functions. * * TODO Check MS SQL Server datatypes (see {@link #convertTypeFromDB(int, String, String, String[])}, * {@link #convertTypeToDB(DBType)}). * *Important note: * Geometrical functions are not translated ; the translation returned for them is their ADQL expression. *
* * @author Grégory Mantelet (ARI) * @version 1.4 (09/2017) * @since 1.4 * * @see SQLServer_ADQLQueryFactory */ public class SQLServerTranslator extends JDBCTranslator { /**Indicate the case sensitivity to apply to each SQL identifier (only SCHEMA, TABLE and COLUMN).
* *Note: * In this implementation, this field is set by the constructor and never modified elsewhere. * It would be better to never modify it after the construction in order to keep a certain consistency. *
*/ protected byte caseSensitivity = 0x00; /** * Builds an SQLServerTranslator which always translates in SQL all identifiers (schema, table and column) in a case sensitive manner ; * in other words, schema, table and column names will be surrounded by double quotes in the SQL translation. */ public SQLServerTranslator(){ caseSensitivity = 0x0F; } /** * Builds an SQLServerTranslator which always translates in SQL all identifiers (schema, table and column) in the specified case sensitivity ; * in other words, schema, table and column names will all be surrounded or not by double quotes in the SQL translation. * * @param allCaseSensitive true to translate all identifiers in a case sensitive manner (surrounded by double quotes), false for case insensitivity. */ public SQLServerTranslator(final boolean allCaseSensitive){ caseSensitivity = allCaseSensitive ? (byte)0x0F : (byte)0x00; } /** * Builds an SQLServerTranslator which will always translate in SQL identifiers with the defined case sensitivity. * * @param catalog true to translate catalog names with double quotes (case sensitive in the DBMS), false otherwise. * @param schema true to translate schema names with double quotes (case sensitive in the DBMS), false otherwise. * @param table true to translate table names with double quotes (case sensitive in the DBMS), false otherwise. * @param column true to translate column names with double quotes (case sensitive in the DBMS), false otherwise. */ public SQLServerTranslator(final boolean catalog, final boolean schema, final boolean table, final boolean column){ caseSensitivity = IdentifierField.CATALOG.setCaseSensitive(caseSensitivity, catalog); caseSensitivity = IdentifierField.SCHEMA.setCaseSensitive(caseSensitivity, schema); caseSensitivity = IdentifierField.TABLE.setCaseSensitive(caseSensitivity, table); caseSensitivity = IdentifierField.COLUMN.setCaseSensitive(caseSensitivity, column); } @Override public boolean isCaseSensitive(final IdentifierField field){ return field == null ? false : field.isCaseSensitive(caseSensitivity); } /** * For SQL Server, {@link #translate(ClauseSelect)} must be overridden for * TOP/LIMIT handling. We must not add the LIMIT at the end of the query, it * must go in the SELECT. * * @see #translate(ClauseSelect) */ @Override public String translate(ADQLQuery query) throws TranslationException{ StringBuffer sql = new StringBuffer(translate(query.getSelect())); sql.append("\nFROM ").append(translate(query.getFrom())); if (!query.getWhere().isEmpty()) sql.append('\n').append(translate(query.getWhere())); if (!query.getGroupBy().isEmpty()) sql.append('\n').append(translate(query.getGroupBy())); if (!query.getHaving().isEmpty()) sql.append('\n').append(translate(query.getHaving())); if (!query.getOrderBy().isEmpty()) sql.append('\n').append(translate(query.getOrderBy())); return sql.toString(); } @Override public String translate(ClauseSelect clause) throws TranslationException{ String sql = null; for(int i = 0; i < clause.size(); i++){ if (i == 0){ sql = clause.getName() + (clause.distinctColumns() ? " DISTINCT" : "") + (clause.hasLimit() ? " TOP " + clause.getLimit() + " " : ""); }else sql += " " + clause.getSeparator(i); sql += " " + translate(clause.get(i)); } return sql; } @Override public String translate(final ADQLJoin join) throws TranslationException{ StringBuffer sql = new StringBuffer(translate(join.getLeftTable())); sql.append(' ').append(join.getJoinType()).append(' ').append(translate(join.getRightTable())).append(' '); // CASE: NATURAL if (join.isNatural()){ try{ StringBuffer buf = new StringBuffer(); // Find duplicated items between the two lists and translate them as ON conditions: DBColumn rightCol; SearchColumnList leftList = join.getLeftTable().getDBColumns(); SearchColumnList rightList = join.getRightTable().getDBColumns(); for(DBColumn leftCol : leftList){ // search for at most one column with the same name in the RIGHT list // and throw an exception is there are several matches: rightCol = ADQLJoin.findAtMostOneColumn(leftCol.getADQLName(), (byte)0, rightList, false); // if there is one... if (rightCol != null){ // ...check there is only one column with this name in the LEFT list, // and throw an exception if it is not the case: ADQLJoin.findExactlyOneColumn(leftCol.getADQLName(), (byte)0, leftList, true); // ...append the corresponding join condition: if (buf.length() > 0) buf.append(" AND "); buf.append(translate(generateJoinColumn(join.getLeftTable(), leftCol, new ADQLColumn(leftCol.getADQLName())))); buf.append("="); buf.append(translate(generateJoinColumn(join.getRightTable(), rightCol, new ADQLColumn(rightCol.getADQLName())))); } } sql.append("ON ").append(buf.toString()); }catch(UnresolvedJoinException uje){ throw new TranslationException("Impossible to resolve the NATURAL JOIN between " + join.getLeftTable().toADQL() + " and " + join.getRightTable().toADQL() + "!", uje); } } // CASE: USING else if (join.hasJoinedColumns()){ try{ StringBuffer buf = new StringBuffer(); // For each columns of usingList, check there is in each list exactly one matching column, and then, translate it as ON condition: DBColumn leftCol, rightCol; ADQLColumn usingCol; SearchColumnList leftList = join.getLeftTable().getDBColumns(); SearchColumnList rightList = join.getRightTable().getDBColumns(); IteratorTranslates all ADQL objects into an SQL interrogation query designed for MySQL.
* *Important: * The geometrical functions are translated exactly as in ADQL. * You will probably need to extend this translator to correctly manage the geometrical functions. *
* * @author Grégory Mantelet (ARI) * @version 2.1 (08/2017) * @since 2.1 */ public class MySQLTranslator extends JDBCTranslator { /** MySQL requires a length for variable-length types such as CHAR, VARCHAR, * BINARY and VARBINARY. This static attributes is the default value set * by this translator if no length is specified. */ public static int DEFAULT_VARIABLE_LENGTH = 200; /** Indicate the case sensitivity to apply to each SQL identifier * (only SCHEMA, TABLE and COLUMN). *Note: * In this implementation, this field is set by the constructor and never * modified elsewhere. It would be better to never modify it after the * construction in order to keep a certain consistency. *
*/ protected byte caseSensitivity = 0x00; /** * Build a MySQLTranslator which always translates in SQL all identifiers * (schema, table and column) in a case sensitive manner ; in other words, * schema, table and column names will be surrounded by back-quotes in the * SQL translation. */ public MySQLTranslator(){ caseSensitivity = 0x0F; } /** * Build a MySQLTranslator which always translates in SQL all identifiers * (schema, table and column) in the specified case sensitivity ; in other * words, schema, table and column names will all be surrounded or not by * back-quotes in the SQL translation. * * @param allCaseSensitive true to translate all identifiers in a * case sensitive manner * (surrounded by back-quotes), * false for case insensitivity. */ public MySQLTranslator(final boolean allCaseSensitive){ caseSensitivity = allCaseSensitive ? (byte)0x0F : (byte)0x00; } /** * Build a MySQLTranslator which will always translate in SQL identifiers * with the defined case sensitivity. * * @param catalog true to translate catalog names with back-quotes * (case sensitive in the DBMS), false otherwise. * @param schema true to translate schema names with back-quotes * (case sensitive in the DBMS), false otherwise. * @param table true to translate table names with back-quotes * (case sensitive in the DBMS), false otherwise. * @param column true to translate column names with back-quotes * (case sensitive in the DBMS), false otherwise. */ public MySQLTranslator(final boolean catalog, final boolean schema, final boolean table, final boolean column){ caseSensitivity = IdentifierField.CATALOG.setCaseSensitive(caseSensitivity, catalog); caseSensitivity = IdentifierField.SCHEMA.setCaseSensitive(caseSensitivity, schema); caseSensitivity = IdentifierField.TABLE.setCaseSensitive(caseSensitivity, table); caseSensitivity = IdentifierField.COLUMN.setCaseSensitive(caseSensitivity, column); } @Override public boolean isCaseSensitive(final IdentifierField field){ return field == null ? false : field.isCaseSensitive(caseSensitivity); } @Override public StringBuffer appendIdentifier(final StringBuffer str, final String id, final boolean caseSensitive){ /* Note: In MySQL the identifier quoting character is a back-quote. */ if (caseSensitive && !id.matches("\"[^\"]*\"")) return str.append('`').append(id).append('`'); else return str.append(id); } /* ********************************************************************** */ /* * * */ /* * TYPE MANAGEMENT * */ /* * * */ /* ********************************************************************** */ @Override public DBType convertTypeFromDB(final int dbmsType, final String rawDbmsTypeName, String dbmsTypeName, final String[] params){ // If no type is provided return VARCHAR: if (dbmsTypeName == null || dbmsTypeName.trim().length() == 0) return null; // Put the dbmsTypeName in lower case for the following comparisons: dbmsTypeName = dbmsTypeName.toLowerCase(); // Extract the length parameter (always the first one): int lengthParam = DBType.NO_LENGTH; if (params != null && params.length > 0){ try{ lengthParam = Integer.parseInt(params[0]); }catch(NumberFormatException nfe){} } // SMALLINT if (dbmsTypeName.equals("smallint") || dbmsTypeName.equals("tinyint") || dbmsTypeName.equals("bool") || dbmsTypeName.equals("boolean")) return new DBType(DBDatatype.SMALLINT); // INTEGER else if (dbmsTypeName.equals("integer") || dbmsTypeName.equals("int") || dbmsTypeName.equals("mediumint")) return new DBType(DBDatatype.INTEGER); // BIGINT else if (dbmsTypeName.equals("bigint")) return new DBType(DBDatatype.BIGINT); // REAL else if (dbmsTypeName.equals("float") || dbmsTypeName.equals("real")) return new DBType(DBDatatype.REAL); // DOUBLE else if (dbmsTypeName.equals("double") || dbmsTypeName.equals("double precision") || dbmsTypeName.equals("dec") || dbmsTypeName.equals("decimal") || dbmsTypeName.equals("numeric") || dbmsTypeName.equals("fixed")) return new DBType(DBDatatype.DOUBLE); // BINARY else if (dbmsTypeName.equals("bit") || dbmsTypeName.equals("binary") || dbmsTypeName.equals("char byte")) return new DBType(DBDatatype.BINARY, lengthParam); // VARBINARY else if (dbmsTypeName.equals("varbinary")) return new DBType(DBDatatype.VARBINARY, lengthParam); // CHAR else if (dbmsTypeName.equals("char") || dbmsTypeName.equals("character") || dbmsTypeName.equals("nchar") || dbmsTypeName.equals("national char")) return new DBType(DBDatatype.CHAR, lengthParam); // VARCHAR else if (dbmsTypeName.equals("varchar") || dbmsTypeName.equals("character varying") || dbmsTypeName.equals("nvarchar") || dbmsTypeName.equals("national varchar")) return new DBType(DBDatatype.VARCHAR, lengthParam); // BLOB else if (dbmsTypeName.equals("blob") || dbmsTypeName.equals("tinyblob") || dbmsTypeName.equals("mediumblob") || dbmsTypeName.equals("longblob")) return new DBType(DBDatatype.BLOB); // CLOB else if (dbmsTypeName.equals("text") || dbmsTypeName.equals("tinytext") || dbmsTypeName.equals("mediumtext") || dbmsTypeName.equals("longtext")) return new DBType(DBDatatype.CLOB); // TIMESTAMP else if (dbmsTypeName.equals("timestamp") || dbmsTypeName.equals("date") || dbmsTypeName.equals("datetime") || dbmsTypeName.equals("time") || dbmsTypeName.equals("year")) return new DBType(DBDatatype.TIMESTAMP); // Default: else return null; } @Override public String convertTypeToDB(final DBType type){ if (type == null) return "VARCHAR(" + DEFAULT_VARIABLE_LENGTH + ")"; switch(type.type){ case SMALLINT: case INTEGER: case REAL: case BIGINT: case TIMESTAMP: return type.type.toString(); case DOUBLE: return "DOUBLE PRECISION"; case CHAR: case VARCHAR: case BINARY: case VARBINARY: return type.type.toString() + "(" + (type.length > 0 ? type.length : DEFAULT_VARIABLE_LENGTH) + ")"; case BLOB: return "BLOB"; case CLOB: return "TEXT"; case POINT: case REGION: default: return "VARCHAR(" + DEFAULT_VARIABLE_LENGTH + ")"; } } @Override public Region translateGeometryFromDB(final Object jdbcColValue) throws ParseException{ throw new ParseException("Unsupported geometrical value! The value \"" + jdbcColValue + "\" can not be parsed as a region."); } @Override public Object translateGeometryToDB(final Region region) throws ParseException{ throw new ParseException("Geometries can not be uploaded in the database in this implementation!"); } /* ********************************************************************** */ /* * * */ /* * SPATIAL FUNCTIONS TRANSLATION * */ /* * * */ /* ********************************************************************** */ @Override public String translate(ExtractCoord extractCoord) throws TranslationException{ return getDefaultADQLFunction(extractCoord); } @Override public String translate(ExtractCoordSys extractCoordSys) throws TranslationException{ return getDefaultADQLFunction(extractCoordSys); } @Override public String translate(AreaFunction areaFunction) throws TranslationException{ return getDefaultADQLFunction(areaFunction); } @Override public String translate(CentroidFunction centroidFunction) throws TranslationException{ return getDefaultADQLFunction(centroidFunction); } @Override public String translate(DistanceFunction fct) throws TranslationException{ return getDefaultADQLFunction(fct); } @Override public String translate(ContainsFunction fct) throws TranslationException{ return getDefaultADQLFunction(fct); } @Override public String translate(IntersectsFunction fct) throws TranslationException{ return getDefaultADQLFunction(fct); } @Override public String translate(BoxFunction box) throws TranslationException{ return getDefaultADQLFunction(box); } @Override public String translate(CircleFunction circle) throws TranslationException{ return getDefaultADQLFunction(circle); } @Override public String translate(PointFunction point) throws TranslationException{ return getDefaultADQLFunction(point); } @Override public String translate(PolygonFunction polygon) throws TranslationException{ return getDefaultADQLFunction(polygon); } @Override public String translate(RegionFunction region) throws TranslationException{ return getDefaultADQLFunction(region); } } src/adql/parser/ 0000755 0001750 0001750 00000000000 13226134464 012555 5 ustar oles oles src/adql/parser/IdentifierItems.java 0000644 0001750 0001750 00000015712 13201352464 016505 0 ustar oles oles package adql.parser; /* * This file is part of ADQLLibrary. * * ADQLLibrary 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. * * ADQLLibrary 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 ADQLLibrary. If not, seeThis class is an array of maximum 4 {@link IdentifierItem}.
** The goal is to represent complex ADQL identifiers (column, table, ...) * which may be composed of more than only one identifier. *
** For instance, a table can be referenced either by only its name or by the * name of its schema and its name. So, in this last case there are 2 * identifiers. *
** It is possible to get one by one each identifier item (by using the * getters), or the concatenation of all (thanks to {@link #join(String)}). *
* * @author Grégory Mantelet (CDS;ARI) * @version 1.4 (11/2017) * * @see IdentifierItem */ public class IdentifierItems { /** * Represent any ADQL identifier (column name, table name or table/column * alias). * * @author Grégory Mantelet (CDS;ARI) * @version 1.4 (11/2017) */ public static class IdentifierItem { public String identifier = null; public boolean caseSensitivity = false; public TextPosition position = null; public IdentifierItem(final Token token, final boolean caseSensitive){ identifier = token.image.replaceAll("\"\"", "\""); caseSensitivity = caseSensitive; position = new TextPosition(token); } @Override public String toString(){ return identifier; } } /** All identifiers. The position of the different fields change in function of the number of elements. * If count=4: [0]=catalog, [1]=schema, [2]=table, [3]=column. */ private IdentifierItem[] identifiers = new IdentifierItem[4]; /** Number of identifier fields added. */ private int count = 0; /** Indicates whether this {@link IdentifierItems} is supposed to represent a table name (true), or a column name (false). */ private boolean tableIdent = false; /** * Builds an IdentifierItems by specifying it is a table or a column identifier. * * @param tableIdentifier true if this IdentifierItems is a table identifier, false otherwise. */ public IdentifierItems(final boolean tableIdentifier){ tableIdent = tableIdentifier; } /** *Apppends a simple identifier, that's to say an additional field (catalog, schema, table, column).
* *Note: This function has no effect if there are already 4 identifiers.
* * @param item Additional item (may be null). */ public void append(final IdentifierItem item){ if (count >= 4) return; identifiers[count++] = item; } /** * Gets the number of fields/identifiers stored in this {@link IdentifierItems}. * * @return The number of identifiers. */ public int size(){ return count; } /** * Gets the whole ind-th identifier/field. * * @param ind Index of the identifier/field to get. * * @return The wanted identifier/field. */ public IdentifierItem get(final int ind){ return (ind < 0 || identifiers[ind] == null) ? null : identifiers[ind]; } /** * Gets the value of the ind-th identifier/field. * * @param ind Index of the identifier/field to get. * * @return The value of the wanted identifier/field. */ public String getIdentifier(final int ind){ return (ind < 0 || identifiers[ind] == null) ? null : identifiers[ind].identifier; } public String getCatalog(){ return getIdentifier(tableIdent ? (count - 3) : (count - 4)); } public String getSchema(){ return getIdentifier(tableIdent ? (count - 2) : (count - 3)); } public String getTable(){ return getIdentifier(tableIdent ? (count - 1) : (count - 2)); } public String getColumn(){ return getIdentifier(tableIdent ? -1 : (count - 1)); } public int getBeginLine(){ return (count == 0 || identifiers[0] == null) ? -1 : identifiers[0].position.beginLine; } public int getEndLine(){ return (count == 0 || identifiers[count - 1] == null) ? -1 : identifiers[count - 1].position.endLine; } public int getBeginColumn(){ return (count == 0 || identifiers[0] == null) ? -1 : identifiers[0].position.beginColumn; } public int getEndColumn(){ return (count == 0 || identifiers[count - 1] == null) ? -1 : identifiers[count - 1].position.endColumn; } public TextPosition getPosition(){ return new TextPosition(getBeginLine(), getBeginColumn(), getEndLine(), getEndColumn()); } public byte getCaseSensitivity(){ byte sensitivity = IdentifierField.getFullCaseSensitive(false); if (count == 0) return sensitivity; int ind = count - 1; // COLUMN: if (!tableIdent){ if (identifiers[ind] != null) sensitivity = IdentifierField.COLUMN.setCaseSensitive(sensitivity, identifiers[ind].caseSensitivity); ind--; } if (ind < 0) return sensitivity; // TABLE: if (identifiers[ind] != null) sensitivity = IdentifierField.TABLE.setCaseSensitive(sensitivity, identifiers[ind].caseSensitivity); ind--; if (ind < 0) return sensitivity; // SCHEMA: if (identifiers[ind] != null) sensitivity = IdentifierField.SCHEMA.setCaseSensitive(sensitivity, identifiers[ind].caseSensitivity); ind--; if (ind < 0) return sensitivity; // CATALOG: if (identifiers[ind] != null) sensitivity = IdentifierField.CATALOG.setCaseSensitive(sensitivity, identifiers[ind].caseSensitivity); return sensitivity; } public boolean getColumnCaseSensitivity(){ if (count == 0 || tableIdent || identifiers[count - 1] == null) return false; return identifiers[count - 1].caseSensitivity; } /** * Joins all identifiers with the given delimiter. * * @param delim The string which must separate the identifiers (if null, the delimiter will be an empty string). * * @return The joint complex identifier. */ public String join(String delim){ if (count == 0) return null; if (delim == null) delim = ""; StringBuffer str = new StringBuffer(); for(int i = 0; i < count; i++){ if (identifiers[i] != null){ if (str.length() > 0) str.append(delim); str.append((identifiers[i] == null) ? "" : identifiers[i].identifier); } } return str.toString(); } @Override public String toString(){ return join("."); } } src/adql/parser/ADQLQueryFactory.java 0000644 0001750 0001750 00000040750 13177122310 016515 0 ustar oles oles package adql.parser; /* * This file is part of ADQLLibrary. * * ADQLLibrary 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. * * ADQLLibrary 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 ADQLLibrary. If not, seeThis class lets the {@link ADQLParser} to build an object representation of an ADQL query.
* *To customize the object representation you merely have to extends the appropriate functions of this class.
* * @author Grégory Mantelet (CDS;ARI) * @version 1.4 (04/2017) * * @see ADQLParser */ public class ADQLQueryFactory { /** * Type of table JOIN. * * @author Grégory Mantelet (CDS) * @version 1.0 (08/2011) */ public static enum JoinType{ CROSS, INNER, OUTER_LEFT, OUTER_RIGHT, OUTER_FULL; } /** * Create a query factory. */ public ADQLQueryFactory(){ ; } public ADQLQuery createQuery() throws Exception{ return new ADQLQuery(); } public ADQLTable createTable(final IdentifierItems idItems, final IdentifierItem alias) throws Exception{ ADQLTable t = new ADQLTable(idItems.getCatalog(), idItems.getSchema(), idItems.getTable()); // Set the table alias: if (alias != null) t.setAlias(alias.identifier); // Set the case sensitivity on the table name parts: byte caseSensitivity = idItems.getCaseSensitivity(); if (alias != null) caseSensitivity = IdentifierField.ALIAS.setCaseSensitive(caseSensitivity, alias.caseSensitivity); t.setCaseSensitive(caseSensitivity); return t; } public ADQLTable createTable(ADQLQuery query, IdentifierItem alias) throws Exception{ ADQLTable t = new ADQLTable(query); if (alias != null){ // Set the table alias: t.setAlias(alias.identifier); // Set the case sensitivity: t.setCaseSensitive(IdentifierField.ALIAS, alias.caseSensitivity); } return t; } public ADQLJoin createJoin(JoinType type, FromContent leftTable, FromContent rightTable) throws Exception{ switch(type){ case CROSS: return new CrossJoin(leftTable, rightTable); case INNER: return new InnerJoin(leftTable, rightTable); case OUTER_LEFT: return new OuterJoin(leftTable, rightTable, OuterType.LEFT); case OUTER_RIGHT: return new OuterJoin(leftTable, rightTable, OuterType.RIGHT); case OUTER_FULL: return new OuterJoin(leftTable, rightTable, OuterType.FULL); default: throw new Exception("Unknown join type: " + type); } } public ADQLJoin createJoin(JoinType type, FromContent leftTable, FromContent rightTable, ClauseConstraints condition) throws Exception{ switch(type){ case CROSS: throw new Exception("A cross join must have no condition (that's to say: no part ON) !"); default: ADQLJoin join = createJoin(type, leftTable, rightTable); join.setJoinCondition(condition); return join; } } public ADQLJoin createJoin(JoinType type, FromContent leftTable, FromContent rightTable, CollectionCreates the user defined functions called as the given name and with the given parameters.
* ** By default, this function returns a {@link DefaultUDF} instance. It is generic enough to cover every kind of functions. * But you can of course override this function in order to return your own instance of {@link UserDefinedFunction}. * In this case, you may not forget to call the super function (super.createUserDefinedFunction(name, params)) so that * all other unknown functions are still returned as {@link DefaultUDF} instances. *
* *IMPORTANT: * The tests done to check whether a user defined function is allowed/managed in this implementation, is done later by the parser. * Only declared UDF will pass the test of the parser. For that, you should give it a list of allowed UDFs (each UDF will be then * represented by a {@link FunctionDef} object). *
* * @param name Name of the user defined function to create. * @param params Parameters of the user defined function to create. * * @return The corresponding user defined function (by default an instance of {@link DefaultUDF}). * * @throws Exception If there is a problem while creating the function. */ public UserDefinedFunction createUserDefinedFunction(String name, ADQLOperand[] params) throws Exception{ return new DefaultUDF(name, params); } public DistanceFunction createDistance(PointFunction point1, PointFunction point2) throws Exception{ return new DistanceFunction(new GeometryValue