/* 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.util.List; import xxl.core.collections.MapEntry; 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; /** * A class containing several static functions that deal with {@link Column}s */ @SuppressWarnings("serial") public class ColumnUtils { /** * Creates a Column of the given class by calling its constructor with the given arguments.<br> * If no suitable constructor for the given arguments is found, an Exception will be thrown. * This takes the first constructor with matching parameters, so it might not be the same that * would be called in "real java code". And varargs constructors might not work.<br> * <i>Example:</i><br> If there is a constructor <code>Foo(Bar x)</code> and one * <code>Foo(Object x)</code> and you try to invoke it with a <code>Bar</code> argument, * but (by coincidence) the <code>Foo(Object x)</code> constructor - that won't fail when * being invoked with a Bar-object - is tried first, it will be used. Emulating the "original" * java behaviour would be really difficult as javas reflection API has no useful functionality * to return the constructor (or Method) that would be used when being invoked with given * parameters: You have to iterate through the method and try to find one that fits..<br> * However, in many/most cases this should work as expected.<br> * <b>Note:</b> To make sure to call the constructor you want you can cast the arguments via * {@link #colCAST(Column, Class)}: if the arguments match the exact constructor signature the * right constructor will be used. * @param clazz you want a new Object of this class. * @param arguments the arguments to invoke the constructor with * @return a Column returning objects of clazz created with the given arguments */ public static Column colNEW(Class<?> clazz, Column... arguments) { return ColumnUtils.colNEW(null, clazz, arguments); } /** * Creates a Column of the given class by calling its constructor with the given arguments.<br> * If no suitable constructor for the given arguments is found, an Exception will be thrown. * This takes the first constructor with matching parameters, so it might not be the same that * would be called in "real java code". And varargs constructors might not work.<br> * <i>Example:</i> If there is a constructor <code>Foo(Bar x)</code> and one * <code>Foo(Object x)</code> and you try to invoke it with a <code>Bar</code> argument, * but (by coincidence) the <code>Foo(Object x)</code> constructor - that won't fail when * being invoked with a Bar-object - is tried first, it will be used. Emulating the "original" * java behaviour would be really difficult as javas reflection API has no useful functionality * to return the constructor (or Method) that would be used when being invoked with given * parameters: You have to iterate through the method and try to find one that fits..<br> * However, in many/most cases this should work as expected.<br> * <b>Note:</b> To make sure to call the method you want you can cast the arguments via * {@link #colCAST(Column, Class)}: if the arguments match the exact method signature the * right method will be used.<br> * <b>Note:</b> To make sure to call the constructor you want you can cast the arguments via * {@link #colCAST(Column, Class)}: if the arguments match the exact constructor signature the * right constructor will be used. * * @param name the name of the new Column (needed to create a new column in a Tuple) * @param clazz you want a new Object of this class. * @param arguments the arguments to invoke the constructor with * @return a Column returning objects of clazz created with the given arguments */ public static Column colNEW(String name, final Class<?> clazz, final Column... cols) { // indicates that no name was provided for this operation final boolean noName = (name == null); if (noName) { // if this is a temporal column for a comparison name = "REFLECTION_NEW"; // bogus-name so createColumnMetaData doesn't explode } Column ret = new ConstructorColumn(name, clazz, cols); ret.addContainedColumns(cols); return ret; } /** * Creates a Column executing a Method with the given name and arguments on the Object * returned by col.<br> * If no suitable (non-void) method for the given arguments is found, an Exception will be * thrown. This takes the first method with matching parameters, so it might not be the same * that would be called in "real java code". And varargs methods might not work.<br> * <i>Example:</i> If there is a method <code>foo(Bar x)</code> and one * <code>foo(Object x)</code> and you try to invoke it with a <code>Bar</code> argument, * but (by coincidence) the <code>foo(Object x)</code> method - that won't fail when * being invoked with a Bar-object - is tried first, it will be used. Emulating the "original" * java behaviour would be really difficult as javas reflection API has no useful functionality * to return the Method that would be used when being invoked with given * parameters: You have to iterate through the method and try to find one that fits..<br> * However, in many/most cases this should work as expected.<br> * <b>Note:</b> To make sure to call the method you want you can cast the arguments via * {@link #colCAST(Column, Class)}: if the arguments match the exact method signature the * right method will be used. * * @param col Column returning the Object the Method will be called on * @param methodName the name of the Method to be used * @param arguments the arguments to invoke the method with * @return a Column returning objects returned by the method invoked with the given arguments */ public static Column colOBJCALL(Column col, String methodName, Column... cols) { return ColumnUtils.colOBJCALL(null, col, methodName, cols); } /** * Creates a Column executing a Method with the given name and arguments on the Object * returned by col.<br> * If no suitable (non-void) method for the given arguments is found, an Exception will be * thrown. This takes the first method with matching parameters, so it might not be the same * that would be called in "real java code". And varargs methods might not work.<br> * * <i>Example:</i> If there is a method <code>foo(Bar x)</code> and one * <code>foo(Object x)</code> and you try to invoke it with a <code>Bar</code> argument, * but (by coincidence) the <code>foo(Object x)</code> method - that won't fail when * being invoked with a Bar-object - is tried first, it will be used. Emulating the "original" * java behaviour would be really difficult as javas reflection API has no useful functionality * to return the Method that would be used when being invoked with given * parameters: You have to iterate through the method and try to find one that fits..<br> * However, in many/most cases this should work as expected.<br> * <b>Note:</b> To make sure to call the method you want you can cast the arguments via * {@link #colCAST(Column, Class)}: if the arguments match the exact method signature the * right method will be used. * * @param name the name of the returned Column * @param col Column returning the Object the Method will be called on * @param methodName the name of the Method to be used * @param arguments the arguments to invoke the method with * @return a Column returning objects returned by the method invoked with the given arguments */ public static Column colOBJCALL(String name, final Column col, final String methodName, final Column... cols) { 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 = "REFLECTION_OBJCALL"; // bogus-name so createColumnMetaData // doesn't explode } Column ret = new DynamicMethodColumn(name, methodName, col, cols); ret.addContainedColumns(cols); return ret; } /** * Creates a Column executing a static function with the given methodname of the given class * with the given arguments.<br> * If no suitable (non-void) static function for the given arguments is found, an Exception * will be thrown. This takes the first method with matching parameters, so it might not be * the same that would be called in "real java code". And varargs methods might not work.<br> * * <i>Example:</i> If there is a method <code>foo(Bar x)</code> and one * <code>foo(Object x)</code> and you try to invoke it with a <code>Bar</code> argument, * but (by coincidence) the <code>foo(Object x)</code> method - that won't fail when * being invoked with a Bar-object - is tried first, it will be used. Emulating the "original" * java behaviour would be really difficult as javas reflection API has no useful functionality * to return the Method that would be used when being invoked with given * parameters: You have to iterate through the method and try to find one that fits..<br> * However, in many/most cases this should work as expected.<br> * <b>Note:</b> To make sure to call the method you want you can cast the arguments via * {@link #colCAST(Column, Class)}: if the arguments match the exact method signature the * right method will be used. * * @param clazz the class containing the static method * @param methodName the name of the static Method to be used * @param arguments the arguments to invoke the method with * @return a Column returning objects returned by the method invoked with the given arguments */ public static Column colSTATICCALL(Class<?> clazz, String methodName, Column... cols) { return ColumnUtils.colSTATICCALL(null, clazz, methodName, cols); } /** * Creates a Column executing a static function with the given methodname of the given class * with the given arguments.<br> * If no suitable (non-void) static function for the given arguments is found, an Exception * will be thrown. This takes the first method with matching parameters, so it might not be * the same that would be called in "real java code". And varargs methods might not work.<br> * * <i>Example:</i> If there is a method <code>foo(Bar x)</code> and one * <code>foo(Object x)</code> and you try to invoke it with a <code>Bar</code> argument, * but (by coincidence) the <code>foo(Object x)</code> method - that won't fail when * being invoked with a Bar-object - is tried first, it will be used. Emulating the "original" * java behaviour would be really difficult as javas reflection API has no useful functionality * to return the Method that would be used when being invoked with given * parameters: You have to iterate through the method and try to find one that fits..<br> * However, in many/most cases this should work as expected.<br> * <b>Note:</b> To make sure to call the method you want you can cast the arguments via * {@link #colCAST(Column, Class)}: if the arguments match the exact method signature the * right method will be used. * * @param name the name of the returned Column * @param clazz the class containing the static method * @param methodName the name of the static Method to be used * @param arguments the arguments to invoke the method with * @return a Column returning objects returned by the method invoked with the given arguments */ public static Column colSTATICCALL(String name, final Class<?> clazz, final String methodName, final Column... arguments) { 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 = "REFLECTION_STATICCALL"; // bogus-name so // createColumnMetaData doesn't // explode } Column ret = new StaticMethodColumn(name, clazz, methodName, arguments); ret.addContainedColumns(arguments); return ret; } /** * This {@link Column} wraps another Column and casts it to the given type.<br> * Particularly the {@link ColumnMetaData} of this Column will be according to the given type. * This is useful for the reflectiving Columns to make sure the right Method/Constructor is * used (just cast the arguments to the exact signature of the method/constructor). * @param col the Column to be casted * @param type the class to be casted to * @return a Column casting col to the given type */ public static Column colCAST(final Column col, final Class<?> type){ return new Column(){ @Override public int getColumnIndex() { return col.getColumnIndex(); } @Override // sollte so passen public ColumnMetaData getColumnMetaData() { col.getColumnMetaData(); // maybe this will throw an exception because no name was given.. // if it didn't throw an exception just return the proper metadata return columnMetaData; } @Override public Object invoke(Tuple left, Tuple right) { // cast the element to make sure it is of the requested type return type.cast( col.invoke(left, right) ); } @Override public Object invoke(Tuple tuple) { // cast the element to make sure it is of the requested type return type.cast( col.invoke(tuple) ); } @Override public void setCorrelatedTuples(List<MapEntry<AdvResultSetMetaData,Tuple>> corrTuples){ col.setCorrelatedTuples(corrTuples); } @Override public void setMetaData(AdvResultSetMetaData metadata, String newAlias) { // TODO @Daniel col.getMetadata() wirft ne exception da ja die col noch nicht "komplett" initialissiert wurde col.setMetaData(metadata, newAlias); try { this.columnMetaData = AdvResultSetMetaData.createColumnMetaData(type, col.getColumnMetaData().getColumnName(), null); } catch(Exception e){ throw new RuntimeException(e); } } @Override public void setMetaDatas(AdvResultSetMetaData leftMetaData, AdvResultSetMetaData rightMetaData) { // TODO @Daniel col.getMetadata() wirft ne exception da ja die col noch nicht "komplett" initialissiert wurde col.setMetaDatas(leftMetaData, rightMetaData); try { this.columnMetaData = AdvResultSetMetaData.createColumnMetaData(type, col.getColumnMetaData().getColumnName(), null); } catch(Exception e){ throw new RuntimeException(e); } } }; } /** * Creates a (pseudo)-Column with a value (the given Object).<br> * <b>This should only be used with predicates (like EQ etc)</b> or * operations like CONCAT or ADD etc and <b>not</b> to create a "real" * column within a Tuple (e.g. in select())! * * @param value * the Object to be contained in this pseudo-Column * @return a Column with the given value */ public static Column val(final Object obj) { if(obj == null) // if obj is null we can't generate metadata.. throw new RuntimeException("Don't use val() with a null value! Use valNULL() instead!"); return new Column(obj, "val(" + obj.toString() + ")"){ @Override public ColumnMetaData getColumnMetaData() { // make sure this val is only used as intended by the user.. // ATTENTION: this means that for "internal" use, like // checkForNumber, the columnmetadata // needs to be accessed directly! throw new UnsupportedOperationException( "If you want to use val() to create a real" + " column within a tuple, give it a name (i.e. use val(obj, name))!"); } }; } public static Column val(Object obj, String columnAlias) { if(obj == null) // if obj is null we can't generate metadata.. throw new RuntimeException("Don't use val() with a null value! Use valNULL() instead!"); return new Column(obj, columnAlias); } /** * Creates a Column containing the value <i>null</i>. You have to specify the type though, to make * our internal MetaDatas happy and, if this is to be used in a reflection Column (like * colOBJCALL etc), to make it possible to find the suitable method that expects this type. * * @param type type of the resulting Column - i.e. the type of the object/pointer that is null. * @param name a name for this Column * @return a Column containing <i>null</i> with given type in the metadata */ public static Column valNULL(Class<?> type, String name){ ColumnMetaData cmd = AdvResultSetMetaData.createColumnMetaData(type, name, ""); return new Column(null, name, cmd); } /** * Creates a {@link Column} containing the value <i>null</i>. You have to specify the type though, to make * our internal MetaDatas happy and, if this is to be used in a reflection Column (like * colOBJCALL etc), to make it possible to find the suitable method that expects this type. * * @param type type of the resulting Column - i.e. the type of the object/pointer that is null. * @param name a name for this Column * @return a Column containing <i>null</i> with given type in the metadata */ public static Column valNULL(Class<?> type){ ColumnMetaData cmd = AdvResultSetMetaData.createColumnMetaData(type, "valNULL", ""); return new Column(null, "valNULL", cmd){ @Override public ColumnMetaData getColumnMetaData() { throw new UnsupportedOperationException( "If you want to use valNULL() to create a real" + " column within a tuple, give it a name (i.e. use valNULL(class, name))!"); } }; } /** * Returns a {@link Column} projecting the <b>i</b>-th Element from a tuple.<br> * May not be used in joins! (Because then it's not clear whether it should be applied to * the left or the right tuple being joined) * @param i index of the column to be projected * @return a Column projecting the <b>i</b>-th Element from a tuple. */ public static Column col(int i) { return new Column(i); } /** * Returns a {@link Column} projecting the <b>i</b>-th Element from a tuple.<br> * May not be used in joins! (Because then it's not clear whether it should be applied to * the left or the right tuple being joined) * @param i index of the column to be projected * @return a Column projecting the <b>i</b>-th Element from a tuple. */ public static Column col(int i, String newColumnName) { return new Column(i, newColumnName); } /** * Returns a {@link Column} projecting the element with given columnName from a Tuple. * * @param name the columnName of the column to be projected * @return a Column projecting the <b>i</b>-th Element from a tuple. */ public static Column col(String name) { return new Column(name); } /** * Returns a {@link Column} projecting the element with given columnName from a Tuple. * Also the returned Column will have a new name, so this can be used (in * {@link AdvTupleCursor#select(Column...) select()}) to rename a Column. * * @param name the columnName of the column to be projected * @return a Column projecting the <b>i</b>-th Element from a tuple. */ public static Column col(String name, String newName) { return new Column(name, newName); } /** * Just returns an array of {@link Column}s containing the given columns. To be used with * {@link AdvTupleCursor#groupBy(Column[], xxl.core.math.functions.MetaDataAggregationFunction[]) * groupBy(Column[], MetaDataAggregationFunction[])} because it's only possible to use varargs * once within one function.<br> * <b>Example:</b> * <code>cur.groupBy(PROJ( col("a"), col("c") ), COUNT("cnt"), MAX(col("b"), "maxB"))</code> * @param columns columns to be put in an array * @return an array of Column containing the given columns */ public static Column[] PROJ(Column... columns) { return columns; } /** * This {@link Column} calculates the square root of <b>col</b><br> * <b>col</b> needs to contain Numbers, of course. * @param col the Column that provides the number to be square-rooted * @return a Column containing the square root of col's value (as Double) */ public static Column SQRT(final Column col) { return SQRT(col, null); } /** * This {@link Column} calculates the square root of <b>col</b><br> * <b>col</b> needs to contain Numbers, of course. * * @param col the Column that provides the number to be square-rooted * @param name the name of this Column (useful for new actual columns within a tuple like * in select() or groupBy()) * @return a Column containing the square root of col's value (as Double) */ public static Column SQRT(final Column col, String name) { // this is the only arithm. operation performed on one column (ADD etc // use two, of course) // so no arithm_op or similar is used 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) { if (!checked) { // yes, this is a bit hackish, but i'm not writing a new // check just for sqrt checkForNumber(col, col); checked = true; } Number val = (Number) col.invoke(tuple); return Math.sqrt(val.doubleValue()); } @Override // the join-case: columns may be from the left or right tuple public Object invoke(Tuple left, Tuple right) { if (!checked) { checkForNumber(col, col); checked = true; } Number val = (Number) col.invoke(left, right); return Math.sqrt(val.doubleValue()); } }; // add col as contained Column so it'll get metadata and correlated // tuples ret.addContainedColumns(col); return ret; } /** * A conditional {@link Column}: If the given {@link AdvPredicate} IF evaluates to <i>true</i> * the Column THEN will be returned, else the Column ELSE will be returned. * @param IF the condition * @param THEN Column to return if the condition is met for the current tuple(s) * @param ELSE Column to return if the condition is not met for the current tuple(s) * @return THEN or ELSE, determined by IF */ public static Column IfThenElse(final AdvPredicate IF, final Column THEN, final Column ELSE){ return IfThenElse(null, IF, THEN, ELSE); } /** * A conditional {@link Column}: If the given {@link AdvPredicate} IF evaluates to <i>true</i> * the Column THEN will be returned, else the Column ELSE will be returned. * @param name the name of the resulting Column (useful for new actual columns within a tuple like * in select() or groupBy()) * @param IF the condition * @param THEN Column to return if the condition is met for the current tuple(s) * @param ELSE Column to return if the condition is not met for the current tuple(s) * @return THEN or ELSE, determined by IF */ public static Column IfThenElse(String name, final AdvPredicate IF, final Column THEN, final Column ELSE){ final boolean noName = name == null; final String columnName = noName ? "ifthenelse" : name; Column ret = new Column(columnName) { private static final long serialVersionUID = 1L; private Class<?> getCommonType(Class<?> clazz1, Class<?> clazz2){ while(!clazz1.isAssignableFrom(clazz2)){ clazz1 = clazz1.getSuperclass(); } return clazz1; } @Override public void setMetaData(AdvResultSetMetaData metadata, String newAlias) { // set metadata for contained columns if (containedColumns != null) { for (Column col : containedColumns) { col.setMetaData(metadata, newAlias); } } // set metadata for IF-predicate IF.setMetaData(metadata, newAlias); Class<?> thenType; Class<?> elseType; try { thenType = Class.forName(THEN.columnMetaData.getColumnClassName()); elseType = Class.forName(ELSE.columnMetaData.getColumnClassName()); } catch (Exception e) { if(noName) throw new RuntimeException("Error while setting metadata for IfThenElse: " +e.getMessage(), e); else throw new RuntimeException("Error while setting metadata for IfThenElse \"" +columnName+"\": "+e.getMessage(), e); } Class<?> cl = getCommonType(thenType, elseType); columnMetaData = AdvResultSetMetaData.createColumnMetaData(cl, columnName, newAlias); } @Override public void setMetaDatas(AdvResultSetMetaData leftMetaData, AdvResultSetMetaData rightMetaData) { // set metadata for contained columns if (containedColumns != null) { for (Column col : containedColumns) { col.setMetaDatas(leftMetaData, rightMetaData); } } // set metadatas in IF-predicate IF.setMetaDatas(leftMetaData, rightMetaData); Class<?> thenType; Class<?> elseType; try { thenType = Class.forName(THEN.columnMetaData.getColumnClassName()); elseType = Class.forName(ELSE.columnMetaData.getColumnClassName()); } catch (Exception e) { if(noName) throw new RuntimeException("Error while setting metadata for IfThenElse: " +e.getMessage(), e); else throw new RuntimeException("Error while setting metadata for IfThenElse \"" +columnName+"\": "+e.getMessage(), e); } Class<?> cl = getCommonType(thenType, elseType); columnMetaData = AdvResultSetMetaData.createColumnMetaData(cl, columnName, null); } @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 IfThenElse to create a real Column within a Tuple," + " give it a name!"); } else { return super.getColumnMetaData(); } } @Override public Object invoke(Tuple tuple) { if(IF.invoke(tuple)) return THEN.invoke(tuple); else return ELSE.invoke(tuple); } @Override // the join-case: columns may be from the left or right tuple public Object invoke(Tuple left, Tuple right) { if(IF.invoke(left, right)) return THEN.invoke(left, right); else return ELSE.invoke(left, right); } }; // add then and else as contained Columns so they'll get metadata and correlated tuples ret.addContainedColumns(THEN, ELSE); return ret; } public static Column indexCol() { return indexCol("index", 0); } public static Column indexCol(long start) { return indexCol("index", start); } public static Column indexCol(String name) { return new IndexedColumn(name, 0); } public static Column indexCol(String name, long start) { return new IndexedColumn(name, start); } }