/* XXL: The eXtensible and fleXible Library for data processing
Copyright (C) 2000-2011 Prof. Dr. Bernhard Seeger
Head of the Database Research Group
Department of Mathematics and Computer Science
University of Marburg
Germany
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; either
version 3 of the License, or (at your option) any later version.
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.
You should have received a copy of the GNU Lesser General Public
License along with this library; If not, see <http://www.gnu.org/licenses/>.
http://code.google.com/p/xxl/
*/
package xxl.core.xxql.columns;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import xxl.core.collections.MapEntry;
import xxl.core.comparators.ComparableComparator;
import xxl.core.functions.AbstractFunction;
import xxl.core.functions.Function;
import xxl.core.predicates.AbstractPredicate;
import xxl.core.predicates.Predicate;
import xxl.core.relational.metaData.ColumnMetaData;
import xxl.core.relational.tuples.Tuple;
import xxl.core.xxql.AdvPredicate;
import xxl.core.xxql.AdvResultSetMetaData;
import xxl.core.xxql.AdvTupleCursor;
import xxl.core.xxql.CorrTuplesReceiver;
import xxl.core.xxql.AdvTupleCursor.CachingStrategy;
/**
* This class represents a single value/object, among other things to be used in
* {@link AdvPredicate predicates} and to select and create new Columns in
* {@link AdvTupleCursor#select(Column...) select}. <br>
* The object is retrieved by calling {@link #invoke(Tuple)} or {@link #invoke(Tuple, Tuple)} and
* most commonly a Column will return the Object contained in one specific column of the tuple
* (thus the name).<br>
* But this class (or subclasses of this class) is also used for static values like
* {@link ColumnUtils#val(Object)}, calculated values like {@link #ADD(Column)} that create a new
* Column from to given Columns, values obtained via reflection from a method-call on an Object
* from a column of the tuple (see {@link ReflectionColumn}) and to access values of
* "correlated tuples" in subqueries like EXISTS etc.
*/
public class Column extends AbstractFunction<Tuple, Object> implements CorrTuplesReceiver {
private static final long serialVersionUID = 1L;
public enum SubQueryType { // FIXME: evtl nach ColumnUtils um imports zu sparen
ALL, ANY
}
// either a value (from val()) or a bound object from an outer cursor/tuple
protected Object boundObject = null;
// index of this Column within a tuple (<1 if that doesn't apply)
private int columnIndex;
// metadata for this Column
protected ColumnMetaData columnMetaData;
// contained Colums (e.g. if this Column is composed like from ADD)
protected List<Column> containedColumns = null;
// if true, this Column projects it's values from a correlated tuple
private boolean fromCorrTuple = false;
// a flag to indicate we're using a bound object (from val() or from a correlated tuple)
private boolean haveBoundObject = false;
// the name of this column
public String columnAlias;
// the new name of this column (for renaming columns)
private String newColumnAlias;
// if this is used in a join, we'll have a left and a right tuple, but a normal Column uses
// only one of them. CONCAT, ADD, ... may "use" both, but they just pass them on to normal colums
// this specifies whether to evaluate the left or the right tuple in a join
//TODO: getter oder wieder protected
public boolean useLeftTuple = true;
// TODO: evtl alle Konstrkutoren private ?!? - wenn dann package protected fuer ColumnUtils
// FIXME: brauchen wir wirklich so viele konstruktoren?
public Column() {
this(0, null, null, null);
}
public Column(int columnIndex) {
this(columnIndex, null, null, null);
}
public Column(int columnIndex, String newColumnAlias) {
this(columnIndex, null, newColumnAlias, null);
}
public Column(String columnAlias) {
this(0, columnAlias, null, null);
}
public Column(String columnAlias, String newColumnAlias) {
this(0, columnAlias, newColumnAlias, null);
}
private Column(int column, String columnAlias, String newColumnAlias, ColumnMetaData columnMetaData) {
this.columnIndex = column;
this.columnAlias = columnAlias;
this.newColumnAlias = newColumnAlias;
this.columnMetaData = columnMetaData;
}
protected Column(Object val, String columnAlias){
this(val, columnAlias, null);
}
protected Column(Object val, String columnAlias, ColumnMetaData cmd) {
this(0, columnAlias, columnAlias, cmd);
boundObject = val;
haveBoundObject = true;
}
Column(String columnAlias, ColumnMetaData cmd) {
this(0, columnAlias, null, cmd);
}
/**
* Returns an the Object represented by this Column. That may be the value of one column of
* the given tuple, a correlated tuple, a value calculated from contained columns or
* just a fixed value.
*/
@Override
public Object invoke(Tuple tuple) {
if (columnIndex > 0) {
// System.out.println(this.name+": "+tuple.getObject(column).toString());
return tuple.getObject(columnIndex);
} else if (haveBoundObject) { // check ob != null ist suboptimal, falls
// der wert der spalte einfach null
// war..
return boundObject;
} else {
throw new RuntimeException(
"You haven't specified a valid Column! (" + columnAlias + ")");
}
}
/**
* Returns an the Object represented by this Column. That may be the value of one column of
* the given tuples, a value calculated from contained columns or just a fixed value.
*/
@Override
public Object invoke(Tuple left, Tuple right) {
if (useLeftTuple) {
return invoke(left);
} else {
return invoke(right);
}
}
/**
* Set the AdvResultSetMetaData of the cursor this column (probably) belongs
* to and the new alias of the resulting cursor.<br>
* It also passes on the MetaData to it's "contained" Colums (e.g. if this
* Column represents a concatenation of two other Columns it's passed on to
* them). If you're using this in a <b>join</b> Predicate you will have to
* set the AdvResultSetMetaData of both joined Cursors - just invoke this
* Method with each of them, the metadata with the first match for the
* internal name will be used.
*
* @param metadata
* AdvResultSetMetaData of the cursor, you're calling
* where/select/group_by on with this column
* @param newAlias
* the new alias of the resulting cursor after
* where/select/group_by (needed especially if this Column
* contains a given value from {@link Column#val(Object, String)
* val(obj, name)})
*/
public void setMetaData(AdvResultSetMetaData metadata, String newAlias) {
// set metadata for contained columns
if (containedColumns != null) {
for (Column col : containedColumns) {
col.setMetaData(metadata, newAlias);
}
}
// handle metadata for this column
if (columnMetaData == null) {
// in the first case the "bound object" is a value from val() - and
// that should not be
// null or we can't create metadata for it.. also this is the only
// case that needs the new alias
if (boundObject != null) {
columnMetaData = AdvResultSetMetaData.createColumnMetaData(
boundObject.getClass(), this.columnAlias, newAlias);
} else if (columnIndex > 0) { // the column was specified by the index
ColumnMetaData tmp = metadata.getColumnMetaData(columnIndex);
if (newColumnAlias == null) {
columnMetaData = tmp;
} else {
try {
columnMetaData =
AdvResultSetMetaData.createColumnMetaData(
Class.forName(tmp.getColumnClassName()),
newColumnAlias, tmp.getTableName()
);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
} else { // column was specified by "alias.name"
if (columnAlias == null || columnAlias.equals("")) {
throw new RuntimeException(
"You didn't specify a name, index or value for the column!");
}
int index = metadata.getIndexByAliasAndName(columnAlias);
// System.out.println("aliasname: "+name+" index: "+index);
if (index > 0) {
columnIndex = index;
ColumnMetaData tmp = metadata.getColumnMetaData(index);
if (newColumnAlias == null) {
columnMetaData = tmp;
} else {
try {
columnMetaData =
AdvResultSetMetaData.createColumnMetaData(
Class.forName(tmp.getColumnClassName()),
newColumnAlias, tmp.getTableName()
);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
} // else: see setCorrelatedTuples()
}
} else { // if the columnMetaData was already set
try { // but didn't have the tablename/alias specified yet (from
// CONCAT etc)
if (columnMetaData.getTableName().equals("")
&& newAlias != null && !newAlias.equals("")) {
ColumnMetaData cmd = columnMetaData;
// build a new one with the old class, (column)name and the
// new alias
columnMetaData =
AdvResultSetMetaData.createColumnMetaData(
Class.forName(cmd.getColumnClassName()),
cmd.getColumnName(),
newAlias
);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
/**
* When used in a JOIN-Predicate, set the {@link AdvResultSetMetaData}s of
* the the left and right Cursors that are joined. So the Column can decide
* whether to use the left or the right tuple in
* {@link Column#invoke(Tuple, Tuple) invoke(left, right)}.<br>
* The metadatas will be also be passed on to contained Columns.
*
* @param leftMetaData
* AdvResultSetMetaData of the left cursor being joined
* @param rightMetaData
* AdvResultSetMetaData of the right cursor being joined
*/
public void setMetaDatas(AdvResultSetMetaData leftMetaData, AdvResultSetMetaData rightMetaData){
// set metadata for contained columns
if (containedColumns != null) {
for (Column col : containedColumns) {
col.setMetaDatas(leftMetaData, rightMetaData);
}
}
if(columnIndex>0){
throw new RuntimeException("You may NOT specify a Column with its index"
+" (col("+columnIndex+")) in a join predicate, because it's not clear"
+" if it refers to the left or right Cursor being joined.");
}
if (haveBoundObject) { // this is a value from val()
// the columnMetaData is needed in ADD etc (specifically checkForNumber) and the
// comparators (createComp) to find out the Columns class
if(columnMetaData == null)
columnMetaData = AdvResultSetMetaData.createColumnMetaData(boundObject.getClass(),
this.columnAlias, "");
} else { // this is not a value from val()
if (columnAlias == null || columnAlias.equals("")) {
throw new RuntimeException(
"You didn't specify a name for a Column in a join-Predicate!");
}
int index = -1;
index = leftMetaData.getIndexByAliasAndName(columnAlias);
if (index > 0) { // the given alias.name matches to a column of the
// left tuple, so use it
int checkIndex = rightMetaData.getIndexByAliasAndName(columnAlias);
if (checkIndex > 0) {
throw new RuntimeException("You can't use \""
+columnAlias+"\" to specify a column in the "
+ "join-predicate, because it maps to a column in both cursors!");
}
// the columnMetaData is needed in ADD etc (specifically
// checkForNumber) and the
// comparators (createComp) to find out the Columns class
columnMetaData = leftMetaData.getColumnMetaData(index);
useLeftTuple = true;
} else { // if it's not in the left cursor, it should be in the
// right one..
index = rightMetaData.getIndexByAliasAndName(columnAlias);
if (index < 1) {
throw new RuntimeException(
"You didn't specify a valid name for a Column in a join-Predicate! ("
+ columnAlias + ")");
}
columnMetaData = rightMetaData.getColumnMetaData(index);
useLeftTuple = false; // use the right tuple.
}
columnIndex = index;
}
}
/**
* Add columns "contained" in this one so metadata and correlated tuples can
* be set for them.<br>
* You have contained columns with CONCAT(), ADD() etc.<br>
* <b>Example:</b><br>
* <code>col("al1.nameX").CONCAT(col("al2.nameY"), "foo")</code> creates a
* new Column named foo from the Columns "al1.nameX" and "al2.nameY" that
* should be present in your cursor or the correlated tuples.<br>
* Metadata will be set for this Column (in select/where/...), but not for
* "al1.nameX" and "al2.nameY" which actually need the metadata to map to
* the right column.. So "foo" gets a list of those Columns and when
* setMetaData() or setCorrelatedTuples() is called upon "foo", it will call
* it on "al1.nameX" and "al2.nameY".
*
* @param cols Columns to be contained in this Column
*/
public void addContainedColumns(Column... cols) {
if (containedColumns == null) {
containedColumns = new ArrayList<Column>(cols.length);
}
containedColumns.addAll(Arrays.asList(cols));
}
/**
* Pass a list of tuples along with their metadata from "outer" cursors in
* case this is a (correlated) subquery. The list has to be sorted from
* inside to outside (in case of nested subqueries). With the name given to
* this column the object will be fetched from the the first tuple with a
* column with according alias and name and this object will be bound to
* this column.<br>
* This is meant to be used with EXISTS, ALL and ANY clauses.
*
* @param corrTuples
* a List of tuples with their related metadata from "outer"
* cursors for correlated subqueries
*
* @see AdvTupleCursor#getCorrTuples()
*/
public void setCorrelatedTuples(
List<MapEntry<AdvResultSetMetaData, Tuple>> corrTuples) {
if (corrTuples == null) {
return;
}
// set correlated Tuples for contained Columns
if (containedColumns != null) {
for (Column col : containedColumns) {
col.setCorrelatedTuples(corrTuples);
}
}
// fromCorrTuple => bound object is from a correlated tuple and may be overwritten
// || this column is not fetched from the "regular" tuples and is not a "val" either
// so we try if might be from a correlated tuple
if (fromCorrTuple || columnIndex < 1 && !haveBoundObject) {
// try to find the object to be bound in the correlated tuples -
// sorted from inside to outside
for (MapEntry<AdvResultSetMetaData, Tuple> ent : corrTuples) {
int index = ent.getKey().getIndexByAliasAndName(this.columnAlias);
if (index > 0) { // a valid index => get boundObject from
// associated tuple and we're done.
// TODO: possible optimization: if a corr tuple has been
// used once, remember which tuple in the
// corrTuples-list it was (and which attribute of that
// tuple) and access it directly next time
boundObject = ent.getValue().getObject(index);
columnMetaData = ent.getKey().getColumnMetaData(index); // get the metadata
haveBoundObject = true;
fromCorrTuple = true;
return;
}
}
}
}
/**
* @return the index of the column in a tuple this Column represents
*/
public int getColumnIndex() {
return columnIndex;
}
/**
* @return the {@link ColumnMetaData} for this Column, if it has a name.
* @throws a RuntimeException if this Column has no name (e.g. an unnamed val())
*/
public ColumnMetaData getColumnMetaData() {
return columnMetaData;
}
/* *********************************
* Comparisons (EQ, LEQ, GT, ....) *
***********************************/
/**
* Returns an {@link AdvPredicate} comparing this Column with another Column for <b>EQ</b>uality
* @param col the other Column
*/
public AdvPredicate EQ(final Column col) {
return EQ(col, null);
}
/**
* Returns an {@link AdvPredicate} comparing this Column with another Column for <b>EQ</b>uality
* with a custom {@link Comparator}
* @param col the other Column
* @param cmp your custom comparator (null for default)
*/
@SuppressWarnings("unchecked")
public AdvPredicate EQ(final Column col, final Comparator cmp) {
AdvPredicate ret = new AdvPredicate() {
Comparator<Object> comp = cmp;
@Override
// normale case (where(), ...)
public boolean invoke(Tuple tuple) {
Object o1 = Column.this.invoke(tuple);
Object o2 = col.invoke(tuple);
if (comp == null) {
try { // try to use a real comparator
comp = createComp(Column.this, col);
} catch(Exception e){
// if that fails just use plain equals
comp = equalsPseudoComp;
}
}
return comp.compare(o1, o2) == 0;
}
@Override
// case of join predicate
public boolean invoke(Tuple lTuple, Tuple rTuple) {
Object o1 = Column.this.invoke(lTuple, rTuple);
Object o2 = col.invoke(lTuple, rTuple);
if (comp == null) {
try { // try to use a real comparator
comp = createComp(Column.this, col);
} catch(Exception e){
// if that fails just use plain equals
comp = equalsPseudoComp;
}
}
return comp.compare(o1, o2) == 0;
}
};
// add *this* and col as contained Columns so they will have metadata
// and correlated tuples set later on (in where() or join())
ret.addContainedColumns(this, col);
return ret;
}
/**
* Returns an AdvPredicate performing a comparison for <b>EQ</b>uality with a (possibly correlated)
* ANY/ALL-subquery.<br>
* <b>Note:</b> The subquery must return a cursor with <i>only a single column</i>!<br>
* <b>Example</b>:
* <code><pre> SELECT * FROM c1
* WHERE c1.a1 = ANY (SELECT b1 FROM c2)</pre></code>
* will be something like: <br>
* <code>
* c1.where( col("c1.a1").EQ( SubQueryType.ANY, c2.select(col("c2.b1")) ) )</code>
*
* @param type the {@link SubQueryType} of the subquery (SubQueryType.ANY or SubQueryType.ALL)
* @param subquery the subquery itself (an AdvTupleCursor), possibly correlated
* @return an appropriate AdvPredicate to be used in where()
*/
public AdvPredicate EQ(final SubQueryType type, final AdvTupleCursor subquery) {
return EQ(type, subquery, null);
}
/**
* Returns an AdvPredicate performing a comparison for <b>EQ</b>uality with a custom comparator with a
* (possibly correlated) ANY/ALL-subquery.<br>
* <b>Note:</b> The subquery must return a cursor with <i>only a single column</i>!<br>
*
* @param type the {@link SubQueryType} of the subquery (SubQueryType.ANY or SubQueryType.ALL)
* @param subquery the subquery itself (an AdvTupleCursor), possibly correlated
* @param cmp a {@link Comparator} to be used or <b>null</b> to use a standard comparator
* @return an appropriate AdvPredicate to be used in where()
*
* @see #EQ(SubQueryType, AdvTupleCursor) EQ(type, subquery) for a small example how ANY/ALL-subqueries are used
*/
@SuppressWarnings( { "serial", "unchecked" })
public AdvPredicate EQ(final SubQueryType type, final AdvTupleCursor subquery,
final Comparator cmp)
{
Predicate<Object> pred = new AbstractPredicate<Object>() {
Comparator<Object> comp = cmp;
@Override
public boolean invoke(Object o1, Object o2) {
if (comp == null) { // if no comparator was set create one.
try {
String outerclassname = Column.this.columnMetaData.getColumnClassName();
String innerclassname = subquery.getResultSetMetaData().getColumnClassName(1);
// if they're of the same class plain equals is ok
if (comp == null) {
try { // try to use a real comparator
comp = createComp(outerclassname, innerclassname);
} catch(Exception e){
// if that fails just use plain equals
comp = equalsPseudoComp;
}
}
} catch (Exception e) {
throw new RuntimeException("Can't compare "
+ Column.this.columnAlias + " and the subquery "
+ subquery.getResultSetMetaData().getAlias()
+ " because: " + e.getMessage(), e);
}
}
return comp.compare(o1, o2) == 0;
}
};
return any_all_subquery(type, subquery, pred);
}
/**
* Returns an {@link AdvPredicate} comparing this Column with another Column for being
* <b>N</b>ot <b>EQ</b>ual
* @param col the other Column
*/
public AdvPredicate NEQ(final Column col) {
return NEQ(col, null);
}
/**
* Returns an {@link AdvPredicate} comparing this Column with another Column for being
* <b>N</b>ot <b>EQ</b>ual with a custom {@link Comparator}
* @param col the other Column
* @param cmp your custom comparator (null for default)
*/
@SuppressWarnings("unchecked")
public AdvPredicate NEQ(final Column col, final Comparator cmp) {
return AdvPredicate.NOT(EQ(col, cmp));
}
/**
* Returns an AdvPredicate performing a comparison for <b>N</b>ot being <b>EQ</b>ual with a
* (possibly correlated) ANY/ALL-subquery.<br>
* <b>Note:</b> The subquery must return a cursor with <i>only a single column</i>!<br>
*
* @param type the {@link SubQueryType} of the subquery (SubQueryType.ANY or SubQueryType.ALL)
* @param subquery the subquery itself (an AdvTupleCursor), possibly correlated
* @return an appropriate AdvPredicate to be used in where()
*
* @see #EQ(SubQueryType, AdvTupleCursor) EQ(type, subquery) for a small example how ANY/ALL-subqueries are used
*/
public AdvPredicate NEQ(final SubQueryType type, final AdvTupleCursor subquery) {
return NEQ(type, subquery, null);
}
/**
* Returns an AdvPredicate performing a comparison for <b>N</b>ot being <b>EQ</b>ual with a custom comparator
* with a (possibly correlated) ANY/ALL-subquery.<br>
* <b>Note:</b> The subquery must return a cursor with <i>only a single column</i>!<br>
*
* @param type the {@link SubQueryType} of the subquery (SubQueryType.ANY or SubQueryType.ALL)
* @param subquery the subquery itself (an AdvTupleCursor), possibly correlated
* @param cmp a {@link Comparator} to be used or <b>null</b> to use a standard comparator
* @return an appropriate AdvPredicate to be used in where()
*
* @see #EQ(SubQueryType, AdvTupleCursor) EQ(type, subquery) for a small example how ANY/ALL-subqueries are used
*/
@SuppressWarnings("unchecked")
public AdvPredicate NEQ(final SubQueryType type, final AdvTupleCursor subquery,
final Comparator cmp)
{
return AdvPredicate.NOT(EQ(type, subquery, cmp));
}
/**
* Returns an {@link AdvPredicate} checking if this Column's value is <b>G</b>reater <b>T</b>han the other
* Column's value.
* @param col the other Column
*/
public AdvPredicate GT(final Column col) {
return GT(col, null);
}
/**
* Returns an {@link AdvPredicate} checking if this Column's value is <b>G</b>reater <b>T</b>han than the other
* Column's value with a custom {@link Comparator}.
* @param col the other Column
* @param cmp your custom comparator (null for default)
*/
@SuppressWarnings("unchecked")
public AdvPredicate GT(final Column col, final Comparator cmp) {
AdvPredicate ret = new AdvPredicate() {
// wenn man nen eigenen comparator setzen wollte, koennte man das
// einfach hier tun und in
// invoke wuerd sich nix aendern
Comparator comp = cmp;
@Override
// normale case (where(), ...)
public boolean invoke(Tuple tuple) {
Object o1 = Column.this.invoke(tuple);
Object o2 = col.invoke(tuple);
if (comp == null) {
try {
comp = createComp(Column.this, col);
} catch (Exception e) {
throw new RuntimeException("Can't compare "
+ Column.this.columnAlias + " and " + col.columnAlias
+ " because: " + e.getMessage(), e);
}
}
return comp.compare(o1, o2) > 0;
}
@Override
// case of join predicate
public boolean invoke(Tuple lTuple, Tuple rTuple) {
Object o1 = Column.this.invoke(lTuple, rTuple);
Object o2 = col.invoke(lTuple, rTuple);
if (comp == null) {
try {
comp = createComp(Column.this, col);
} catch (Exception e) {
throw new RuntimeException("Can't compare "
+ Column.this.columnAlias + " and " + col.columnAlias
+ " because: " + e.getMessage(), e);
}
}
return comp.compare(o1, o2) > 0;
}
};
// add *this* and col as contained Columns so they will have metadata
// and correlated tuples set later on (in where() or join())
ret.addContainedColumns(this, col);
return ret;
}
/**
* Returns an AdvPredicate checking if this Columns value is <b>G</b>reater <b>T</b>han ANY/ALL
* values of the (possibly correlated) subquery with a custom {@link Comparator}.<br>
* <b>Note:</b> The subquery must return a cursor with <i>only a single column</i>!<br>
*
* @param type the {@link SubQueryType} of the subquery (SubQueryType.ANY or SubQueryType.ALL)
* @param subquery the subquery itself (an AdvTupleCursor), possibly correlated
* @return an appropriate AdvPredicate to be used in where()
*
* @see #EQ(SubQueryType, AdvTupleCursor) EQ(type, subquery) for a small example how ANY/ALL-subqueries are used
*/
public AdvPredicate GT(final SubQueryType type, final AdvTupleCursor subquery) {
return GT(type, subquery, null);
}
/**
* Returns an AdvPredicate checking if this Columns value is <b>G</b>reater <b>T</b>han ANY/ALL
* values of the (possibly correlated) subquery.<br>
* <b>Note:</b> The subquery must return a cursor with <i>only a single column</i>!<br>
*
* @param type the {@link SubQueryType} of the subquery (SubQueryType.ANY or SubQueryType.ALL)
* @param subquery the subquery itself (an AdvTupleCursor), possibly correlated
* @param cmp a {@link Comparator} to be used or <b>null</b> to use a standard comparator
* @return an appropriate AdvPredicate to be used in where()
*
* @see #EQ(SubQueryType, AdvTupleCursor) EQ(type, subquery) for a small example how ANY/ALL-subqueries are used
*/
@SuppressWarnings( { "serial", "unchecked" })
public AdvPredicate GT(final SubQueryType type, final AdvTupleCursor subquery, final Comparator cmp){
// a simple predicate to be used by any_all_subquery
Predicate<Object> pred = new AbstractPredicate<Object>() {
Comparator comp = cmp;
@Override
public boolean invoke(Object o1, Object o2) {
if (comp == null) {
try {
String outerclassname = Column.this.columnMetaData.getColumnClassName();
String innerclassname = subquery.getResultSetMetaData().getColumnClassName(1);
comp = createComp(outerclassname, innerclassname);
} catch (Exception e) {
throw new RuntimeException("Can't compare "
+ Column.this.columnAlias + " and the subquery "
+ subquery.getResultSetMetaData().getAlias()
+ " because: " + e.getMessage(), e);
}
}
return comp.compare(o1, o2) > 0;
}
};
return any_all_subquery(type, subquery, pred);
}
/**
* Returns an {@link AdvPredicate} checking if this Column's value is <b>L</b>ess <b>T</b>han the other
* Column's value.
* @param col the other Column
*/
public AdvPredicate LT(final Column col) {
return LT(col, null);
}
/**
* Returns an {@link AdvPredicate} checking if this Column's value is <b>L</b>ess <b>T</b>han than the other
* Column's value with a custom {@link Comparator}.
* @param col the other Column
* @param cmp your custom comparator (null for default)
*/
@SuppressWarnings("unchecked")
public AdvPredicate LT(final Column col, final Comparator cmp) {
AdvPredicate ret = new AdvPredicate() {
// wenn man nen eigenen comparator setzen wollte, koennte man das
// einfach hier tun und in
// invoke wuerd sich nix aendern
Comparator comp = cmp;
@Override
// normale case (where(), ...)
public boolean invoke(Tuple tuple) {
Object o1 = Column.this.invoke(tuple);
Object o2 = col.invoke(tuple);
if (comp == null) {
try {
comp = createComp(Column.this, col);
} catch (Exception e) {
throw new RuntimeException("Can't compare "
+ Column.this.columnAlias + " and " + col.columnAlias
+ " because: " + e.getMessage(), e);
}
}
return comp.compare(o1, o2) < 0;
}
@Override
// case of join predicate
public boolean invoke(Tuple lTuple, Tuple rTuple) {
Object o1 = Column.this.invoke(lTuple, rTuple);
Object o2 = col.invoke(lTuple, rTuple);
if (comp == null) {
try {
comp = createComp(Column.this, col);
} catch (Exception e) {
throw new RuntimeException("Can't compare "
+ Column.this.columnAlias + " and " + col.columnAlias
+ " because: " + e.getMessage(), e);
}
}
return comp.compare(o1, o2) < 0;
}
};
// add *this* and col as contained Columns so they will have metadata
// and correlated tuples set later on (in where() or join())
ret.addContainedColumns(this, col);
return ret;
}
/**
* Returns an AdvPredicate checking if this Columns value is <b>L</b>ess <b>T</b>han ANY/ALL
* values of the (possibly correlated) subquery.<br>
* <b>Note:</b> The subquery must return a cursor with <i>only a single column</i>!<br>
*
* @param type the {@link SubQueryType} of the subquery (SubQueryType.ANY or SubQueryType.ALL)
* @param subquery the subquery itself (an AdvTupleCursor), possibly correlated
* @return an appropriate AdvPredicate to be used in where()
*
* @see #EQ(SubQueryType, AdvTupleCursor) EQ(type, subquery) for a small example how ANY/ALL-subqueries are used
*/
public AdvPredicate LT(final SubQueryType type, final AdvTupleCursor subquery) {
return LT(type, subquery, null);
}
/**
* Returns an AdvPredicate checking if this Columns value is <b>L</b>ess <b>T</b>han ANY/ALL
* values of the (possibly correlated) subquery with a custom {@link Comparator}.<br>
* <b>Note:</b> The subquery must return a cursor with <i>only a single column</i>!<br>
*
* @param type the {@link SubQueryType} of the subquery (SubQueryType.ANY or SubQueryType.ALL)
* @param subquery the subquery itself (an AdvTupleCursor), possibly correlated
* @param cmp a {@link Comparator} to be used or <b>null</b> to use a standard comparator
* @return an appropriate AdvPredicate to be used in where()
*
* @see #EQ(SubQueryType, AdvTupleCursor) EQ(type, subquery) for a small example how ANY/ALL-subqueries are used
*/
@SuppressWarnings( { "serial", "unchecked" })
public AdvPredicate LT(final SubQueryType type, final AdvTupleCursor subquery, Comparator cmp){
Predicate<Object> pred = new AbstractPredicate<Object>() {
Comparator comp = null;
@Override
public boolean invoke(Object o1, Object o2) {
if (comp == null) {
try {
String outerclassname = Column.this.columnMetaData.getColumnClassName();
String innerclassname = subquery.getResultSetMetaData().getColumnClassName(1);
comp = createComp(outerclassname, innerclassname);
} catch (Exception e) {
throw new RuntimeException("Can't compare "
+ Column.this.columnAlias + " and the subquery "
+ subquery.getResultSetMetaData().getAlias()
+ " because: " + e.getMessage(), e);
}
}
return comp.compare(o1, o2) < 0;
}
};
return any_all_subquery(type, subquery, pred);
}
/**
* Returns an {@link AdvPredicate} checking if this Column's value is
* <b>G</b>reater or <b>EQ</b>ual than the other Column's value.
* @param col the other Column
*/
public AdvPredicate GEQ(final Column col) {
return GEQ(col, null);
}
/**
* Returns an {@link AdvPredicate} checking if this Column's value is <b>G</b>reater
* or <b>EQ</b>ual than the other Column's value with a custom {@link Comparator}.
* @param col the other Column
* @param cmp your custom comparator (null for default)
*/
@SuppressWarnings("unchecked")
public AdvPredicate GEQ(final Column col, final Comparator cmp) {
return AdvPredicate.NOT(LT(col, cmp));
}
/**
* Returns an AdvPredicate checking if this Columns value is <b>G</b>reater or <b>EQ</b>ual
* than ANY/ALL values of the (possibly correlated) subquery.<br>
* <b>Note:</b> The subquery must return a cursor with <i>only a single column</i>!<br>
*
* @param type the {@link SubQueryType} of the subquery (SubQueryType.ANY or SubQueryType.ALL)
* @param subquery the subquery itself (an AdvTupleCursor), possibly correlated
* @return an appropriate AdvPredicate to be used in where()
*
* @see #EQ(SubQueryType, AdvTupleCursor) EQ(type, subquery) for a small example how ANY/ALL-subqueries are used
*/
public AdvPredicate GEQ(final SubQueryType type, final AdvTupleCursor subquery) {
return GEQ(type, subquery, null);
}
/**
* Returns an AdvPredicate checking if this Columns value is <b>G</b>reater or <b>EQ</b>ual
* than ANY/ALL values of the (possibly correlated) subquery with a custom {@link Comparator}.<br>
* <b>Note:</b> The subquery must return a cursor with <i>only a single column</i>!<br>
*
* @param type the {@link SubQueryType} of the subquery (SubQueryType.ANY or SubQueryType.ALL)
* @param subquery the subquery itself (an AdvTupleCursor), possibly correlated
* @param cmp a {@link Comparator} to be used or <b>null</b> to use a standard comparator
* @return an appropriate AdvPredicate to be used in where()
*
* @see #EQ(SubQueryType, AdvTupleCursor) EQ(type, subquery) for a small example how ANY/ALL-subqueries are used
*/
@SuppressWarnings("unchecked")
public AdvPredicate GEQ(final SubQueryType type, final AdvTupleCursor subquery,
final Comparator cmp)
{
return AdvPredicate.NOT(LT(type, subquery, cmp));
}
/**
* Returns an {@link AdvPredicate} checking if this Column's value is <b>L</b>ess
* or <b>EQ</b>ual than the other Column's value.
* @param col the other Column
* @param cmp your custom comparator (null for default)
*/
public AdvPredicate LEQ(final Column col) {
return LEQ(col, null);
}
/**
* Returns an {@link AdvPredicate} checking if this Column's value is <b>L</b>ess
* or <b>EQ</b>ual than the other Column's value with a custom {@link Comparator}.
* @param col the other Column
* @param cmp your custom comparator (null for default)
*/
@SuppressWarnings("unchecked")
public AdvPredicate LEQ(final Column col, final Comparator cmp) {
return AdvPredicate.NOT(GT(col, cmp));
}
/**
* Returns an AdvPredice checking if this Columns value is <b>L</b>ess or <b>EQ</b>ual
* than ANY/ALL values of the (possibly correlated) subquery.<br>
* <b>Note:</b> The subquery must return a cursor with <i>only a single column</i>!<br>
*
* @param type the {@link SubQueryType} of the subquery (SubQueryType.ANY or SubQueryType.ALL)
* @param subquery the subquery itself (an AdvTupleCursor), possibly correlated
* @return an appropriate AdvPredicate to be used in where()
*
* @see #EQ(SubQueryType, AdvTupleCursor) EQ(type, subquery) for a small example how ANY/ALL-subqueries are used
*/
public AdvPredicate LEQ(final SubQueryType type, final AdvTupleCursor subquery) {
return LEQ(type, subquery, null);
}
/**
* Returns an AdvPredice checking if this Columns value is <b>L</b>ess or <b>EQ</b>ual
* than ANY/ALL values of the (possibly correlated) subquery with a custom {@link Comparator}.<br>
* <b>Note:</b> The subquery must return a cursor with <i>only a single column</i>!<br>
*
* @param type the {@link SubQueryType} of the subquery (SubQueryType.ANY or SubQueryType.ALL)
* @param subquery the subquery itself (an AdvTupleCursor), possibly correlated
* @param cmp a {@link Comparator} to be used or <b>null</b> to use a standard comparator
* @return an appropriate AdvPredicate to be used in where()
*
* @see #EQ(SubQueryType, AdvTupleCursor) EQ(type, subquery) for a small example how ANY/ALL-subqueries are used
*/
@SuppressWarnings("unchecked")
public AdvPredicate LEQ(final SubQueryType type, final AdvTupleCursor subquery,
final Comparator cmp)
{
return AdvPredicate.NOT(GT(type, subquery, cmp));
}
/**
* Returns an {@link AdvPredicate} checking if this {@link Column}s value as a String (using
* {@link Object#toString()}) matches with the given regular expression.<br>
* The regular expression uses the java.util.regex classes, so see {@link Pattern} for
* details on how the regex has to look like.
*
* @param regex the regular expression used for comparison
* @return an AdvPredicate checking if this Columns value as a String matches the given regex
*/
public AdvPredicate LIKE(final String regex){
AdvPredicate ret = new AdvPredicate() {
private Pattern p;
{
try {
p = Pattern.compile(regex);
} catch (PatternSyntaxException e) {
throw new RuntimeException("You used an illegal regular expression pattern in "
+"LIKE("+regex+"): "+e.getMessage(), e);
}
}
@Override
// normale case (where(), ...)
public boolean invoke(Tuple tuple) {
Object o = Column.this.invoke(tuple);
Matcher m = p.matcher(o.toString());
return m.matches();
}
@Override
// case of join predicate
public boolean invoke(Tuple lTuple, Tuple rTuple) {
Object o = Column.this.invoke(lTuple, rTuple);
Matcher m = p.matcher(o.toString());
return m.matches();
}
};
return ret;
}
/* ***************************************************
* Operations (mostly arithmetic like ADD, MUL, ...) *
* ***************************************************/
/**
* @param col the Column this Column will be concatenated with
* @return a Column containing a String that is a concatenaton of the strings (Object.toString)
* from this Column and col
*/
public Column CONCAT(Column col) {
return CONCAT(col, null);
}
/**
* @param col the Column this Column will be concatenated with
* @param name the created Columns name
* @return a Column with given name containing a String that is a concatenaton of the strings
* (Object.toString) from this Column and col
*/
@SuppressWarnings("serial")
public Column CONCAT(final Column col, String name) {
final boolean noName = (name == null);
if (name == null) {
name = "concat";
}
// den alias setzen wir spaeter in setMetaData() .. bisschen hackish,
// aber mir faellt nix besseres ein :/
final ColumnMetaData cmd = AdvResultSetMetaData.createColumnMetaData(
String.class, name, null);
Column ret = new Column(name, cmd) {
@Override
public ColumnMetaData getColumnMetaData() {
if(noName)
throw new RuntimeException("If you want to use CONCAT to create a new column"
+" in in a tuple (e.g. in select()), give it a name, i.e. use"
+" CONCAT(col, name)!");
else
return super.getColumnMetaData();
}
@Override
public String invoke(Tuple tuple) {
return Column.this.invoke(tuple).toString().concat(
col.invoke(tuple).toString());
}
@Override
// fuer den join-fall (columns koennen aus linkem oder rechtem tupel
// sein)
public Object invoke(Tuple left, Tuple right) {
return Column.this.invoke(left, right).toString()
.concat(col.invoke(left, right).toString());
}
};
// add *this* and col as contained Columns so they'll get metadata and
// correlated tuples
ret.addContainedColumns(this, col);
return ret;
}
/**
* Performs summation of this Column's value with <b>col</b>'s value.<br>
* <i>Both need to contain Numbers, i.e. evaluate to values of a class
* <b>derived from {@link java.lang.Number}</b>!</i>
*
* @param col
* @return a Column containing the result of the calculation as a {@link Double}
*/
public Column ADD(final Column col) {
return ADD(col, null);
}
/**
* Performs summation ("plus") of this Column's value with <b>col</b>'s value.<br>
* <i>Both need to contain Numbers, i.e. evaluate to values of a class
* <b>derived from {@link java.lang.Number}</b>!</i>
*
* @param col
* @param name name of the new Column (if it's to be an actual column in a tuple)
* @return a Column containing the result of the calculation as a {@link Double}
*/
public Column ADD(final Column col, String name) {
Function<Number, Double> op = new AbstractFunction<Number, Double>() {
private static final long serialVersionUID = 1L;
@Override
public Double invoke(Number n1, Number n2) {
return n1.doubleValue() + n2.doubleValue();
}
};
return arithm_op(this, col, op, name);
}
/**
* Performs subtraction ("minus") of this Column's value with <b>col</b>'s value.<br>
* <i>Both need to contain Numbers, i.e. evaluate to values of a class
* <b>derived from {@link java.lang.Number}</b>!</i>
*
* @param col
* @return a Column containing the result of the calculation as a {@link Double}
*/
public Column SUB(final Column col) {
return SUB(col, null);
}
/**
* Performs subtraction ("minus") of this Column's value with <b>col</b>'s value.<br>
* <i>Both need to contain Numbers, i.e. evaluate to values of a class
* <b>derived from {@link java.lang.Number}</b>!</i>
*
* @param col
* @param name name of the new Column (if it's to be an actual column in a tuple)
* @return a Column containing the result of the calculation as a {@link Double}
*/
public Column SUB(final Column col, String name) {
Function<Number, Double> op = new AbstractFunction<Number, Double>() {
private static final long serialVersionUID = 1L;
@Override
public Double invoke(Number n1, Number n2) {
return n1.doubleValue() - n2.doubleValue();
}
};
return arithm_op(this, col, op, name);
}
/**
* Performs multiplication of this Column's value with <b>col</b>'s value.<br>
* <i>Both need to contain Numbers, i.e. evaluate to values of a class
* <b>derived from {@link java.lang.Number}</b>!</i>
*
* @param col
* @return a Column containing the result of the calculation as a {@link Double}
*/
public Column MUL(final Column col) {
return MUL(col, null);
}
/**
* Performs multiplication of this Column's value with <b>col</b>'s value.<br>
* <i>Both need to contain Numbers, i.e. evaluate to values of a class
* <b>derived from {@link java.lang.Number}</b>!</i>
*
* @param col
* @param name name of the new Column (if it's to be an actual column in a tuple)
* @return a Column containing the result of the calculation as a {@link Double}
*/
public Column MUL(final Column col, String name) {
Function<Number, Double> op = new AbstractFunction<Number, Double>() {
private static final long serialVersionUID = 1L;
@Override
public Double invoke(Number n1, Number n2) {
return n1.doubleValue() * n2.doubleValue();
}
};
return arithm_op(this, col, op, name);
}
/**
* Performs division of this Column's value by <b>col</b>'s value.<br>
* <i>Both need to contain Numbers, i.e. evaluate to values of a class
* <b>derived from {@link java.lang.Number}</b>!</i>
*
* @param col
* @return a Column containing the result of the calculation as a {@link Double}
*/
public Column DIV(final Column col) {
return DIV(col, null);
}
/**
* Performs division of this Column's value by <b>col</b>'s value.<br>
* <i>Both need to contain Numbers, i.e. evaluate to values of a class
* <b>derived from {@link java.lang.Number}</b>!</i>
*
* @param col
* @param name name of the new Column (if it's to be an actual column in a tuple)
* @return a Column containing the result of the calculation as a {@link Double}
*/
public Column DIV(final Column col, String name) {
Function<Number, Double> op = new AbstractFunction<Number, Double>() {
private static final long serialVersionUID = 1L;
@Override
public Double invoke(Number n1, Number n2) {
return n1.doubleValue() / n2.doubleValue();
}
};
return arithm_op(this, col, op, name);
}
/**
* Performs an exponentiation with this Column's value being the base and
* <b>col</b>'s value being the exponent.<br>
* <i>Both need to contain Numbers, i.e. evaluate to values of a class
* <b>derived from {@link java.lang.Number}</b>!</i>
*
* @param col
* @return a Column containing the result of the calculation as a {@link Double}
*/
public Column POW(final Column col) {
return POW(col, null);
}
/**
* Performs an exponentiation with this Column's value being the base and
* <b>col</b>'s value being the exponent.<br>
* <i>Both need to contain Numbers, i.e. evaluate to values of a class
* <b>derived from {@link java.lang.Number}</b>!</i>
*
* @param col
* @param name name of the new Column (if it's to be an actual column in a tuple)
* @return a Column containing the result of the calculation as a {@link Double}
*/
public Column POW(final Column col, String name) {
Function<Number, Double> op = new AbstractFunction<Number, Double>() {
private static final long serialVersionUID = 1L;
@Override
public Double invoke(Number n1, Number n2) {
return Math.pow(n1.doubleValue(), n2.doubleValue());
}
};
return arithm_op(this, col, op, name);
}
/* ****************************************************************************
* Helping functions that make implementing operations and comparisons easier *
******************************************************************************/
/**
* Creates an ALL/ANY subquery on the given cursor with the given Predicate.<br>
* <b>Example</b>:<br>
* In AdvPredicate EQ(type, subquery) you just create a Predicate that
* compares two Objects(!) for equality and pass it on to this method: <br>
* <code><pre> public AdvPredicate simple_EQ(final SubQueryType type, final AdvTupleCursor subquery){
* Predicate<Object> pred = new AbstractPredicate<Object>() {
* {@code @Override}
* public boolean invoke(Object o1, Object o2) {
* return o1.equals(o2);
* }
* };
* return any_all_subquery(type, subquery, pred);
* } </pre></code>
*
* @param type
* the {@link SubQueryType} of the subquery (SubQueryType.ANY or
* SubQueryType.ALL)
* @param subquery
* the subquery itself, possibly correlated
* @param pred
* the predicate used to compare "outer" and "inner" Objects
* @return an appropriate AdvPredicate to be used in where()
*/
protected AdvPredicate any_all_subquery(final SubQueryType type, final AdvTupleCursor subquery,
final Predicate<Object> pred)
{
// subquery should not cache - except for the first cursor, so it's resettable.
// so if "only cache if absolutely necessary" via MAYBE_FIRST isn't
// enforced, we'll enable caching the first cursor (even if the wrapped cursor
// is resettable)
if (!subquery.getCachingStrategy().equals(CachingStrategy.MAYBE_FIRST)
&& !subquery.getCachingStrategy().equals(CachingStrategy.ONLY_FIRST)) {
subquery.setCachingStrategy(CachingStrategy.ONLY_FIRST, true);
}
AdvPredicate ret = new AdvPredicate() {
@Override
public boolean invoke(Tuple tuple) {
/* ********************************
* correlated tuples for subquery *
**********************************/
// the current tuple
MapEntry<AdvResultSetMetaData, Tuple> newCorrTuple =
new MapEntry<AdvResultSetMetaData, Tuple>(getMetaData(), tuple);
// create list of correlated Tuples (current tuple and the ones
// "inherited" from the containing cursor)
List<MapEntry<AdvResultSetMetaData, Tuple>> corrTuples =
new LinkedList<MapEntry<AdvResultSetMetaData, Tuple>>();
corrTuples.add(newCorrTuple); // add current tuple first
if (getCorrelatedTuples() != null) {
corrTuples.addAll(getCorrelatedTuples()); // tuples that were set in this
// predicate via setCorrelatedTuples()
}
// set correlated tuples in subquery's CorrTuplesReceivers (predicates, ..)
for (CorrTuplesReceiver ctr : subquery.getCorrTuplesRec()) {
ctr.setCorrelatedTuples(corrTuples);
}
/* ***********************
* actual implementation *
*************************/
try {
if (subquery.getResultSetMetaData().getColumnCount() != 1) {
throw new RuntimeException("Subqueries used with ANY/ALL are supposed to"
+" return just a single value per Tuple!");
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
/*
* if this is an ANY-subquery the result is false by default,
* because just one "true" tuple from the inner cursor is
* sufficient for this ANY-clause to be "true" if this is an
* ALL-subquery the result is true by default, because just one
* "false" tuple from the inner cursor is sufficient for this
* ALL-clause to be "false"
*/
boolean result = type == SubQueryType.ANY ? false : true;
// the "outer" object - from the current tuple WHERE
Object outerObj = Column.this.invoke(tuple);
while (subquery.hasNext()) {
// the "inner" Object from the subquery - remember the Tuple
// should have only one column, so it's at position 1
Object innerObj = subquery.next().getObject(1);
// in case of ANY: on first pred returning true, result is
// set to true and we break
// in case of ALL: on first pred returning false, result is
// set to false and we break
if (pred.invoke(outerObj, innerObj) != result) {
result = !result;
break;
}
}
subquery.reset(); // reset subquery for next tuple
return result;
}
@Override
public boolean invoke(Tuple argument0, Tuple argument1) {
throw new UnsupportedOperationException(
"Don't use ANY or ALL in a JOIN-predicate.");
}
};
// add *this* as contained Column so it will have metadata
// and correlated tuples set later on (in where())
ret.addContainedColumns(this);
return ret;
}
/**
* Creates a {@link Comparator} for Objects of the given classes, if they're
* {@link Comparable} (if they're not, an Exception will be thrown). <br>
* If they're Numbers, they will be casted to {@link Number} and their
* double values will be compared.<br>
* If they're not numbers one class needs to be a subclass of the other
* class (or they need to be of the same class) or they can't be compared.
*
* @param cl1 type of the left object to be compared
* @param cl2 type of the right object to be compared
* @return a Comparator comparing Objects of type cl1 and cl2 with cl1 as
* left argument and cl2 as right argument
* @throws Exception if the given classes can't be compared
*/
@SuppressWarnings("unchecked")
static Comparator<Object> createComp(Class<?> cl1, Class<?> cl2)
throws Exception {
if (!Comparable.class.isAssignableFrom(cl1)) {
throw new Exception("Class " + cl1.getName()
+ " is not comparable!");
}
if (!Comparable.class.isAssignableFrom(cl2)) {
throw new Exception("Class " + cl2.getName()
+ " is not comparable!");
}
Comparator<Object> cmp = null;
if (cl1.isAssignableFrom(cl2)) { // cl2 subclass of cl1 (or same class)
cmp = new Comparator<Object>() {
@Override
public int compare(Object obj1, Object obj2) {
if(obj1 == null){ // we don't want nullpointer-exceptions
if(obj2 == null)
return 0;
return -1; // null is smaller than anything else
} else if(obj2 == null)
return 1;
Comparable o1 = (Comparable) obj1;
Comparable o2 = (Comparable) obj2;
return o1.compareTo(o2);
}
};
} else if (cl2.isAssignableFrom(cl1)) { // cl1 subclass of cl2
cmp = new Comparator<Object>() {
@Override
public int compare(Object obj1, Object obj2) {
if(obj1 == null){
if(obj2 == null)
return 0;
return 1;
} else if(obj2 == null)
return -1;
Comparable o1 = (Comparable) obj1;
Comparable o2 = (Comparable) obj2;
return -1 * o2.compareTo(o1);
}
};
} else if (Number.class.isAssignableFrom(cl1)
&& Number.class.isAssignableFrom(cl2)) {
// both are numbers
cmp = new Comparator<Object>() {
@Override
public int compare(Object o1, Object o2) {
if(o1 == null){
if(o2 == null)
return 0;
return -1;
} else if(o2 == null)
return 1;
Number n1 = (Number) o1;
Number n2 = (Number) o2;
return ComparableComparator.DOUBLE_COMPARATOR
.compare(n1.doubleValue(), n2.doubleValue());
}
};
}
if (cmp != null) {
return cmp;
} else {
throw new Exception("Can't compare those types: " + cl1.getName()
+ " and " + cl2.getName());
}
}
/**
* Creates a {@link Comparator} for Objects contained in the given Columns,
* if they're {@link Comparable} (if they're not, an Exception will be
* thrown). <br>
* If they're Numbers, they will be casted to {@link Number} and their
* double values will be compared.<br>
* If they're not numbers one class needs to be a subclass of the other
* class (or they need to be of the same class) or they can't be compared.
*
* @param col1
* Column containing the left object to be compared
* @param col2
* Column containing the right object to be compared
* @return a Comparator comparing Objects of given types with col1's Object
* as left argument and col2's Object as right argument
* @throws Exception
* if the given classes can't be compared
*/
public static Comparator<Object> createComp(Column col1, Column col2)
throws Exception {
Class<?> cl1;
Class<?> cl2;
try {
cl1 = Class.forName(col1.columnMetaData.getColumnClassName());
cl2 = Class.forName(col2.columnMetaData.getColumnClassName());
} catch (Exception e) {
throw new RuntimeException(e);
}
return createComp(cl1, cl2);
}
/**
* Creates a {@link Comparator} for Objects of the given classes, if they're
* {@link Comparable} (if they're not, an Exception will be thrown). <br>
* If they're Numbers, they will be casted to {@link Number} and their
* double values will be compared.<br>
* If they're not numbers one class needs to be a subclass of the other
* class (or they need to be of the same class) or they can't be compared.
*
* @param cl1
* class name of the left object to be compared
* @param cl2
* class name of the right object to be compared
* @return a Comparator comparing Objects of type cl1 and cl2 with cl1 as
* left argument and cl2 as right argument
* @throws Exception
* if the given classes can't be compared
*/
static Comparator<Object> createComp(String className1, String className2)
throws Exception {
Class<?> cl1;
Class<?> cl2;
try {
cl1 = Class.forName(className1);
cl2 = Class.forName(className2);
} catch (Exception e) {
throw new RuntimeException(e);
}
return createComp(cl1, cl2);
}
/**
* Pseudo-Comparator that compares two Objects for equality and returns 0 if
* they're equal and 1 if they aren't. To be used with EQ and NEQ when both
* Objects are of the same type.
*/
public static Comparator<Object> equalsPseudoComp = new Comparator<Object>() {
@Override
public int compare(Object o1, Object o2) {
if(o1 == null || o2 == null) // prevent nullpointer exception
return (o1 == o2) ? 0 : 1; // if o2 is also null they're equal, else they're not
return o1.equals(o2) ? 0 : 1;
}
};
/**
* Used to implement arithmetic operations with two parameters like ADD,
* SUB, ...<br>
* You just have to supply a Function that does the actual operation.<br>
* The name will be the name of the new Column (in case you want to create a
* new one in select). The name may be <i>null</i> if the resulting Column
* is just to be used in an arithmetic operation or a comparison - but in
* that case getColumnMetaData() will throw an Exception to make sure it
* really isn't used in select.<br>
* The Function <b>op</b> should have {@code invoke(Number n1, Number n2)}
* implemented to perform the operation.<br>
* <b>Example:</b><br>
*
* <pre>
* <code> // for ADD
* Function<Number, Double> op = new AbstractFunction<Number, Double>() {
* {@code @Override}
* public Double invoke(Number n1, Number n2) {
* return n1.doubleValue() + n2.doubleValue();
* }
* };</pre</code>
*
* @param col1 the first Column
* @param col2 the second Column on that
* @param op a Function performing the operation
* @param name the name for the resulting Column (or <i>null</i> if none is needed)
* @return a Column performing the arithmetic operation specified in op on col1 and col2
*/
protected static Column arithm_op(final Column col1, final Column col2,
final Function<Number, Double> op, String name)
{
final boolean noName = (name == null); // indicates that no name was
// provided for this operation
if (noName) { // if this is a temporal column for a comparison
name = "val_from_arith_op"; // bogus-name so createColumnMetaData doesn't explode
}
// columnmetadata for this Column - the alias will be set later in setMetaData()..
// this is a bit hackish but i haven't got a better idea on how to do this
final ColumnMetaData cmd =
AdvResultSetMetaData.createColumnMetaData(Double.class, name, null);
Column ret = new Column(name, cmd) {
private static final long serialVersionUID = 1L;
// indicates whether the columns have already been checked for
// really containing numbers
boolean checked = false;
@Override
public ColumnMetaData getColumnMetaData() {
if (noName) {
// like val: if no name was given this may only be used in
// comparisons, arithmetic
// operations etc, but not to create a column for a tuple
// (in select)!
throw new UnsupportedOperationException(
"If you want to use an arithmetic operation"
+ " (ADD, SUB, ...) to create a real column within a tuple, give"
+" it a name (i.e. use ADD(col, name) etc)!");
} else {
return super.getColumnMetaData();
}
}
@Override
public Object invoke(Tuple tuple) {
// make sure the columns contain numbers - this check can't be done any sooner
// (e.g. when creating this Column) because we have to be sure
// col.setMetaData(..) has been called: the Columns (generally) don't "know"
// their type before that
if (!checked) {
checkForNumber(col1, col2);
checked = true;
}
Number val1 = (Number) col1.invoke(tuple);
Number val2 = (Number) col2.invoke(tuple);
return op.invoke(val1, val2);
}
@Override
// the join-case: columns may be from the left or right tuple
public Object invoke(Tuple left, Tuple right) {
if (!checked) { // make sure the columns contain numbers
checkForNumber(col1, col2);
checked = true;
}
Number val1 = (Number) col1.invoke(left, right);
Number val2 = (Number) col2.invoke(left, right);
return op.invoke(val1, val2);
}
};
// add col1 and col2 as contained Columns so they'll get metadata and
// correlated tuples
ret.addContainedColumns(col1, col2);
return ret;
}
/**
* Checks whether the values stored in the {@link Column}s are subclasses of
* {@link java.lang.Number}.<br>
* If they're not, no arithmetic operations like ADD can be performed onthem.
*
* @param col1
* the first Column to be checked
* @param col2
* the second Column to be checked
*/
protected static void checkForNumber(Column col1, Column col2) {
Class<?> cl1;
Class<?> cl2;
try {
cl1 = Class.forName(col1.columnMetaData.getColumnClassName());
cl2 = Class.forName(col2.columnMetaData.getColumnClassName());
} catch (Exception e) {
throw new RuntimeException(e);
}
if (!Number.class.isAssignableFrom(cl1)) {
throw new RuntimeException(col1.columnAlias + " contains a value of type "
+ cl1.getName() + " that is not a Number!");
}
if (!Number.class.isAssignableFrom(cl2)) {
throw new RuntimeException(col2.columnAlias + " contains a value of type "
+ cl2.getName() + " that is not a Number!");
}
}
}