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 <http://www.gnu.org/licenses/>.
 *
 * Copyright 2011-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
 *                       Astronomisches Rechen Institut (ARI)
 */

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import adql.db.STCS.CoordSys;
import adql.db.STCS.Region;
import adql.db.STCS.RegionType;
import adql.db.exception.UnresolvedColumnException;
import adql.db.exception.UnresolvedFunctionException;
import adql.db.exception.UnresolvedIdentifiersException;
import adql.db.exception.UnresolvedTableException;
import adql.parser.QueryChecker;
import adql.parser.grammar.ParseException;
import adql.query.ADQLIterator;
import adql.query.ADQLObject;
import adql.query.ADQLQuery;
import adql.query.ClauseADQL;
import adql.query.ClauseSelect;
import adql.query.ColumnReference;
import adql.query.IdentifierField;
import adql.query.SelectAllColumns;
import adql.query.SelectItem;
import adql.query.from.ADQLTable;
import adql.query.from.FromContent;
import adql.query.operand.ADQLColumn;
import adql.query.operand.ADQLOperand;
import adql.query.operand.StringConstant;
import adql.query.operand.UnknownType;
import adql.query.operand.function.ADQLFunction;
import adql.query.operand.function.DefaultUDF;
import adql.query.operand.function.UserDefinedFunction;
import adql.query.operand.function.geometry.BoxFunction;
import adql.query.operand.function.geometry.CircleFunction;
import adql.query.operand.function.geometry.GeometryFunction;
import adql.query.operand.function.geometry.PointFunction;
import adql.query.operand.function.geometry.PolygonFunction;
import adql.query.operand.function.geometry.RegionFunction;
import adql.search.ISearchHandler;
import adql.search.SearchColumnHandler;
import adql.search.SimpleReplaceHandler;
import adql.search.SimpleSearchHandler;

/**
 * This {@link QueryChecker} implementation is able to do the following
 * verifications on an ADQL query:
 * <ol>
 * 	<li>Check the existence of all table and column references found in a
 * 		query</li>
 * 	<li>Resolve all unknown functions as supported User Defined Functions
 * 		(UDFs)</li>
 * 	<li>Check that types of columns and UDFs match with their context</li>
 * </ol>
 *
 * <p><i><b>IMPORTANT note:</b>
 * 	Since v2.0, the check of supported geometric functions, STC-s expressions
 * 	and coordinate systems are performed automatically in
 * 	{@link adql.parser.ADQLParser ADQLParser} through the notion of Optional
 * 	Features. The declaration of supported geometric functions must now be done
 * 	with {@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}
 * 	(see also {@link adql.parser.feature.FeatureSet FeatureSet}).
 * </i></p>
 *
 * <h3>Check tables and columns</h3>
 * <p>
 * 	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.
 * </p>
 *
 * <p>These information are:</p>
 * <ul>
 * 	<li>the corresponding {@link DBTable} or {@link DBColumn} (see getter and
 * 		setter for DBLink in {@link ADQLTable} and {@link ADQLColumn})</li>
 * 	<li>the link between an {@link ADQLColumn} and its {@link ADQLTable}</li>
 * </ul>
 *
 * <p><i><u>Note:</u>
 * 	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 JDBCTranslator}.
 * </i></p>
 *
 * @author Gr&eacute;gory Mantelet (CDS;ARI)
 * @version 2.0 (08/2019)
 */
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, ...).
	 *
	 * <p>
	 * 	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.
	 * </p>
	 *
	 * @since 1.3
	 * @deprecated Since v2.0, supported geometrical functions must be declared
	 *             in ADQLParser. */
	@Deprecated
	protected String[] allowedGeo = null;

	/** <p>List of all allowed coordinate systems.</p>
	 * <p>
	 * 	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.
	 * </p>
	 * <p><i>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.</i></p>
	 * <p>
	 * 	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.
	 * </p>
	 * @since 1.3
	 * @deprecated Since v2.0, supported coordinate systems must be declared
	 *             in ADQLParser. */
	@Deprecated
	protected String[] allowedCoordSys = null;

	/** <p>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.</p>
	 * <p>If NULL, all coordinate systems are allowed.</p>
	 * @since 1.3
	 * @deprecated Since v2.0, supported coordinate systems must be declared
	 *             in ADQLParser. */
	@Deprecated
	protected String coordSysRegExp = null;

	/** <p>List of all allowed User Defined Functions (UDFs).</p>
	 * <p>
	 * 	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.
	 * </p>
	 * @since 1.3 */
	protected FunctionDef[] allowedUdfs = null;

	/* ************ */
	/* CONSTRUCTORS */
	/* ************ */
	/**
	 * <p>Builds a {@link DBChecker} with an empty list of tables.</p>
	 *
	 * <p>Verifications done by this object after creation:</p>
	 * <ul>
	 * 	<li>Existence of tables and columns:            <b>NO <i>(even unknown or fake tables and columns are allowed)</i></b></li>
	 * 	<li>Existence of User Defined Functions (UDFs): <b>NO <i>(any "unknown" function is allowed)</i></b></li>
	 * 	<li>Support of coordinate systems:              <b>NO <i>(all valid coordinate systems are allowed)</i></b></li>
	 * </ul>
	 */
	public DBChecker() {
		this(null, null);
	}

	/**
	 * <p>Builds a {@link DBChecker} with the given list of known tables.</p>
	 *
	 * <p>Verifications done by this object after creation:</p>
	 * <ul>
	 * 	<li>Existence of tables and columns:            <b>OK</b></li>
	 * 	<li>Existence of User Defined Functions (UDFs): <b>NO <i>(any "unknown" function is allowed)</i></b></li>
	 * 	<li>Support of coordinate systems:              <b>NO <i>(all valid coordinate systems are allowed)</i></b></li>
	 * </ul>
	 *
	 * @param tables	List of all available tables.
	 */
	public DBChecker(final Collection<? extends DBTable> tables) {
		this(tables, null);
	}

	/**
	 * <p>Builds a {@link DBChecker} with the given list of known tables and with a restricted list of user defined functions.</p>
	 *
	 * <p>Verifications done by this object after creation:</p>
	 * <ul>
	 * 	<li>Existence of tables and columns:            <b>OK</b></li>
	 * 	<li>Existence of User Defined Functions (UDFs): <b>OK</b></li>
	 * 	<li>Support of coordinate systems:              <b>NO <i>(all valid coordinate systems are allowed)</i></b></li>
	 * </ul>
	 *
	 * @param tables		List of all available tables.
	 * @param allowedUdfs	List of all allowed user defined functions.
	 *                   	If NULL, no verification will be done (and so, all UDFs are allowed).
	 *                   	If empty list, no "unknown" (or UDF) is allowed.
	 *                   	<i>Note: match with items of this list are done case insensitively.</i>
	 *
	 * @since 1.3
	 */
	public DBChecker(final Collection<? extends DBTable> tables, final Collection<? extends FunctionDef> allowedUdfs) {
		// Sort and store the given tables:
		setTables(tables);

		Object[] tmp;
		int cnt;

		// Store all allowed UDFs in a sorted array:
		if (allowedUdfs != null) {
			// Remove all NULL:
			tmp = new FunctionDef[allowedUdfs.size()];
			cnt = 0;
			for(FunctionDef udf : allowedUdfs) {
				if (udf != null)
					tmp[cnt++] = udf;
			}
			// make a copy of the array:
			this.allowedUdfs = new FunctionDef[cnt];
			System.arraycopy(tmp, 0, this.allowedUdfs, 0, cnt);

			tmp = null;
			// sort the values:
			Arrays.sort(this.allowedUdfs);
		}
	}

	/**
	 * <p>Builds a {@link DBChecker} with the given list of known tables and with a restricted list of user defined functions.</p>
	 *
	 * <p>Verifications done by this object after creation:</p>
	 * <ul>
	 * 	<li>Existence of tables and columns:            <b>OK</b></li>
	 * 	<li>Existence of User Defined Functions (UDFs): <b>NO <i>(any "unknown" function is allowed)</i></b></li>
	 * 	<li>Support of geometrical functions:           <b>OK</b></li>
	 * 	<li>Support of coordinate systems:              <b>OK</b></li>
	 * </ul>
	 *
	 * @param tables			List of all available tables.
	 * @param allowedGeoFcts	List of all allowed geometrical functions (i.e. CONTAINS, POINT, UNION, CIRCLE, COORD1).
	 *                      	If NULL, no verification will be done (and so, all geometries are allowed).
	 *                      	If empty list, no geometry function is allowed.
	 *                      	<i>Note: match with items of this list are done case insensitively.</i>
	 * @param allowedCoordSys	List of all allowed coordinate system patterns. The syntax of a such pattern is the following:
	 *                       	"{frame} {refpos} {flavor}" ; on the contrary to a coordinate system expression, here no part is optional.
	 *                       	Each part of this pattern can be one the possible values (case insensitive), a list of possible values
	 *                       	expressed with the syntax "({value1}|{value2}|...)", or a '*' for any valid value.
	 *                       	For instance: "ICRS (GEOCENTER|heliocenter) *".
	 *                       	If the given list is NULL, no verification will be done (and so, all coordinate systems are allowed).
	 *                       	If it is empty, no coordinate system is allowed (except the default values - generally expressed by an empty string: '').
	 *
	 * @since 1.3
	 * @deprecated	Since v2.0, the check of geometrical functions support is
	 *            	performed in ADQLParser. It must now be done with
	 *            	{@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}
	 * 	          	(see also {@link adql.parser.feature.FeatureSet FeatureSet}).
	 */
	@Deprecated
	public DBChecker(final Collection<? extends DBTable> tables, final Collection<String> allowedGeoFcts, final Collection<String> allowedCoordSys) throws ParseException {
		this(tables, null, allowedGeoFcts, allowedCoordSys);
	}

	/**
	 * <p>Builds a {@link DBChecker}.</p>
	 *
	 * <p>Verifications done by this object after creation:</p>
	 * <ul>
	 * 	<li>Existence of tables and columns:            <b>OK</b></li>
	 * 	<li>Existence of User Defined Functions (UDFs): <b>OK</b></li>
	 * 	<li>Support of coordinate systems:              <b>OK</b></li>
	 * </ul>
	 *
	 * <p><i><b>IMPORTANT note:</b>
	 * 	Since v2.0, the check of supported geometrical functions is performed
	 * 	directly in ADQLParser through the notion of Optional Features.
	 * 	The declaration of supported geometrical functions must now be done
	 * 	with {@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}
	 * 	(see also {@link adql.parser.feature.FeatureSet FeatureSet}).
	 * </i></p>
	 *
	 * @param tables			List of all available tables.
	 * @param allowedUdfs		List of all allowed user defined functions.
	 *                   		If NULL, no verification will be done (and so, all UDFs are allowed).
	 *                   		If empty list, no "unknown" (or UDF) is allowed.
	 *                   		<i>Note: match with items of this list are done case insensitively.</i>
	 * @param allowedGeoFcts	List of all allowed geometrical functions (i.e. CONTAINS, POINT, UNION, CIRCLE, COORD1).
	 *                      	If NULL, no verification will be done (and so, all geometries are allowed).
	 *                      	If empty list, no geometry function is allowed.
	 *                      	<i>Note: match with items of this list are done case insensitively.</i>
	 * @param allowedCoordSys	List of all allowed coordinate system patterns. The syntax of a such pattern is the following:
	 *                       	"{frame} {refpos} {flavor}" ; on the contrary to a coordinate system expression, here no part is optional.
	 *                       	Each part of this pattern can be one the possible values (case insensitive), a list of possible values
	 *                       	expressed with the syntax "({value1}|{value2}|...)", or a '*' for any valid value.
	 *                       	For instance: "ICRS (GEOCENTER|heliocenter) *".
	 *                       	If the given list is NULL, no verification will be done (and so, all coordinate systems are allowed).
	 *                       	If it is empty, no coordinate system is allowed (except the default values - generally expressed by an empty string: '').
	 *
	 * @since 2.0
	 * @deprecated	Since v2.0, the check of geometrical functions support is
	 *            	performed in ADQLParser. It must now be done with
	 *            	{@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}
	 * 	          	(see also {@link adql.parser.feature.FeatureSet FeatureSet}).
	 */
	@Deprecated
	public DBChecker(final Collection<? extends DBTable> tables, final Collection<? extends FunctionDef> allowedUdfs, final Collection<String> allowedGeoFcts, final Collection<String> allowedCoordSys) throws ParseException {
		// Set the list of available tables + Set the list of all known UDFs:
		this(tables, allowedUdfs);

		// Set the list of allowed geometrical functions:
		allowedGeo = specialSort(allowedGeoFcts);

		// Set the list of allowed coordinate systems:
		this.allowedCoordSys = specialSort(allowedCoordSys);
		coordSysRegExp = STCS.buildCoordSysRegExp(this.allowedCoordSys);
	}

	/**
	 * Transform the given collection of string elements in a sorted array.
	 * Only non-NULL and non-empty strings are kept.
	 *
	 * @param items	Items to copy and sort.
	 *
	 * @return	A sorted array containing all - except NULL and empty strings - items of the given collection.
	 *
	 * @since 1.3
	 *
	 * @deprecated	Since v2.0, this tool function is no longer used. It was
	 *            	useful only to collect allowed geometries and coordinate
	 *            	systems....but these are now checked by
	 *            	{@link adql.parser.ADQLParser ADQLParser}.
	 */
	@Deprecated
	protected final static String[] specialSort(final Collection<String> items) {
		// Nothing to do if the array is NULL:
		if (items == null)
			return null;

		// Keep only valid items (not NULL and not empty string):
		String[] tmp = new String[items.size()];
		int cnt = 0;
		for(String item : items) {
			if (item != null && item.trim().length() > 0)
				tmp[cnt++] = item;
		}

		// Make an adjusted array copy:
		String[] copy = new String[cnt];
		System.arraycopy(tmp, 0, copy, 0, cnt);

		// Sort the values:
		Arrays.sort(copy);

		return copy;
	}

	/* ****** */
	/* SETTER */
	/* ****** */
	/**
	 * <p>Sets the list of all available tables.</p>
	 *
	 * <p><i><u>Note:</u>
	 * 	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.
	 * </i></p>
	 *
	 * @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.
	 *
	 * <p><i><b>Note:</b>
	 * 	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.
	 * </i></p>
	 *
	 * @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.
	 *
	 * <p>Main verifications done in this function:</p>
	 * <ol>
	 * 	<li>Existence of DB items (tables and columns)</li>
	 * 	<li>Semantic verification of sub-queries</li>
	 * 	<li>Support of every encountered User Defined Functions (UDFs -
	 * 		functions unknown by the syntactic parser)</li>
	 * 	<li>Consistency of types still unknown (because the syntactic parser
	 * 		could not yet resolve them)</li>
	 * </ol>
	 *
	 * @param query			The query to check.
	 * @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. <i><b>Note:</b> this
	 *                   	parameter is NULL if this function is called with
	 *                   	the root/father query as parameter.</i>
	 *
	 * @throws UnresolvedIdentifiersException	An {@link UnresolvedIdentifiersException}
	 *                                       	if one or several of the above
	 *                                       	listed tests have detected some
	 *                                       	semantic errors (i.e. unresolved
	 *                                       	table, columns, function).
	 *
	 * @since 1.2
	 *
	 * @see #checkDBItems(ADQLQuery, Stack, UnresolvedIdentifiersException)
	 * @see #checkSubQueries(ADQLQuery, Stack, SearchColumnList, UnresolvedIdentifiersException)
	 * @see #checkUDFs(ADQLQuery, UnresolvedIdentifiersException)
	 * @see #checkTypes(ADQLQuery, UnresolvedIdentifiersException)
	 */
	protected void check(final ADQLQuery query, final Stack<SearchColumnList> fathersList) throws UnresolvedIdentifiersException {
		UnresolvedIdentifiersException errors = new UnresolvedIdentifiersException();

		// A. Check DB items (tables and columns):
		SearchColumnList availableColumns = checkDBItems(query, fathersList, errors);

		// B. Check UDFs:
		if (allowedUdfs != null)
			checkUDFs(query, errors);

		// C. Check types:
		checkTypes(query, errors);

		// D. Check sub-queries:
		checkSubQueries(query, fathersList, availableColumns, errors);

		// Throw all errors, if any:
		if (errors.getNbErrors() > 0)
			throw errors;
	}

	/* ************************************************ */
	/* CHECKING METHODS FOR DB ITEMS (TABLES & COLUMNS) */
	/* ************************************************ */

	/**
	 * <p>Check DB items (tables and columns) used in the given ADQL query.</p>
	 *
	 * <p>Operations done in this function:</p>
	 * <ol>
	 * 	<li>Resolve all found tables</li>
	 * 	<li>Get the whole list of all available columns <i>Note: this list is returned by this function.</i></li>
	 * 	<li>Resolve all found columns</li>
	 * </ol>
	 *
	 * @param query			Query in which the existence of DB items 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.
	 *                   	<i>Note: this parameter is NULL if this function is called with the root/father query as parameter.</i>
	 * @param errors		List of errors to complete in this function each time an unknown table or column is encountered.
	 *
	 * @return	List of all columns available in the given query.
	 *
	 * @see #resolveTables(ADQLQuery, Stack, UnresolvedIdentifiersException)
	 * @see FromContent#getDBColumns()
	 * @see #resolveColumns(ADQLQuery, Stack, Map, SearchColumnList, UnresolvedIdentifiersException)
	 *
	 * @since 1.3
	 */
	protected SearchColumnList checkDBItems(final ADQLQuery query, final Stack<SearchColumnList> fathersList, final UnresolvedIdentifiersException errors) {
		// a. Resolve all tables:
		Map<DBTable, ADQLTable> mapTables = resolveTables(query, fathersList, errors);

		// b. Get the list of all columns made available in the clause FROM:
		SearchColumnList availableColumns;
		try {
			availableColumns = query.getFrom().getDBColumns();
		} catch(ParseException pe) {
			errors.addException(pe);
			availableColumns = new SearchColumnList();
		}

		// c. Resolve all columns:
		resolveColumns(query, fathersList, mapTables, availableColumns, errors);

		return availableColumns;
	}

	/**
	 * Search all table references inside the given query, resolve them against
	 * the available tables, and if there is only one match, attach the matching
	 * metadata to them.
	 *
	 * <b>Management of sub-query tables</b>
	 * <p>
	 * 	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.
	 * </p>
	 *
	 * <b>Management of "{table}.*" in the SELECT clause</b>
	 * <p>
	 * 	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).
	 * </p>
	 *
	 * <b>Table alias</b>
	 * <p>
	 * 	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.
	 * </p>
	 * <p>
	 * 	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.
	 * </p>
	 *
	 * @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.
	 *                   	<i>Note: this parameter is NULL if this function is
	 *                   	called with the root/father query as parameter.</i>
	 * @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 Map<DBTable, ADQLTable> resolveTables(final ADQLQuery query, final Stack<SearchColumnList> fathersList, final UnresolvedIdentifiersException errors) {
		HashMap<DBTable, ADQLTable> mapTables = new HashMap<DBTable, ADQLTable>();
		ISearchHandler sHandler;

		// Check the existence of all tables:
		sHandler = new SearchTableHandler();
		sHandler.search(query.getFrom());
		for(ADQLObject result : sHandler) {
			try {
				ADQLTable table = (ADQLTable)result;

				// resolve the table:
				DBTable dbTable = null;
				if (table.isSubQuery()) {
					// check the sub-query tables:
					check(table.getSubQuery(), fathersList);
					// generate its DBTable:
					dbTable = generateDBTable(table.getSubQuery(), table.getAlias());
				} else {
					dbTable = resolveTable(table);
					// wrap this table metadata if an alias should be used:
					if (dbTable != null && table.hasAlias())
						dbTable = new DBTableAlias(dbTable, (table.isCaseSensitive(IdentifierField.ALIAS) ? table.getAlias() : table.getAlias().toLowerCase()));
				}

				// link with the matched DBTable:
				table.setDBLink(dbTable);
				mapTables.put(dbTable, table);
			} catch(ParseException pe) {
				errors.addException(pe);
			}
		}

		// Attach table information on wildcards with the syntax "{tableName}.*" of the SELECT clause:
		/* Note: no need to check the table name among the father tables, because there is
		 *       no interest to select a father column in a sub-query
		 *       (which can return only one column ; besides, no aggregate is allowed
		 *       in sub-queries).*/
		sHandler = new SearchWildCardHandler();
		sHandler.search(query.getSelect());
		for(ADQLObject result : sHandler) {
			try {
				SelectAllColumns wildcard = (SelectAllColumns)result;
				ADQLTable table = wildcard.getAdqlTable();
				DBTable dbTable = null;

				// first, try to resolve the table by table alias:
				if (table.getTableName() != null && table.getSchemaName() == null) {
					List<ADQLTable> tables = query.getFrom().getTablesByAlias(table.getTableName(), table.isCaseSensitive(IdentifierField.TABLE));
					if (tables.size() == 1)
						dbTable = tables.get(0).getDBLink();
				}

				// then try to resolve the table reference by table name:
				if (dbTable == null)
					dbTable = resolveTable(table);

				// set the corresponding tables among the list of resolved tables:
				wildcard.setAdqlTable(mapTables.get(dbTable));
			} catch(ParseException pe) {
				errors.addException(pe);
			}
		}

		return mapTables;
	}

	/**
	 * Resolve the given table, that's to say search for the corresponding {@link DBTable}.
	 *
	 * @param table	The table to resolve.
	 *
	 * @return		The corresponding {@link DBTable} if found, <i>null</i> otherwise.
	 *
	 * @throws ParseException	An {@link UnresolvedTableException} if the given table can't be resolved.
	 */
	protected DBTable resolveTable(final ADQLTable table) throws ParseException {
		List<DBTable> tables = lstTables.search(table);

		// good if only one table has been found:
		if (tables.size() == 1)
			return tables.get(0);
		// but if more than one: ambiguous table name !
		else if (tables.size() > 1)
			throw new UnresolvedTableException(table, (tables.get(0).getADQLSchemaName() == null ? "" : tables.get(0).getADQLSchemaName() + ".") + tables.get(0).getADQLName(), (tables.get(1).getADQLSchemaName() == null ? "" : tables.get(1).getADQLSchemaName() + ".") + tables.get(1).getADQLName());
		// otherwise (no match): unknown table !
		else
			throw new UnresolvedTableException(table);
	}

	/**
	 * <p>Search 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.</p>
	 *
	 * <b>Management of selected columns' references</b>
	 * <p>
	 * 	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.
	 * </p>
	 * <p>
	 * 	These references are also checked, in a second step, in this function. Thus, column metadata are
	 * 	also attached to them, as common columns.
	 * </p>
	 *
	 * @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.
	 *                   	<i>Note: this parameter is NULL if this function is called with the root/father query as parameter.</i>
	 * @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 Stack<SearchColumnList> fathersList, final Map<DBTable, ADQLTable> mapTables, final SearchColumnList list, final UnresolvedIdentifiersException errors) {
		ISearchHandler sHandler;

		// Check the existence of all columns:
		sHandler = new SearchColumnOutsideGroupByHandler();
		sHandler.search(query);
		for(ADQLObject result : sHandler) {
			try {
				ADQLColumn adqlColumn = (ADQLColumn)result;
				// resolve the column:
				DBColumn dbColumn = resolveColumn(adqlColumn, list, fathersList);
				// link with the matched DBColumn:
				adqlColumn.setDBLink(dbColumn);
				adqlColumn.setAdqlTable(mapTables.get(dbColumn.getTable()));
			} catch(ParseException pe) {
				errors.addException(pe);
			}
		}

		// Check the GROUP BY items:
		ClauseSelect select = query.getSelect();
		sHandler = new SearchColumnHandler();
		sHandler.search(query.getGroupBy());
		for(ADQLObject result : sHandler) {
			try {
				ADQLColumn adqlColumn = (ADQLColumn)result;
				// resolve the column:
				DBColumn dbColumn = checkGroupByItem(adqlColumn, select, list);
				// link with the matched DBColumn:
				if (dbColumn != null) {
					adqlColumn.setDBLink(dbColumn);
					adqlColumn.setAdqlTable(mapTables.get(dbColumn.getTable()));
				}
			} catch(ParseException pe) {
				errors.addException(pe);
			}
		}

		// Check the correctness of all column references (= references to selected columns):
		/* Note: no need to provide the father tables when resolving column references,
		 *       because no father column can be used in ORDER BY. */
		sHandler = new SearchColReferenceHandler();
		sHandler.search(query);
		for(ADQLObject result : sHandler) {
			try {
				ColumnReference colRef = (ColumnReference)result;
				// resolve the column reference:
				DBColumn dbColumn = checkColumnReference(colRef, select, list);
				// link with the matched DBColumn:
				colRef.setDBLink(dbColumn);
				if (dbColumn != null)
					colRef.setAdqlTable(mapTables.get(dbColumn.getTable()));
			} catch(ParseException pe) {
				errors.addException(pe);
			}
		}
	}

	/**
	 * <p>Resolve the given column, that's to say search for the corresponding {@link DBColumn}.</p>
	 *
	 * <p>
	 * 	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).
	 * </p>
	 *
	 * @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.
	 *                   	<i>Note: this parameter is NULL if this function is called with the root/father query as parameter.</i>
	 *
	 * @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, Stack<SearchColumnList> fathersList) throws ParseException {
		List<DBColumn> foundColumns = dbColumns.search(column);

		// good if only one column has been found:
		if (foundColumns.size() == 1)
			return foundColumns.get(0);
		// but if more than one: ambiguous table reference !
		else if (foundColumns.size() > 1) {
			if (column.getTableName() == null)
				throw new UnresolvedColumnException(column, (foundColumns.get(0).getTable() == null) ? "<NULL>" : (foundColumns.get(0).getTable().getADQLName() + "." + foundColumns.get(0).getADQLName()), (foundColumns.get(1).getTable() == null) ? "<NULL>" : (foundColumns.get(1).getTable().getADQLName() + "." + foundColumns.get(1).getADQLName()));
			else
				throw new UnresolvedTableException(column, (foundColumns.get(0).getTable() == null) ? "<NULL>" : foundColumns.get(0).getTable().getADQLName(), (foundColumns.get(1).getTable() == null) ? "<NULL>" : foundColumns.get(1).getTable().getADQLName());
		}// otherwise (no match): unknown column !
		else {
			if (fathersList == null || fathersList.isEmpty())
				throw new UnresolvedColumnException(column);
			else {
				Stack<SearchColumnList> subStack = new Stack<SearchColumnList>();
				subStack.addAll(fathersList.subList(0, fathersList.size() - 1));
				return resolveColumn(column, fathersList.peek(), subStack);
			}
		}
	}

	/**
	 * Check whether the given column corresponds to a selected item's alias or to an existing column.
	 *
	 * @param col			The column to check.
	 * @param select		The SELECT clause of the ADQL query.
	 * @param dbColumns		The list of all available columns.
	 *
	 * @return 	The corresponding {@link DBColumn} if this column corresponds to an existing column,
	 *        	<i>NULL</i> otherwise.
	 *
	 * @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.
	 *
	 * @see ClauseSelect#searchByAlias(String)
	 * @see #resolveColumn(ADQLColumn, SearchColumnList, Stack)
	 *
	 * @since 1.4
	 */
	protected DBColumn checkGroupByItem(final ADQLColumn col, final ClauseSelect select, final SearchColumnList dbColumns) throws ParseException {
		/* If the column name is not qualified, it may be a SELECT-item's alias.
		 * So, try resolving the name as an alias.
		 * If it fails, perform the normal column resolution.*/
		if (col.getTableName() == null) {
			List<SelectItem> founds = select.searchByAlias(col.getColumnName(), col.isCaseSensitive(IdentifierField.COLUMN));
			if (founds.size() == 1)
				return null;
			else if (founds.size() > 1)
				throw new UnresolvedColumnException(col, founds.get(0).getAlias(), founds.get(1).getAlias());
		}
		return resolveColumn(col, dbColumns, null);
	}

	/**
	 * Check whether the given column reference corresponds to a selected item (column or an expression with an alias)
	 * or to an existing column.
	 *
	 * @param colRef		The column reference which must be checked.
	 * @param select		The SELECT clause of the ADQL query.
	 * @param dbColumns		The list of all available columns.
	 *
	 * @return 		The corresponding {@link DBColumn} if this reference is actually the name of a column, <i>null</i> otherwise.
	 *
	 * @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.
	 *
	 * @see ClauseSelect#searchByAlias(String)
	 * @see #resolveColumn(ADQLColumn, SearchColumnList, Stack)
	 */
	protected DBColumn checkColumnReference(final ColumnReference colRef, final ClauseSelect select, final SearchColumnList dbColumns) throws ParseException {
		if (colRef.isIndex()) {
			int index = colRef.getColumnIndex();
			if (index > 0 && index <= select.size()) {
				SelectItem item = select.get(index - 1);
				if (item.getOperand() instanceof ADQLColumn)
					return ((ADQLColumn)item.getOperand()).getDBLink();
				else
					return null;
			} else
				throw new ParseException("Column index out of bounds: " + index + " (must be between 1 and " + select.size() + ") !", colRef.getPosition());
		} else {
			ADQLColumn col = new ADQLColumn(null, colRef.getColumnName());
			col.setCaseSensitive(colRef.isCaseSensitive());
			col.setPosition(colRef.getPosition());

			// search among the select_item aliases:
			List<SelectItem> founds = select.searchByAlias(colRef.getColumnName(), colRef.isCaseSensitive());
			if (founds.size() == 1)
				return null;
			else if (founds.size() > 1)
				throw new UnresolvedColumnException(col, founds.get(0).getAlias(), founds.get(1).getAlias());

			// check the corresponding column:
			return resolveColumn(col, dbColumns, null);
		}
	}

	/**
	 * Generate a {@link DBTable} corresponding to the given sub-query with the given table name.
	 * This {@link DBTable} will contain all {@link DBColumn} returned by {@link ADQLQuery#getResultingColumns()}.
	 *
	 * @param subQuery	Sub-query in which the specified table must be searched.
	 * @param tableName	Name of the table to search.
	 *
	 * @return	The corresponding {@link DBTable} if the table has been found in the given sub-query, <i>null</i> otherwise.
	 *
	 * @throws ParseException	Can be used to explain why the table has not been found. <i>Note: not used by default.</i>
	 */
	public static DBTable generateDBTable(final ADQLQuery subQuery, final String tableName) throws ParseException {
		DefaultDBTable dbTable = new DefaultDBTable(tableName);

		DBColumn[] columns = subQuery.getResultingColumns();
		for(DBColumn dbCol : columns)
			dbTable.addColumn(dbCol.copy(dbCol.getADQLName(), dbCol.getADQLName(), dbTable));

		return dbTable;
	}

	/* ************************* */
	/* CHECKING METHODS FOR UDFs */
	/* ************************* */

	/**
	 * <p>Search all UDFs (User Defined Functions) inside the given query, and then
	 * check their signature against the list of allowed UDFs.</p>
	 *
	 * <p><i>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.
	 * </i></p>
	 *
	 * @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 {
			ArrayList<UserDefinedFunction> toResolveLater = new ArrayList<UserDefinedFunction>();
			UserDefinedFunction udf;
			int match;
			BinarySearch<FunctionDef, UserDefinedFunction> binSearch = new BinarySearch<FunctionDef, UserDefinedFunction>() {
				@Override
				protected int compare(UserDefinedFunction searchItem, FunctionDef arrayItem) {
					return arrayItem.compareTo(searchItem) * -1;
				}
			};

			// Try to resolve all the found UDFs:
			/* Note: at this stage, it can happen that UDFs can not be yet resolved because the building of
			 *       their signature depends of other UDFs. That's why, these special cases should be kept
			 *       for a later resolution try. */
			for(ADQLObject result : sHandler) {
				udf = (UserDefinedFunction)result;
				// if the type of not all parameters are resolved, postpone the resolution:
				if (!isAllParamTypesResolved(udf))
					toResolveLater.add(udf);
				// otherwise:
				else {
					// search for a match:
					match = binSearch.search(udf, allowedUdfs);
					// if no match...
					if (match < 0)
						errors.addException(new UnresolvedFunctionException(udf));
					// if there is a match, metadata may be attached (particularly if the function is built automatically by the syntactic parser):
					else if (udf instanceof DefaultUDF)
						((DefaultUDF)udf).setDefinition(allowedUdfs[match]);
				}
			}

			// Try to resolve UDFs whose some parameter types are depending of other UDFs:
			/* Note: we need to iterate from the end in order to resolve first the most wrapped functions
			 *       (e.g. fct1(fct2(...)) ; fct2 must be resolved before fct1). */
			for(int i = toResolveLater.size() - 1; i >= 0; i--) {
				udf = toResolveLater.get(i);
				// search for a match:
				match = binSearch.search(udf, allowedUdfs);
				// if no match, add an error:
				if (match < 0)
					errors.addException(new UnresolvedFunctionException(udf));
				// otherwise, metadata may be attached (particularly if the function is built automatically by the syntactic parser):
				else if (udf instanceof DefaultUDF)
					((DefaultUDF)udf).setDefinition(allowedUdfs[match]);
			}

			// 3. Replace all the resolved DefaultUDF by an instance of the class associated with the set signature:
			(new ReplaceDefaultUDFHandler(errors)).searchAndReplace(query);
		}
	}

	/**
	 * <p>Tell whether the type of all parameters of the given ADQL function
	 * is resolved.</p>
	 *
	 * <p>A parameter type may not be resolved for 2 main reasons:</p>
	 * <ul>
	 * 	<li>the parameter is a <b>column</b>, but this column has not been successfully resolved. Thus its type is still unknown.</li>
	 * 	<li>the parameter is a <b>UDF</b>, but this UDF has not been already resolved. Thus, as for the column, its return type is still unknown.
	 * 		But it could be known later if the UDF is resolved later ; a second try should be done afterwards.</li>
	 * </ul>
	 *
	 * @param fct	ADQL function whose the parameters' type should be checked.
	 *
	 * @return	<i>true</i> if the type of all parameters is known, <i>false</i> otherwise.
	 *
	 * @since 1.3
	 */
	protected final boolean isAllParamTypesResolved(final ADQLFunction fct) {
		for(ADQLOperand op : fct.getParameters()) {
			if (op.isGeometry() == op.isNumeric() && op.isNumeric() == op.isString())
				return false;
		}
		return true;
	}

	/* ************************************************************************************************* */
	/* METHODS CHECKING THE GEOMETRIES (geometrical functions, coordinate systems and STC-S expressions) */
	/* ************************************************************************************************* */

	/**
	 * <p>Check all geometries.</p>
	 *
	 * <p>Operations done in this function:</p>
	 * <ol>
	 * 	<li>Check that all explicit (string constant) coordinate system definitions are supported</i></li>
	 * 	<li>Check all STC-S expressions (only in {@link RegionFunction} for the moment) and
	 * 	    Apply the 2 previous checks on them</li>
	 * </ol>
	 *
	 * <p><i><b>IMPORTANT note:</b>
	 * 	Since v2.0, the check of supported geometrical functions is performed
	 * 	directly in ADQLParser through the notion of Optional Features.
	 * 	The declaration of supported geometrical functions must now be done
	 * 	with {@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}
	 * 	(see also {@link adql.parser.feature.FeatureSet FeatureSet}).
	 * </i></p>
	 *
	 * @param query		Query in which geometries must be checked.
	 * @param errors	List of errors to complete in this function each time a geometry item is not supported.
	 *
	 * @see #resolveCoordinateSystems(ADQLQuery, UnresolvedIdentifiersException)
	 * @see #resolveSTCSExpressions(ADQLQuery, BinarySearch, UnresolvedIdentifiersException)
	 *
	 * @since 1.3
	 *
	 * @deprecated	Since 2.0, validation of the geometric functions is
	 *            	performed automatically by
	 *            	{@link adql.parser.ADQLParser ADQLParser}. Geometric
	 *            	functions are optional features and should be declared as
	 *            	such in the {@link adql.parser.ADQLParser ADQLParser} if
	 *            	they are supported (see
	 *            	{@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}).
	 */
	@Deprecated
	protected void checkGeometries(final ADQLQuery query, final UnresolvedIdentifiersException errors) {
		BinarySearch<String, String> binSearch = new BinarySearch<String, String>() {
			@Override
			protected int compare(String searchItem, String arrayItem) {
				return searchItem.compareToIgnoreCase(arrayItem);
			}
		};

		// a. Check whether the coordinate systems are allowed:
		if (allowedCoordSys != null)
			resolveCoordinateSystems(query, errors);

		// b. Check all STC-S expressions (in RegionFunctions only) + the used coordinate systems (if StringConstant only):
		if (allowedGeo == null || (allowedGeo.length > 0 && binSearch.search("REGION", allowedGeo) >= 0))
			resolveSTCSExpressions(query, binSearch, errors);
	}

	/**
	 * Search for all geometrical functions and check whether they are allowed.
	 *
	 * @param query		Query in which geometrical functions must be checked.
	 * @param errors	List of errors to complete in this function each time a geometrical function is not supported.
	 *
	 * @see #checkGeometryFunction(String, ADQLFunction, BinarySearch, UnresolvedIdentifiersException)
	 *
	 * @since 1.3
	 *
	 * @deprecated	Since 2.0, validation of the geometric functions is
	 *            	performed automatically by
	 *            	{@link adql.parser.ADQLParser ADQLParser}. Geometric
	 *            	functions are optional features and should be declared as
	 *            	such in the {@link adql.parser.ADQLParser ADQLParser} if
	 *            	they are supported (see
	 *            	{@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}).
	 */
	@Deprecated
	protected final void resolveGeometryFunctions(final ADQLQuery query, final BinarySearch<String, String> binSearch, final UnresolvedIdentifiersException errors) {
		ISearchHandler sHandler = new SearchGeometryHandler();
		sHandler.search(query);

		String fctName;
		for(ADQLObject result : sHandler) {
			fctName = result.getName();
			checkGeometryFunction(fctName, (ADQLFunction)result, binSearch, errors);
		}
	}

	/**
	 * <p>Check whether the specified geometrical function is allowed by this implementation.</p>
	 *
	 * <p><i>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.
	 * </i></p>
	 *
	 * @param fctName		Name of the geometrical function to test.
	 * @param fct			The function instance being or containing the geometrical function to check. <i>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
	 *
	 * @deprecated	Since 2.0, validation of the geometric functions is
	 *            	performed automatically by
	 *            	{@link adql.parser.ADQLParser ADQLParser}. Geometric
	 *            	functions are optional features and should be declared as
	 *            	such in the {@link adql.parser.ADQLParser ADQLParser} if
	 *            	they are supported (see
	 *            	{@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}).
	 */
	@Deprecated
	protected final void checkGeometryFunction(final String fctName, final ADQLFunction fct, final BinarySearch<String, String> binSearch, final UnresolvedIdentifiersException errors) {
		int match = -1;
		if (allowedGeo.length != 0)
			match = binSearch.search(fctName, allowedGeo);
		if (match < 0)
			errors.addException(new UnresolvedFunctionException("The geometrical function \"" + fctName + "\" is not available in this implementation!", fct));
	}

	/**
	 * <p>Search all explicit coordinate system declarations, check their syntax and whether they are allowed by this implementation.</p>
	 *
	 * <p><i>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.
	 * </i></p>
	 *
	 * @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
	 *
	 * @deprecated	Since 2.0, the validation of coordinate systems is performed
	 *            	automatically by {@link adql.parser.ADQLParser ADQLParser}.
	 */
	@Deprecated
	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
	 *
	 * @deprecated	Since 2.0, the validation of coordinate systems is performed
	 *            	automatically by {@link adql.parser.ADQLParser ADQLParser}.
	 */
	@Deprecated
	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
	 *
	 * @deprecated	Since 2.0, the validation of coordinate systems is performed
	 *            	automatically by {@link adql.parser.ADQLParser ADQLParser}.
	 */
	@Deprecated
	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()));
		}
	}

	/**
	 * <p>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.</p>
	 *
	 * <p><i>Note:
	 * 	In the current ADQL language definition, STC-S expressions can be found only as only parameter of the REGION function.
	 * </i></p>
	 *
	 * @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
	 *
	 * @deprecated	Since 2.0, the validation of STCs expressions is performed
	 *            	automatically by {@link adql.parser.ADQLParser ADQLParser}.
	 */
	@Deprecated
	protected void resolveSTCSExpressions(final ADQLQuery query, final BinarySearch<String, String> binSearch, final UnresolvedIdentifiersException errors) {
		// Search REGION functions:
		ISearchHandler sHandler = new SearchRegionHandler();
		sHandler.search(query);

		// Parse and check their STC-S expression:
		String stcs;
		Region region;
		for(ADQLObject result : sHandler) {
			try {
				// get the STC-S expression:
				stcs = ((StringConstant)((RegionFunction)result).getParameter(0)).getValue();

				// parse the STC-S expression (and so check the syntax):
				region = STCS.parseRegion(stcs);

				// check whether the regions (this one + the possible inner ones) and the coordinate systems are allowed:
				checkRegion(region, (RegionFunction)result, binSearch, errors);
			} catch(ParseException pe) {
				errors.addException(new ParseException(pe.getMessage(), result.getPosition()));
			}
		}
	}

	/**
	 * <p>Check the given region.</p>
	 *
	 * <p>The following points are checked in this function:</p>
	 * <ul>
	 * 	<li>whether the coordinate system is allowed</li>
	 * 	<li>whether the type of region is allowed</li>
	 * 	<li>whether the inner regions are correct (here this function is called recursively on each inner region).</li>
	 * </ul>
	 *
	 * @param r			The region to check.
	 * @param fct		The REGION function containing the region to check.
	 * @param errors	List of errors to complete in this function if the given region or its inner regions are not supported.
	 *
	 * @see #checkCoordinateSystem(adql.db.STCS.CoordSys, ADQLOperand, UnresolvedIdentifiersException)
	 * @see #checkGeometryFunction(String, ADQLFunction, BinarySearch, UnresolvedIdentifiersException)
	 * @see #checkRegion(adql.db.STCS.Region, RegionFunction, BinarySearch, UnresolvedIdentifiersException)
	 *
	 * @since 1.3
	 *
	 * @deprecated	Since 2.0, the validation of REGIONs is performed
	 *            	automatically by {@link adql.parser.ADQLParser ADQLParser}.
	 */
	@Deprecated
	protected void checkRegion(final Region r, final RegionFunction fct, final BinarySearch<String, String> binSearch, final UnresolvedIdentifiersException errors) {
		if (r == null)
			return;

		// Check the coordinate system (if any):
		if (r.coordSys != null)
			checkCoordinateSystem(r.coordSys, fct, errors);

		// Check that the region type is allowed:
		if (allowedGeo != null) {
			if (allowedGeo.length == 0)
				errors.addException(new UnresolvedFunctionException("The region type \"" + r.type + "\" is not available in this implementation!", fct));
			else
				checkGeometryFunction((r.type == RegionType.POSITION) ? "POINT" : r.type.toString(), fct, binSearch, errors);
		}

		// Check all the inner regions:
		if (r.regions != null) {
			for(Region innerR : r.regions)
				checkRegion(innerR, fct, binSearch, errors);
		}
	}

	/* **************************************************** */
	/* METHODS CHECKING TYPES UNKNOWN WHILE CHECKING SYNTAX */
	/* **************************************************** */

	/**
	 * <p>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.</p>
	 *
	 * <p>
	 * 	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.
	 * </p>
	 *
	 * <p>
	 * 	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.
	 * </p>
	 *
	 * <p><i><b>Important note:</b>
	 * 	This function does not check the types exactly, but just roughly by considering only three categories:
	 * 	string, numeric and geometry.
	 * </i></p>
	 *
	 * @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 */
	/* ******************************** */

	/**
	 * <p>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)}.</p>
	 *
	 * <b>Fathers stack</b>
	 * <p>
	 * 	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.
	 * </p>
	 * <p>
	 * 	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.
	 * </p>
	 *
	 *
	 * @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.
	 *                   		<i>Note: this parameter is NULL if this function is called with the root/father query as parameter.</i>
	 * @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<SearchColumnList> fathersList, final SearchColumnList availableColumns, final UnresolvedIdentifiersException errors) {
		// Check sub-queries outside the clause FROM:
		ISearchHandler sHandler = new SearchSubQueryHandler();
		sHandler.search(query);
		if (sHandler.getNbMatch() > 0) {

			// Push the list of columns into the father columns stack:
			if (fathersList == null)
				fathersList = new Stack<SearchColumnList>();
			fathersList.push(availableColumns);

			// Check each found sub-query:
			for(ADQLObject result : sHandler) {
				try {
					check((ADQLQuery)result, fathersList);
				} catch(UnresolvedIdentifiersException uie) {
					Iterator<ParseException> itPe = uie.getErrors();
					while(itPe.hasNext())
						errors.addException(itPe.next());
				}
			}

			// Pop the list of columns from the father columns stack:
			fathersList.pop();
		}
	}

	/* *************** */
	/* SEARCH HANDLERS */
	/* *************** */

	/**
	 * Lets searching all {@link ADQLColumn} in the given object, EXCEPT in the GROUP BY clause.
	 *
	 * <p>
	 * 	{@link ADQLColumn}s of the GROUP BY may be aliases and so, they can not be checked
	 *	exactly as a normal column.
	 * </p>
	 *
	 * @author Gr&eacute;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&eacute;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&eacute;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&eacute;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);
		}
	}

	/**
	 * <p>Lets searching subqueries in every clause except the FROM one (hence the modification of the {@link #goInto(ADQLObject)}.</p>
	 *
	 * <p><i>
	 * 	<u>Note:</u> 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.
	 * </i></p>
	 *
	 * @author Gr&eacute;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&eacute;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);
		}
	}

	/**
	 * <p>Let replacing every {@link DefaultUDF}s whose a {@link FunctionDef} is set by their corresponding {@link UserDefinedFunction} class.</p>
	 *
	 * <p><i><b>Important note:</b>
	 * 	If the replacer can not be created using the class returned by {@link FunctionDef#getUDFClass()}, no replacement is performed.
	 * </i></p>
	 *
	 * @author Gr&eacute;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&eacute;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);
		}
	}

	/**
	 * <p>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.</p>
	 *
	 * <p><i><b>Important note:</b>
	 * 	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.
	 * </i></p>
	 *
	 * @author Gr&eacute;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&eacute;gory Mantelet (ARI)
	 * @version 1.3 (10/2014)
	 * @since 1.3
	 * @deprecated	Since 2.0, the validation of REGIONs is performed
	 *            	automatically by {@link adql.parser.ADQLParser ADQLParser}.
	 */
	@Deprecated
	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&eacute;gory Mantelet (ARI)
	 * @version 1.3 (10/2014)
	 * @since 1.3
	 * @deprecated	Since 2.0, the validation of REGIONs is performed
	 *            	automatically by {@link adql.parser.ADQLParser ADQLParser}.
	 */
	@Deprecated
	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;
		}

	}

	/**
	 * <p>Implement the binary search algorithm over a sorted array.</p>
	 *
	 * <p>
	 * 	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.
	 * </p>
	 *
	 * <p>
	 * 	For that reason, the "compare" function must always be implemented.
	 * </p>
	 *
	 * @author Gr&eacute;gory Mantelet (ARI)
	 * @version 1.3 (10/2014)
	 *
	 * @param <T>	Type of items stored in the array.
	 * @param <S>	Type of the item to search.
	 *
	 * @since 1.3
	 */
	protected static abstract class BinarySearch<T, S> {
		private int s, e, m, comp;

		/**
		 * <p>Search the given item in the given array.</p>
		 *
		 * <p>
		 * 	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.
		 * </p>
		 *
		 * @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);
	}

}
