package dbControl;
//import org.apache.beehive.controls.api.ControlException;
import dbControl.util.JavaTypeHelper;
import dbControl.util.ResultSetHelper;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Field;
import java.sql.ResultSetMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.util.GregorianCalendar;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.logging.Logger;
/**
* This class extracts the the content of a ResultSet into various forms of
* object (e.g. an Iterator, HashMap, etc)
*/
public class ResultSetExtractor
{
//Error messages
private static final String ERROR_SETTING_FIELD = "Error occurred while setting a value from the database to class field.";
private static final String ERROR_SETTING_ARRAY = "Error occurred while adding an elment to array.";
private static final String ERROR_NO_TYPE_SPECIFIED_FOR_EXTRACT_OBJECT = "Object type not specified when extracting object from ResultSet.";
private static final String ERROR_NO_TYPE_SPECIFIED_FOR_EXTRACT_ITERATOR = "Iterator element type not specified when extracting iterator from ResultSet.";
//ResultSet to extract data from
ResultSet rs;
//Meta data of the resultset.
ResultSetMetaData rsMetaData;
//Calendar used for working with dates.
protected GregorianCalendar cal;
//Column count of the resultset
int columnCount;
//Name of columns in the resultset
String[] columnNames = null;
// Field or Method
AccessibleObject[] fields = null;
/**
* Constructor
* @param rs the resultset to exrract data from
* @param cal the calendar to use when working with dates
*/
public ResultSetExtractor(ResultSet rs, Calendar cal) throws SQLException
{
this.rs = rs;
this.columnCount = rs.getMetaData().getColumnCount();
this.rsMetaData = rs.getMetaData();
this.columnNames = new String[columnCount + 1];
for (int i = 1 ; i <= columnCount ; i++)
this.columnNames[i] = this.rsMetaData.getColumnName(i).toUpperCase();
}
private Map<String, AccessibleObject> getColumnNameToFieldMap(Class objectType) throws SQLException
{
HashMap<String, AccessibleObject> columnNameToFieldMap =
new HashMap<String, AccessibleObject>(columnCount * 2);
//Add names of all column from resultset to map.
for (int i = 1 ; i <= columnCount ; i++)
columnNameToFieldMap.put(columnNames[i], null);
//Map the class fields to column names in the map.
for (Class clazz = objectType ; null != clazz && Object.class != clazz ; clazz = clazz.getSuperclass())
{
Field[] classFields = clazz.getDeclaredFields();
if (null != classFields)
for (int i = 0 ; i < classFields.length ; i++)
{
Field f = classFields[i];
if (Modifier.isStatic(f.getModifiers()))
continue;
String fieldName = f.getName().toUpperCase();
if (!columnNameToFieldMap.containsKey(fieldName))
continue;
columnNameToFieldMap.put(fieldName, f);
}
}
//Map the class's attribute setter methods to column names in the map.
Method[] classMethods = objectType.getMethods();
if (null != classMethods)
for (int i = 0 ; i < classMethods.length ; i++)
{
Method m = classMethods[i];
String methodName = m.getName();
if (methodName.length() < 4 || !methodName.startsWith("set"))
continue;
if (!Character.isUpperCase(methodName.charAt(3)))
continue;
String fieldName = methodName.substring(3).toUpperCase();
if (!columnNameToFieldMap.containsKey(fieldName))
continue;
if (Modifier.isStatic(m.getModifiers()))
continue;
Class[] params = m.getParameterTypes();
if (1 != params.length)
continue;
if (Types.OTHER == JavaTypeHelper.getSqlType(params[0]))
continue;
if (!Void.TYPE.equals(m.getReturnType()))
continue;
// check for overloads
Object field = columnNameToFieldMap.get(fieldName);
if (null != field)
continue;
columnNameToFieldMap.put(fieldName, m);
}
return columnNameToFieldMap;
}
/**
* Extracts the data of the resultset into a array of a specified
* type.
* @param arrayClass array type
* @return array of the specified type
*/
public Object extractArray(Class arrayClass) throws Exception{
Class componentType = arrayClass.getComponentType();
ArrayList list = new ArrayList();
while(rs.next())
{
list.add(extractObject(componentType));
}
Object array = java.lang.reflect.Array.newInstance(componentType, list.size());
try
{
for (int i = 0 ; i < list.size() ; i++)
java.lang.reflect.Array.set(array, i, list.get(i));
}
catch (IllegalArgumentException iae)
{
throw new Exception(ERROR_SETTING_ARRAY, iae);
}
return array;
}
/**
* Extracts the data of a resultset into an iterator of a specified type.
* @param iteratorElementType type of the elements in the iterator
* @return an iterator with elements of the specified type.
*/
public Iterator extractIterator(Class iteratorElementType) throws Exception
{
return new ResultSetIterator(iteratorElementType);
}
/**
* Extracts one row of data from a resultset into a HashMap with
* the column names as keys.
* @return column name to value map of a row from the resultset.
*/
public HashMap<String, Object> extractHashMap() throws Exception
{
return new ResultSetHashMap();
}
/**
* Extracts one row of data from a resultset into a unmodifiable
* map with the column names as keys.
* @return column name to value map of a row from the resultset.
*/
public Map<String, Object> extractUnmodifiableMap() throws Exception
{
return Collections.unmodifiableMap(new ResultSetHashMap());
}
/**
* Extracts one row of data into an object of a specified type.
* @param returnClass type of object to extract the data to
* @return an object of the specified type
*/
public Object extractObject(Class returnClass) throws Exception
{
if (returnClass == null)
{
throw new Exception(ERROR_NO_TYPE_SPECIFIED_FOR_EXTRACT_OBJECT);
}
if (columnCount == 1)
{
Object val = ResultSetHelper.readValue(rs, columnNames[1], returnClass.getClass(), cal);
if (returnClass.isAssignableFrom(val.getClass()))
{
return val;
}
}
// calculate reflection information
Map<String, AccessibleObject> columnNameToFieldMap = getColumnNameToFieldMap(returnClass);
Object resultObject = returnClass.newInstance();
// array used for method.invoke()
Object[] args = new Object[1];
Set<String> columnNames = columnNameToFieldMap.keySet();
for (String columnName : columnNames)
{
AccessibleObject accessibleObj = columnNameToFieldMap.get(columnName);
if (accessibleObj == null)
continue;
try
{
if (accessibleObj instanceof Field)
{
Object resultValue = ResultSetHelper.readValue(rs, columnName, ((Field)accessibleObj).getType(), cal);
((Field)accessibleObj).set(resultObject, resultValue);
}
else
{
Object resultValue = ResultSetHelper.readValue(rs, columnName, ((Method)accessibleObj).getParameterTypes()[0], cal);
args[0] = resultValue;
((Method)accessibleObj).invoke(resultObject, args);
}
}
catch (IllegalArgumentException iae)
{
throw new Exception(ERROR_SETTING_FIELD, iae);
}
}
return resultObject;
}
/**
* The ResultSetHashMap class extends a standard HashMap and
* populates it with data derived from a JDBC ResultSet.
* <p>
* Note: the keys are treated case-insensitively, and therefore requests
* made on the map are case-insensitive. Any direct access to the keys
* will yield uppercase keys.
* <p>
* Note: only the row associated with the current cursor position
* is used.
*/
private class ResultSetHashMap extends HashMap<String, Object>
{
ResultSetHashMap() throws Exception
{
super();
ResultSetMetaData md = rs.getMetaData();
if (rs.next())
{
for (int i = 1 ; i <= md.getColumnCount() ; i++)
{
super.put(md.getColumnName(i).toUpperCase(), rs.getObject(i));
}
}
}
public boolean containsKey(Object key)
{
if (key instanceof String)
key = ((String)key).toUpperCase();
return super.containsKey(key);
}
public Object get(Object key)
{
if (key instanceof String)
key = ((String)key).toUpperCase();
return super.get(key);
}
public Object put(String key, Object value)
{
key = key.toUpperCase();
return super.put(key, value);
}
public Object remove(String key)
{
key = key.toUpperCase();
return super.remove(key);
}
}
private class ResultSetIterator implements Iterator {
Class iteratorElementType;
boolean primed = false;
ResultSetIterator(Class iteratorElementType) throws Exception
{
if (iteratorElementType == null)
throw new Exception(ERROR_NO_TYPE_SPECIFIED_FOR_EXTRACT_ITERATOR);
this.iteratorElementType = iteratorElementType;
}
public boolean hasNext()
{
if (primed)
return true;
try
{
primed = rs.next();
}
catch (SQLException sqle)
{
return false;
}
return primed;
}
public Object next()
{
try
{
if (!primed)
{
primed = rs.next();
if (!primed)
throw new NoSuchElementException();
}
// reset upon consumption
primed = false;
return extractObject(iteratorElementType);
}
catch (Exception e)
{
// Since Iterator interface is locked, all we can do
// is put the real exception inside an expected one.
NoSuchElementException xNoSuch = new NoSuchElementException("ResultSet exception: " + e);
xNoSuch.initCause(e);
throw xNoSuch;
}
}
public void remove()
{
throw new UnsupportedOperationException("remove not supported");
}
}
}