/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2005-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2007-2012, Geomatys
*
* This library 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;
* version 2.1 of the License.
*
* This library 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.
*/
package org.geotoolkit.internal.sql.table;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.EnumSet;
import org.apache.sis.util.CharSequences;
/**
* Base class for {@link Column} and {@link Parameter}. Those two sub-classes
* represent a SQL element which can be identified by an index:
* <p>
* <ul>
* <li>{@link java.sql.ResultSet#getString(int)} for {@link Column}</li>
* <li>{@link java.sql.PreparedStatement#setString(int, String)} for {@link Parameter}</li>
* </ul>
* <p>
* The index for a given {@code ColumnOrParameter} is computed from the index of previous
* elements in the {@link Query} for which this instance is created.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 3.09
*
* @since 3.09 (derived from Seagis)
* @module
*/
public abstract class ColumnOrParameter {
/**
* The parameter index for each {@linkplain query type}. If a query type is not
* supported by this element, then the corresponding index will be 0. Otherwise
* any valid index shall be equal or greater than 1.
*/
private final short[] index;
/**
* The functions to apply on this column or parameter. Will be created only if needed.
* It a majority of cases, this map is never created.
*
* @see #getFunction(QueryType)
*/
private EnumMap<QueryType,String> functions;
/**
* Creates a new language element without query. This is used only for temporary
* columns to be referenced by {@link CrossReference}. It should not be used for
* any other usage.
*/
ColumnOrParameter() {
index = null;
}
/**
* Creates a new language element for the specified query.
*
* @param query The query for which the element is created.
* @param types The query types for which the element applies.
*/
ColumnOrParameter(final Query query, final QueryType... types) {
this(query, (types != null && types.length != 0) ?
EnumSet.copyOf(Arrays.asList(types)) : EnumSet.noneOf(QueryType.class));
}
/**
* Creates a new language element for the specified query.
*
* @param query The query for which the element is created.
* @param types The query types for which the element applies.
*/
private ColumnOrParameter(final Query query, final EnumSet<QueryType> types) {
/*
* Computes the length of the 'index' array, which must be sufficient
* for holding the last query type (in ordinal order).
*/
int length = 0;
for (final QueryType type : types) {
final int ordinal = type.ordinal();
if (ordinal >= length) {
length = ordinal + 1;
}
}
/*
* Computes the index. For each QueryType supported by this language element, we scan
* the previous elements until we find one supporting the same QueryType. The index
* is then the previous index + columnSpan.
*/
index = new short[length];
final ColumnOrParameter[] existingElements = query.add(this);
search: for (final QueryType type : types) {
final int typeOrdinal = type.ordinal();
for (int i=existingElements.length; --i>=0;) {
final ColumnOrParameter previous = existingElements[i];
if (typeOrdinal < previous.index.length) {
short position = previous.index[typeOrdinal];
if (position != 0) {
if (++position < 0) {
throw new ArithmeticException("Overflow");
}
index[typeOrdinal] = position;
continue search;
}
}
}
index[typeOrdinal] = 1;
}
}
/**
* Returns the element ({@linkplain Column column} or {@linkplain Parameter parameter}) index
* when used in a query of the given type. Valid index numbers start at 1. This method returns
* 0 if this language element is not applicable to a query of the specified type.
*
* @param type The query type.
* @return The element index in the SQL prepared statment, or 0 if none.
*/
public final int indexOf(final QueryType type) {
final int ordinal = type.ordinal();
if (ordinal >= 0 && ordinal < index.length) {
return index[ordinal];
}
return 0;
}
/**
* Returns the function for this column or parameter when used in a query of the given type,
* or {@code null} if none. The purpose of this method depends on the subclass:
*
* <ul>
* <li><p>If this class is an instance of {@link Column}, then the function returned by this
* method is an <cite>aggregate functions</cite> like {@code "MIN"} or {@code "MAX"} to be
* used in the {@code SELECT} part of the SQL statement. This function is <strong>not</strong>
* used in the {@code WHERE} part of the SQL statement.</p></li>
*
* <li><p>If this class is an instance of {@link Parameter}, then the function returned by
* this method is applied on the parameter value. For example instead of {@code column=?},
* the caller way want {@code column=GeometryFromText(?,4326)}.</p></li>
* </ul>
*
* @param type The type of the query for which to get the function.
* @return The function for the given query type, or {@code null} if none.
*/
final String getFunction(final QueryType type) {
return (functions != null) ? functions.get(type) : null;
}
/**
* Sets a function for this column or parameter when used in a query of the given type.
* See {@link #getFunction(QueryType)} for a meaning of what functions can be.
*
* @param numQuestionMarks The expected number of question marks in the {@code function}
* argument. This is 0 for the {@link Column} class and 1 for {@link Parameter}.
* @param function The function to use with this column or parameter.
* @param types The type of the queries for which to use the given function.
*/
final void setFunction(final int numQuestionMarks, final String function, final QueryType... types) {
if (CharSequences.count(function, '?') != numQuestionMarks) {
throw new IllegalArgumentException(function);
}
if (functions == null) {
functions = new EnumMap<>(QueryType.class);
}
for (final QueryType type : types) {
functions.put(type, function);
}
}
/**
* Returns a hash code value for this language element.
*/
@Override
public int hashCode() {
return Arrays.hashCode(index);
}
/**
* Compares this language element with the specified object for equality.
*
* @param object The object to compare with this column or parameter.
*/
@Override
public boolean equals(final Object object) {
if (object != null && object.getClass() == getClass()) {
final ColumnOrParameter that = (ColumnOrParameter) object;
return Arrays.equals(this.index, that.index);
}
return false;
}
}