/*
 * Copyright (c) 2008, Harald Kuhr
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name "TwelveMonkeys" nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.twelvemonkeys.sql;


import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.lang.SystemUtil;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Vector;

/**
 * Class used for reading table data from a database through JDBC, and map 
 * the data to Java classes.
 *
 * @see ObjectMapper
 *
 * @author Harald Kuhr (haraldk@iconmedialab.no)
 * @author last modified by $Author: haku $
 * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/sql/ObjectReader.java#1 $
 *
 * @todo Use JDK logging instead of proprietary logging.
 *
 */
public class ObjectReader {

    /**
     * Main method, for testing purposes only.
     */

    public final static void main(String[] pArgs) throws SQLException {
        /*
          System.err.println("Testing only!");
	
          // Get default connection
          ObjectReader obr = new ObjectReader(DatabaseConnection.getConnection());

          com.twelvemonkeys.usedcars.DBCar car = new com.twelvemonkeys.usedcars.DBCar(new Integer(1));
          com.twelvemonkeys.usedcars.DBDealer dealer = new com.twelvemonkeys.usedcars.DBDealer("NO4537");

          System.out.println(obr.readObject(dealer));
          com.twelvemonkeys.usedcars.Dealer[] dealers = (com.twelvemonkeys.usedcars.Dealer[]) obr.readObjects(dealer);

          for (int i = 0; i < dealers.length; i++) {
          System.out.println(dealers[i]);
          }

          System.out.println("------------------------------------------------------------------------------\n"
          + "Total: " + dealers.length + " dealers in database\n");
	
          Hashtable where = new Hashtable();
	
          where.put("zipCode", "0655");
          dealers = (com.twelvemonkeys.usedcars.Dealer[]) obr.readObjects(dealer, where);

          for (int i = 0; i < dealers.length; i++) {
          System.out.println(dealers[i]);
          }

          System.out.println("------------------------------------------------------------------------------\n"
          + "Total: " + dealers.length + " dealers matching query: " 
          + where + "\n");
	

          com.twelvemonkeys.usedcars.Car[] cars = null;
          cars = (com.twelvemonkeys.usedcars.Car[]) obr.readObjects(car);

          for (int i = 0; i < cars.length; i++) {
          System.out.println(cars[i]);
          }

          System.out.println("------------------------------------------------------------------------------\n"
          + "Total: " + cars.length + " cars in database\n");

          where = new Hashtable();
          where.put("year", new Integer(1995));
          cars = (com.twelvemonkeys.usedcars.Car[]) obr.readObjects(car, where);

          for (int i = 0; i < cars.length; i++) {
          System.out.println(cars[i]);
          }

          System.out.println("------------------------------------------------------------------------------\n"
          + "Total: " + cars.length + " cars matching query: "
          + where + " \n");
	

          where = new Hashtable();
          where.put("publishers", "Bilguiden");
          cars = (com.twelvemonkeys.usedcars.Car[]) obr.readObjects(car, where);

          for (int i = 0; i < cars.length; i++) {
          System.out.println(cars[i]);
          }

          System.out.println("------------------------------------------------------------------------------\n"
          + "Total: " + cars.length + " cars matching query: " 
          + where + "\n");

          System.out.println("==============================================================================\n"	
          + getStats());
        */
    }


    protected Log mLog = null;
    protected Properties mConfig = null;

    /**
     * The connection used for all database operations executed by this 
     * ObjectReader.
     */

    Connection mConnection = null;

    /**
     * The cache for this ObjectReader.
     * Probably a source for memory leaks, as it has no size limitations.
     */

    private Hashtable mCache = new Hashtable();

    /**
     * Creates a new ObjectReader, using the given JDBC Connection. The 
     * Connection will be used for all database reads by this ObjectReader.
     *
     * @param connection A JDBC Connection
     */

    public ObjectReader(Connection pConnection) {
        mConnection = pConnection;
	
        try {
            mConfig = SystemUtil.loadProperties(getClass());
        }
        catch (FileNotFoundException fnf) {
            // Just go with defaults
        }
        catch (IOException ioe) {
            new Log(this).logError(ioe);
        }

        mLog = new Log(this, mConfig);
    }

    /**
     * Gets a string containing the stats for this ObjectReader.
     *
     * @return A string to display the stats.
     */

    public static String getStats() {
        long total = sCacheHit + sCacheMiss + sCacheUn;
        double hit = ((double) sCacheHit / (double) total) * 100.0;
        double miss = ((double) sCacheMiss / (double) total) * 100.0;
        double un = ((double) sCacheUn / (double) total) * 100.0;

        // Default locale
        java.text.NumberFormat nf = java.text.NumberFormat.getInstance();

        return "Total: " + total + " reads. "
            + "Cache hits: " + sCacheHit + " (" + nf.format(hit) + "%), "
            + "Cache misses: " + sCacheMiss + " (" + nf.format(miss) + "%), "
            + "Unattempted: " + sCacheUn + " (" + nf.format(un) + "%) ";
    }

    /**
     * Get an array containing Objects of type objClass, with the 
     * identity values for the given class set.
     */
    
    private Object[] readIdentities(Class pObjClass, Hashtable pMapping, 
                                    Hashtable pWhere, ObjectMapper pOM) 
        throws SQLException {
        sCacheUn++;
        // Build SQL query string
        if (pWhere == null)
            pWhere = new Hashtable();
	
        String[] keys = new String[pWhere.size()];
        int i = 0;
        for (Enumeration en = pWhere.keys(); en.hasMoreElements(); i++) {
            keys[i] = (String) en.nextElement();
        }

        // Get SQL for reading identity column
        String sql = pOM.buildIdentitySQL(keys)
            + buildWhereClause(keys, pMapping);

        // Log?
        mLog.logDebug(sql + " (" + pWhere + ")");

        // Prepare statement and set values
        PreparedStatement statement = mConnection.prepareStatement(sql);	
        for (int j = 0; j < keys.length; j++) {
            Object key =  pWhere.get(keys[j]);
	    
            if (key instanceof Integer)
                statement.setInt(j + 1, ((Integer) key).intValue());
            else if (key instanceof BigDecimal)
                statement.setBigDecimal(j + 1, (BigDecimal) key);
            else
                statement.setString(j + 1, key.toString());
        }
	
        // Execute query
        ResultSet rs = null;
        try {
            rs = statement.executeQuery();
        }
        catch (SQLException e) {
            mLog.logError(sql + " (" + pWhere + ")", e);
            throw e;
        }
        Vector result = new Vector();

        // Map query to objects
        while (rs.next()) {
            Object obj = null;
	    
            try {
                obj = pObjClass.newInstance();
            }
            catch (IllegalAccessException iae) {
                iae.printStackTrace();
            }
            catch (InstantiationException ie) {
                ie.printStackTrace();
            }
	    
            // Map it
            pOM.mapColumnProperty(rs, 1, 
                                  pOM.getProperty(pOM.getPrimaryKey()), obj);
            result.addElement(obj);
        }

        // Return array of identifiers
        return result.toArray((Object[]) Array.newInstance(pObjClass, 
                                                           result.size()));
    }


    /**
     * Reads one object implementing the DatabaseReadable interface from the 
     * database.
     *
     * @param readable A DatabaseReadable object
     * @return The Object read, or null in no object is found
     */

    public Object readObject(DatabaseReadable pReadable) throws SQLException {
        return readObject(pReadable.getId(), pReadable.getClass(), 
                          pReadable.getMapping());
    }

    /**
     * Reads the object with the given id from the database, using the given
     * mapping.
     *
     * @param id An object uniquely identifying the object to read
     * @param objClass The clas 
     * @return The Object read, or null in no object is found
     */

    public Object readObject(Object pId, Class pObjClass, Hashtable pMapping)
        throws SQLException {
        return readObject(pId, pObjClass, pMapping, null);
    }

    /**
     * Reads all the objects of the given type  from the 
     * database. The object must implement the DatabaseReadable interface.
     *
     * @return An array of Objects, or an zero-length array if none was found
     */

    public Object[] readObjects(DatabaseReadable pReadable) 
        throws SQLException {
	    return readObjects(pReadable.getClass(), 
                           pReadable.getMapping(), null);
    }

    /**
     * Sets the property value to an object using reflection
     *
     * @param obj The object to get a property from
     * @param property The name of the property
     * @param value The property value
     *
     */

    private void setPropertyValue(Object pObj, String pProperty, 
                                  Object pValue) {
	
        Method m = null;
        Class[] cl = {pValue.getClass()};
	
        try {
            //Util.setPropertyValue(pObj, pProperty, pValue);	   
	    
            // Find method
            m = pObj.getClass().
                getMethod("set" + StringUtil.capitalize(pProperty), cl);
            // Invoke it
            Object[] args = {pValue};
            m.invoke(pObj, args);
	    
        }
        catch (NoSuchMethodException e) {
            e.printStackTrace();
        }		
        catch (IllegalAccessException iae) {
            iae.printStackTrace();
        }
        catch (InvocationTargetException ite) {
            ite.printStackTrace();
        }

    }

    /**
     * Gets the property value from an object using reflection
     *
     * @param obj The object to get a property from
     * @param property The name of the property
     *
     * @return The property value as an Object
     */

    private Object getPropertyValue(Object pObj, String pProperty) {
	
        Method m = null;
        Class[] cl = new Class[0];
	
        try {
            //return Util.getPropertyValue(pObj, pProperty);
	    
            // Find method
            m = pObj.getClass().
                getMethod("get" + StringUtil.capitalize(pProperty), 
                          new Class[0]);
            // Invoke it
            Object result = m.invoke(pObj, new Object[0]);	    
            return result;
	    
        }
        catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        catch (IllegalAccessException iae) {
            iae.printStackTrace();
        }
        catch (InvocationTargetException ite) {
            ite.printStackTrace();
        }
        return null;
    }

    /**
     * Reads and sets the child properties of the given parent object.
     *
     * @param parent The object to set the child obects to.
     * @param om The ObjectMapper of the parent object.
     */

    private void setChildObjects(Object pParent, ObjectMapper pOM)
        throws SQLException {
        if (pOM == null) {
            throw new NullPointerException("ObjectMapper in readChildObjects "
                                           + "cannot be null!!");
        }

        for (Enumeration keys = pOM.mMapTypes.keys(); keys.hasMoreElements();) {
            String property = (String) keys.nextElement();
            String mapType = (String) pOM.mMapTypes.get(property);
	    
            if (property.length() <= 0 || mapType == null) {
                continue;
            }
	    
            // Get the id of the parent
            Object id = getPropertyValue(pParent, 
                                         pOM.getProperty(pOM.getPrimaryKey()));

            if (mapType.equals(ObjectMapper.OBJECTMAP)) {
                // OBJECT Mapping		

                // Get the class for this property
                Class objectClass = (Class) pOM.mClasses.get(property);
		
                DatabaseReadable dbr = null;
                try {
                    dbr = (DatabaseReadable) objectClass.newInstance();
                }
                catch (Exception e) {
                    mLog.logError(e);
                }
		
                /*
                  Properties mapping = readMapping(objectClass);
                */
		    
                // Get property mapping for child object
                if (pOM.mJoins.containsKey(property))
                    // mapping.setProperty(".join", (String) pOM.joins.get(property));
                    dbr.getMapping().put(".join", pOM.mJoins.get(property));
		
                // Find id and put in where hash
                Hashtable where = new Hashtable();

                // String foreignKey = mapping.getProperty(".foreignKey");
                String foreignKey = (String) 
                    dbr.getMapping().get(".foreignKey");

                if (foreignKey != null) {
                    where.put(".foreignKey", id);
                }

                Object[] child = readObjects(dbr, where);
                // Object[] child = readObjects(objectClass, mapping, where);

                if (child.length < 1)
                    throw new SQLException("No child object with foreign key "
                                           + foreignKey + "=" + id);
                else if (child.length != 1)
                    throw new SQLException("More than one object with foreign "
                                           + "key " + foreignKey + "=" + id);

                // Set child object to the parent
                setPropertyValue(pParent, property, child[0]);
            }
            else if (mapType.equals(ObjectMapper.COLLECTIONMAP)) {
                // COLLECTION Mapping

                // Get property mapping for child object
                Hashtable mapping = pOM.getPropertyMapping(property);

                // Find id and put in where hash
                Hashtable where = new Hashtable();
                String foreignKey = (String) mapping.get(".foreignKey");
                if (foreignKey != null) {
                    where.put(".foreignKey", id);
                }
		
                DBObject dbr = new DBObject();
                dbr.mapping = mapping; // ugh...
                // Read the objects
                Object[] objs = readObjects(dbr, where);

                // Put the objects in a hash
                Hashtable children = new Hashtable();
                for (int i = 0; i < objs.length; i++) {
                    children.put(((DBObject) objs[i]).getId(), 
                                 ((DBObject) objs[i]).getObject());
                }
		
                // Set child properties to parent object
                setPropertyValue(pParent, property, children);
            }
        }
    }

    /**
     * Reads all objects from the database, using the given mapping.
     *
     * @param objClass The class of the objects to read
     * @param mapping The hashtable containing the object mapping
     *
     * @return An array of Objects, or an zero-length array if none was found
     */

    public Object[] readObjects(Class pObjClass, Hashtable pMapping)
        throws SQLException {
        return readObjects(pObjClass, pMapping, null);
    }

    /**
     * Builds extra SQL WHERE clause
     *
     * @param keys An array of ID names
     * @param mapping The hashtable containing the object mapping
     *
     * @return A string containing valid SQL
     */

    private String buildWhereClause(String[] pKeys, Hashtable pMapping) {
        StringBuilder sqlBuf = new StringBuilder();

        for (int i = 0; i < pKeys.length; i++) {
            String column = (String) pMapping.get(pKeys[i]);
            sqlBuf.append(" AND ");
            sqlBuf.append(column);
            sqlBuf.append(" = ?");
        }

        return sqlBuf.toString();

    }

    private String buildIdInClause(Object[] pIds, Hashtable pMapping) {
        StringBuilder sqlBuf = new StringBuilder();

        if (pIds != null && pIds.length > 0) {
            sqlBuf.append(" AND ");
            sqlBuf.append(pMapping.get(".primaryKey"));
            sqlBuf.append(" IN (");

            for (int i = 0; i < pIds.length; i++) {
                sqlBuf.append(pIds[i]); // SETTE INN '?' ???
                sqlBuf.append(", ");
            }
            sqlBuf.append(")");
        }

        return sqlBuf.toString();

    }

    /**
     * Reads all objects from the database, using the given mapping.
     *
     * @param readable A DatabaseReadable object
     * @param mapping The hashtable containing the object mapping
     *
     * @return An array of Objects, or an zero-length array if none was found
     */    

    public Object[] readObjects(DatabaseReadable pReadable, Hashtable pWhere) 
        throws SQLException {
	    return readObjects(pReadable.getClass(),
                           pReadable.getMapping(), pWhere);
    }


    /**
     * Reads the object with the given id  from the database, using the given
     * mapping.
     * This is the most general form of readObject().
     *
     * @param id An object uniquely identifying the object to read
     * @param objClass The class of the object to read
     * @param mapping The hashtable containing the object mapping
     * @param where An hashtable containing extra criteria for the read
     *
     * @return An array of Objects, or an zero-length array if none was found
     */    

    public Object readObject(Object pId, Class pObjClass, 
                             Hashtable pMapping, Hashtable pWhere)
        throws SQLException {
        ObjectMapper om = new ObjectMapper(pObjClass, pMapping);
        return readObject0(pId, pObjClass, om, pWhere);
    }

    public Object readObjects(Object[] pIds, Class pObjClass, 
                              Hashtable pMapping, Hashtable pWhere)
        throws SQLException {
        ObjectMapper om = new ObjectMapper(pObjClass, pMapping);
        return readObjects0(pIds, pObjClass, om, pWhere);
    }

    /**
     * Reads all objects from the database, using the given mapping.
     * This is the most general form of readObjects().
     *
     * @param objClass The class of the objects to read
     * @param mapping The hashtable containing the object mapping
     * @param where An hashtable containing extra criteria for the read
     *
     * @return An array of Objects, or an zero-length array if none was found
     */    

    public Object[] readObjects(Class pObjClass, Hashtable pMapping, 
                                Hashtable pWhere) throws SQLException {
        return readObjects0(pObjClass, pMapping, pWhere);
    }

    // readObjects implementation

    private Object[] readObjects0(Class pObjClass, Hashtable pMapping, 
                                  Hashtable pWhere) throws SQLException {
        ObjectMapper om = new ObjectMapper(pObjClass, pMapping);

        Object[] ids = readIdentities(pObjClass, pMapping, pWhere, om);

        Object[] result = readObjects0(ids, pObjClass, om, pWhere); 

        return result;
    }

    private Object[] readObjects0(Object[] pIds, Class pObjClass, 
                                  ObjectMapper pOM, Hashtable pWhere) 
        throws SQLException {
        Object[] result = new Object[pIds.length];

        // Read each object from ID
        for (int i = 0; i < pIds.length; i++) {
	
            // TODO: For better cahce efficiency/performance:
            // - Read as many objects from cache as possible
            // - Read all others in ONE query, and add to cache
            /*
              sCacheUn++;
              // Build SQL query string
              if (pWhere == null)
              pWhere = new Hashtable();
	
              String[] keys = new String[pWhere.size()];
              int i = 0;
              for (Enumeration en = pWhere.keys(); en.hasMoreElements(); i++) {
              keys[i] = (String) en.nextElement();
              }

              // Get SQL for reading identity column
              String sql = pOM.buildSelectClause() + pOM.buildFromClause() + 
              + buildWhereClause(keys, pMapping) + buildIdInClause(pIds, pMapping);

              // Log?
              mLog.logDebug(sql + " (" + pWhere + ")");


              // Log?
              mLog.logDebug(sql + " (" + pWhere + ")");

              PreparedStatement statement = null;

              // Execute query, and map columns/properties
              try {
              statement = mConnection.prepareStatement(sql);
	    
              // Set keys 
              for (int j = 0; j < keys.length; j++) {
              Object value = pWhere.get(keys[j]);

              if (value instanceof Integer)
              statement.setInt(j + 1, ((Integer) value).intValue());
              else if (value instanceof BigDecimal)
              statement.setBigDecimal(j + 1, (BigDecimal) value);
              else
              statement.setString(j + 1, value.toString());
              }
              // Set ids
              for (int j = 0; j < pIds.length; j++) {
              Object id = pIds[i];

              if (id instanceof Integer)
              statement.setInt(j + 1, ((Integer) id).intValue());
              else if (id instanceof BigDecimal)
              statement.setBigDecimal(j + 1, (BigDecimal) id);
              else
              statement.setString(j + 1, id.toString());
              }

              ResultSet rs = statement.executeQuery();

              Object[] result = pOM.mapObjects(rs);

              // Set child objects and return
              for (int i = 0; i < result.length; i++) {
              // FOR THIS TO REALLY GET EFFECTIVE, WE NEED TO SET ALL
              // CHILDREN IN ONE GO!
              setChildObjects(result[i], pOM);
              mContent.put(pOM.getPrimaryKey() + "=" + pId, result[0]);

              }
              // Return result
              return result[0];

              }
            */

            Object id = getPropertyValue(result[i], 
                                         pOM.getProperty(pOM.getPrimaryKey()));

            result[i] = readObject0(id, pObjClass, pOM, null);

        }

        return result;
    }

    // readObject implementation, used for ALL database reads

    static long sCacheHit;
    static long sCacheMiss;
    static long sCacheUn;
    
    private Object readObject0(Object pId, Class pObjClass, ObjectMapper pOM, 
                               Hashtable pWhere) throws SQLException {
        if (pId == null && pWhere == null)
            throw new IllegalArgumentException("Either id or where argument" 
                                               + "must be non-null!");
	
        // First check if object exists in cache
        if (pId != null) {
            Object o = mCache.get(pOM.getPrimaryKey() + "=" + pId);
            if (o != null) {
                sCacheHit++;
                return o;
            }
            sCacheMiss++;
        }
        else {
            sCacheUn++;
        }
	
        // Create where hash
        if (pWhere == null) 
            pWhere = new Hashtable();

        // Make sure the ID is in the where hash
        if (pId != null)
            pWhere.put(pOM.getProperty(pOM.getPrimaryKey()), pId);
	
        String[] keys = new String[pWhere.size()];
        Enumeration en = pWhere.keys();
        for (int i = 0; en.hasMoreElements(); i++) {
            keys[i] = (String) en.nextElement();
        }	

        // Get SQL query string
        String sql = pOM.buildSQL() + buildWhereClause(keys, pOM.mPropertiesMap);

        // Log?
        mLog.logDebug(sql + " (" + pWhere + ")");

        PreparedStatement statement = null;

        // Execute query, and map columns/properties
        try {
            statement = mConnection.prepareStatement(sql);
	    
            for (int j = 0; j < keys.length; j++) {
                Object value = pWhere.get(keys[j]);

                if (value instanceof Integer)
                    statement.setInt(j + 1, ((Integer) value).intValue());
                else if (value instanceof BigDecimal)
                    statement.setBigDecimal(j + 1, (BigDecimal) value);
                else
                    statement.setString(j + 1, value.toString());
            }

            ResultSet rs = statement.executeQuery();

            Object[] result = pOM.mapObjects(rs);

            // Set child objects and return
            if (result.length == 1) {
                setChildObjects(result[0], pOM);
                mCache.put(pOM.getPrimaryKey() + "=" + pId, result[0]);

                // Return result
                return result[0];
            }
            // More than 1 is an error...
            else if (result.length > 1) {
                throw new SQLException("More than one object with primary key "
                                       + pOM.getPrimaryKey() + "=" 
                                       + pWhere.get(pOM.getProperty(pOM.getPrimaryKey())) + "!");
            }
        }
        catch (SQLException e) {
            mLog.logError(sql + " (" + pWhere + ")", e);
            throw e;
        }
        finally {
            try {
                statement.close();
            }
            catch (SQLException e) {
                mLog.logError(e);
            }
        }

        return null;
    }

    /**
     * Utility method for reading a property mapping from a properties-file
     *
     */
    
    public static Properties loadMapping(Class pClass) {
        try {
            return SystemUtil.loadProperties(pClass);
        }
        catch (FileNotFoundException fnf) {
            // System.err... err... 
            System.err.println("ERROR: " + fnf.getMessage());	    
        }
        catch (IOException ioe) {
            ioe.printStackTrace();    
        }
        return new Properties();	    
    }

    /**
     * @deprecated Use loadMapping(Class) instead
     * @see #loadMapping(Class)
     */

    public static Properties readMapping(Class pClass) {
        return loadMapping(pClass);
    }


}

/**
 * Utility class
 */

class DBObject implements DatabaseReadable {
    Object id;
    Object o;
    static Hashtable mapping; // WHOA, STATIC!?!?
    
    public DBObject() {
    }
    
    public void setId(Object id) {
        this.id = id;
    }
    public Object getId() {
        return id;
    }
    
    public void setObject(Object o) {
        this.o = o;
    }
    public Object getObject() {
        return o;
    }
    
    public Hashtable getMapping() {
        return mapping;
    }
}


