/* 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;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import xxl.core.relational.metaData.ColumnMetaData;
import xxl.core.relational.metaData.ColumnMetaDataResultSetMetaData;
// TODO: nen interface hiervon, falls mal jemand eigene advresultsetmetadatas bauen will (z.b. aus echten sql-metadaten)?
public class AdvResultSetMetaData extends ColumnMetaDataResultSetMetaData {
private String myAlias=null;
// mapping name -> index if name is unique
private Map<String, Integer> name2index;
// mapping (alias, name)->index of object
// Spezialfall: Cursorn ueber pseudo-primitive Datentypen ist der name standardmaessig "value"
// gibt logischen index im zugehoerigen tupel zurueck (wie man ihn mit getObject() nutzen wuerde)
private Map<AliasName, Integer> aliasName2index;
public String getAlias(){
return myAlias;
}
/**
* This constructor is meant to be used with "fresh" tuples, i.e. not concatenated ones or the
* like that may already have mappings with multiple aliases on the same column.
* @param myAlias alias of the Cursor belonging to this metadata, null if none
* @param columnMetaDatas the metadatas of each column
*/
public AdvResultSetMetaData(String myAlias, ColumnMetaData... columnMetaDatas) {
this(myAlias, true, columnMetaDatas);
}
/**
* This constructor may be used if the mappings can't be trivially generated, but will be done <b>manually
* after</b> creating the Object with this constructor (e.g. when concatenating metadata etc). <br>
* However if fillMappings is set to true, the mappings will be generated.
* @param myAlias alias of the Cursor belonging to this metadata, null if none
* @param fillMappings if true, mappings (alias.name->index) will be generated. If false, you
* have to do that yourself afterwards
* @param columnMetaDatas the metadatas of each column
*
* @see AdvResultSetMetaData#concat(AdvResultSetMetaData, AdvResultSetMetaData) concat(AdvResultSetMetaData, AdvResultSetMetaData)
*/
public AdvResultSetMetaData(String myAlias, boolean fillMappings, ColumnMetaData... columnMetaDatas) {
super(columnMetaDatas);
this.myAlias = myAlias;
name2index = new HashMap<String, Integer>();
aliasName2index = new HashMap<AliasName, Integer>();
if(fillMappings){ // the simple case: no contained tuples, mappings are straight forward
try {
for(int i=0;i<columnMetaDatas.length;i++){
String name = columnMetaDatas[i].getColumnName();
// name2index
name2index.put(name, i+1);
// aliasName2index
if(myAlias != null && !myAlias.equals(""))
addMappingAliasNameIndex(myAlias, name, i+1);
// if the column has another alias (e.g. tablename from jdbc or the like)
String altAlias = columnMetaDatas[i].getTableName();
if(!altAlias.equals(myAlias) && !altAlias.equals(""))
addMappingAliasNameIndex(altAlias, name, i+1);
}
} catch(SQLException e){
throw new RuntimeException(e);
}
}
}
/**
* private helping function creating an array of ColumnMetaDatas from a simple ResultSetMetaData
* @param rsm ResultSetMetaData, e.g. from a JDBC-ResultSet
* @return an array of ColumnMetaDatas suitable for AdvResultSetMetaData
*/
private static ColumnMetaData[] cmdsFromRSM(ResultSetMetaData rsm){
ColumnMetaData[] cmds=null;
try {
cmds = new ColumnMetaData[rsm.getColumnCount()];
for(int i=1;i<=cmds.length;i++){
Class<?> javaclass = Class.forName(rsm.getColumnClassName(i));
String name = rsm.getColumnName(i);
String altalias = rsm.getTableName(i);
cmds[i-1] = createColumnMetaData(javaclass, name, altalias);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return cmds;
}
/**
* This constructor creates an AdvResultSetMetaData object from a simple ResultSetMetaData.
* Should be used when wrapping JDBC-ResultSets in AdvTupleCursors
* @param rsm a ResultSetMetaData, e.g. from a JDBC-ResultSet
* @param alias the alias of the new AdvTupleCursor wrapping the ResultSet
*/
public AdvResultSetMetaData(ResultSetMetaData rsm, String alias){
this(alias, cmdsFromRSM(rsm));
}
// klasse, die 2 strings enthaelt: fuer alias und name, soll als schluessel der aliasName2index-hashmap dienen
static class AliasName { // FIXME: hierfuer gibts irgendne fertige xxl-klasse
String alias=null;
String name=null;
public AliasName(String alias, String name) {
this.alias=alias;
this.name=name;
}
// sinnvolle vergleichsfunktion, macht sich sicher gut fuers mapping...
@Override
public boolean equals(Object obj) {
AliasName an = (AliasName)obj; // obj really shouldn't be of any other class.
return an.alias.equals(alias) && an.name.equals(name);
}
@Override
public int hashCode() {
// die codes bitweise verodern, damit alias und name nicht austauschbar sind
return alias.hashCode() ^ name.hashCode();
}
@Override
public String toString() {
return alias + "." + name;
}
}
/**
* Returns a concatenatenation of two AdvResultSetMetaDatas
*
* @param first 1. AdvResultSetMetaData
* @param second 2. AdvResultSetMetaData
* @return (first AdvResultSetMetaData)++(second AdvResultSetMetaData)
*/
public static AdvResultSetMetaData concat(AdvResultSetMetaData first, AdvResultSetMetaData second){
return concat(first, second, null);
}
/**
* Returns a concatenatenation of two AdvResultSetMetaDatas with a new Alias for the resulting metadata.<br>
* Please note that only columns with unique names (within this metadata) can be accessed by "newAlias.name",
* so you might want to rename some column's names (or use the old alias to access them).
*
* @param first 1. AdvResultSetMetaData
* @param second 2. AdvResultSetMetaData
* @param newAlias the alias of the newly created metadata. null if you don't want an additional alias.
* @return (first AdvResultSetMetaData)++(second AdvResultSetMetaData)
*/
public static AdvResultSetMetaData concat(AdvResultSetMetaData first, AdvResultSetMetaData second, String newAlias){
// FIXME: ich *glaube*, dass die mappings bei doppelten namen mit gleichem alias (nach join) spinnen, d.h.
// vorhanden sind. sollte nicht sein, da join.bla dann uneindeutig ist. - sollte gefixt sein
int cmdlength;
ColumnMetaData[] columnMetaDatas;
try {
cmdlength = first.getColumnCount()+second.getColumnCount();
columnMetaDatas = new ColumnMetaData[cmdlength];
for(int i=0;i<first.getColumnCount();i++){
columnMetaDatas[i] = first.columnMetaData[i];
}
for(int i=0;i<second.getColumnCount();i++){
columnMetaDatas[i+first.getColumnCount()] = second.columnMetaData[i];
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
String tmpAlias = (newAlias == null || newAlias.equals("")) ? first.getAlias() : newAlias;
// just use the old alias of left father if a new one wasn't given
AdvResultSetMetaData ret = new AdvResultSetMetaData(tmpAlias, false, columnMetaDatas);
// create and adjust mappings
try {
// ## name2index ## the elements of the *second* tuple lie behind *first* tuple
ret.name2index.putAll(first.name2index);
int elemOffset = first.getColumnCount(); // logical position of the last element from *first*
for(Entry<String, Integer> ent : second.name2index.entrySet()){
if(ret.name2index.containsKey(ent.getKey())) // if the mapping is not unambiguous, don't map at all.
ret.name2index.remove(ent.getKey());
else
ret.name2index.put(ent.getKey(), ent.getValue()+elemOffset);
}
// ## aliasName2index ## - again, the mappings for *first* tuple don't change..
ret.aliasName2index.putAll(first.aliasName2index);
for(Entry<AliasName, Integer> ent : second.aliasName2index.entrySet()){
// .. but the indexes of the elements from the *second* tuple get that offset
ret.addMappingAliasNameIndex(ent.getKey().alias, ent.getKey().name, ent.getValue()+elemOffset);
}
// if there is a new alias, add mappings in aliasName2index
if(newAlias != null && !newAlias.equals("")) {
// add a mapping for each unique name and the new alias to aliasName2index
for(Entry<String, Integer> ent : ret.name2index.entrySet()){
AliasName tmp = new AliasName(newAlias, ent.getKey());
ret.aliasName2index.put(tmp, ent.getValue());
}
}
} catch(SQLException e){
throw new RuntimeException(e);
}
return ret;
}
/**
* Returns a "clone" of this MetaData, but there are new mappings for the new alias.
* (newAlias.nameOfSomeColumn -> index)<br>
* Please note that there will (and can) be no mapping for duplicate names (You may still acess
* them with their original Alias, though).
* @param newAlias the new alias
* @return an AdvResultSetMetaData with the given alias
*/
public AdvResultSetMetaData clone(String newAlias){
ColumnMetaData[] cmds = new ColumnMetaData[this.columnMetaData.length];
for(int i=0;i<cmds.length;i++){
ColumnMetaData tmp = this.columnMetaData[i];
try {
// if the column has the old alias as tablename replace it with the new alias
if(tmp.getTableName().equals(this.getAlias())){
Class<?> cl = Class.forName(tmp.getColumnClassName());
tmp = createColumnMetaData(cl, tmp.getColumnName(), newAlias);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
cmds[i]=tmp;
}
AdvResultSetMetaData ret = new AdvResultSetMetaData(newAlias, false, cmds);
ret.myAlias = newAlias;
ret.name2index = this.name2index; // the names itself won't change
ret.aliasName2index = new HashMap<AliasName, Integer>();
// the old aliases may still be used, but not the alias of the cloned cursor/metadata
// mappings for it need to be replaced by mappings with the new alias or selfjoins won't work
for(Entry<AliasName, Integer> ent : this.aliasName2index.entrySet()){
// if it's the alias of the cloned metadata
if(ent.getKey().alias.equals(this.getAlias())) {
// add a mapping for the new alias
ret.addMappingAliasNameIndex(newAlias, ent.getKey().name, ent.getValue());
} else { // else add the old mapping
ret.aliasName2index.put(ent.getKey(), ent.getValue());
}
}
// but we have to add new aliases for newAlias, so we take the non-ambiguous names from
// name2index and add a mapping for them and the new alias to aliasName2index
for(Entry<String, Integer> ent : name2index.entrySet()){
AliasName tmp = new AliasName(newAlias, ent.getKey());
ret.aliasName2index.put(tmp, ent.getValue());
}
return ret;
}
/**
* Returns the index of the element that was originally in the tuple/cursor with given alias
* and has the given name. <br/>
* <i>Note:</i> The default name of an attribute in a Tuple wrapping a primitive Type (or it's wrappers
* like java.lang Integer), e.g. from a Cursor<Float>, is "value".
*
* @param alias the tuples/cursors alias
* @param name the attribute's/element's name
* @return logical index in this tuple (resp. tuples of this cursor) or -1 if not found
*/
public int getIndexByAliasAndName(String alias, String name){
Integer index = aliasName2index.get(new AliasName(alias, name));
return (index == null) ? -1 : index.intValue();
}
/**
* Returns the index of the element that was originally in the tuple/cursor with given alias
* and name. (Format: alias.name or just name, but then the name has to be unique within this Cursor/Metadata) <br/>
* <i>Note:</i> The default name of an attribute in a Tuple wrapping a primitive Type (or it's wrappers
* like java.lang Integer), e.g. from a Cursor<Float>, is "value".
*
* @param aliasname the tuples/cursors alias and name (alias.name)
* @return logical index in this tuple (resp. tuples of this cursor) or -1 if not found
*/
public int getIndexByAliasAndName(String aliasname){
String[] tmp = aliasname.split("\\.");
if(tmp.length == 2){
return getIndexByAliasAndName(tmp[0], tmp[1]);
}
else if(tmp.length == 1)
return getIndexByName(tmp[0]);
else
return -1;
}
/**
* Returns index for given name or -1 if the name is not found or not unique
* (i.e. the name exists several times with different aliases)
* @param name the attributes name
* @return index of the column for the given name or -1 if not found
*/
public int getIndexByName(String name) {
Integer index = name2index.get(name);
return (index == null) ? -1 : index.intValue();
}
/**
* Returns the ColumnMetaData of the given column
* @param column the index of the column which's metadata is to retrieved
* @return ColumnMetaData for column with given index
*/
public ColumnMetaData getColumnMetaData(int column){
return columnMetaData[column-1];
}
/**
* Adds a mapping from alias and name to logical index within a tuple to this metadata.<br>
* Should probably not be used much outsite this class..
* @param alias the alias of the tuple/table containing the element at index
* @param name the name of the element at index
* @param index the logical index of the element within tuples containing this metadata
*/
protected void addMappingAliasNameIndex(String alias, String name, int index){
AliasName an = new AliasName(alias, name);
aliasName2index.put(an, index);
// name2index.put(name, index); wird "von hand" gemacht an den (wenigen) entsprechenden stellen
// damit werden auch duplikate verhindert.
}
/**
* Returns an appropriate ColumnMetaData-object for the AdvResultSetMetaData. All relevant information can
* (hopefully) be extracted from the class of the Object in this column and the columns name
* @param cl a column represented by this metadata will contain an Object of this class
* @param name the Columns name
* @return an appropriate ColumnMetaData
*/
public static ColumnMetaData createColumnMetaData(final Class<?> cl, final String name, final String alias){
if(name == null || name.equals(""))
throw new RuntimeException("You need to specify a name for the new Column");
return new ColumnMetaData() {
@Override
public boolean isWritable() throws SQLException {
return false; // schreiben wollnwa nicht - oder?
}
@Override
public boolean isSigned() throws SQLException {
// was ist, wenn das garkeine zahlen sind?
return true; // java kann ja nix anders..
}
@Override
public boolean isSearchable() throws SQLException {
return true; // grundsaetzlich sollte erstmal alles in ner where-clause gehen
// TODO: was ist mit arrays?
}
@Override
public boolean isReadOnly() throws SQLException {
return true; // wir wollen nix ueberschreiben.
}
@Override
public int isNullable() throws SQLException {
return ResultSetMetaData.columnNullable; // null kann wohl mal vorkommen, glaub ich
}
@Override
public boolean isDefinitelyWritable() throws SQLException {
return false;
}
@Override
public boolean isCurrency() throws SQLException {
// TODO is this ever needed?
return false;
}
@Override
public boolean isCaseSensitive() throws SQLException {
return true;
}
@Override
public boolean isAutoIncrement() throws SQLException {
return false;
}
@Override
public String getTableName() throws SQLException {
return alias != null ? alias : "";
}
@Override
public String getSchemaName() throws SQLException {
return "";
}
@Override
public int getScale() throws SQLException {
// TODO is this ever needed?
return 0;
}
@Override
public int getPrecision() throws SQLException {
// TODO is this ever needed? don't know how to calculate this properly..
return 0;
}
@Override
public String getColumnTypeName() throws SQLException {
// TODO does this matter?
return cl.getSimpleName();
}
@Override
public int getColumnType() throws SQLException {
// TODO is this ever needed? if so, we need an appropriate mapping to an sql-type
// (have fun with that :-P)
// we *will not* use this. do *not* use the mappings from Types.java, because you'll
// get an exception if you have any "fancy" type in this Column
// (only ~20 basic types are supported)
// return 0;
throw new UnsupportedOperationException("Don't use getColumnType(), it's impossible to"+
"support for non-basic types..");
}
@Override
public String getColumnName() throws SQLException {
return name;
}
@Override
public String getColumnLabel() throws SQLException {
return getColumnName();
}
@Override
public int getColumnDisplaySize() throws SQLException {
// TODO is this ever needed?
return 128;
}
@Override
public String getColumnClassName() throws SQLException {
return cl.getName();
}
@Override
public String getCatalogName() throws SQLException {
// TODO what is this?
return "";
}
};
}
}