Table.java [src/soils/db/tables] Revision: default  Date:
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package soils.db.tables;

import csip.api.server.ServiceException;
import static csip.utils.JSONUtils.preprocess;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import soils.exceptions.SDMException;

/**
 * Abstract base class for all subsequent database tables.
 *
 * @author <a href="mailto:shaun.case@colostate.edu">Shaun Case</a>
 */
public abstract class Table {

  /**
   * This ConcurrentHashMap contains all of the columns made available for this
   * table to be used by any calling programs. This list can be added to over
   * time if the database is edited or if columns were initially left out of the
   * list because no calling programs were currently using them. An
   * implementations of this class should use the addDataColumn function to add
   * columns to the list. This list is final because it should only be
   * created/modified once during construction of the implementing class.
   *
   * @see Table#addDataColumn
   */
  protected final ConcurrentHashMap<String, TableColumn> columns = new ConcurrentHashMap<>();
  protected ArrayList<String> outputOrderList = new ArrayList<>();
  protected String tableName = "";
  protected String schemaName = "";

  protected ArrayList<String> getMandatoryColumns() {
    return null;
  }

  public synchronized void deepCopy(Table newTable) {
    if (null != newTable) {
      ConcurrentHashMap<String, TableColumn> newColumns = newTable.getColumns();

      for (String key : columns.keySet()) {
        if (newColumns.containsKey(key)) {
          newColumns.get(key).setValue(columns.get(key).value);
        }
      }
    }
  }

  public ConcurrentHashMap<String, TableColumn> getColumns() {
    return columns;
  }

  public Enumeration<String> getColumnNames() {
    return columns.keys();
  }

  public String getFullColumnNames(boolean onlyUsed) {
    String ret_val = "";
    int count = 0;

    for (TableColumn column : columns.values()) {
      if ((column.isUsed() && onlyUsed) || (!onlyUsed)) {
        if (count > 0) {
          ret_val += ", ";
        }
        ret_val += tableName + "." + column.name;
        count++;
      }
    }
    return ret_val;
  }

  public String getTableName() {
    return tableName;
  }

  /**
   * This function adds a new column to the stored list of columns that an
   * implementation of this abstract class needs for its uses. This function is
   * final because it interacts with a member of this abstract class, neither of
   * which should be overridden.
   *
   * @param name
   * @param newColumn
   */
  public final void addDataColumn(String name, TableColumn newColumn) {
    if ((null != columns) && (null != name) && (!name.isEmpty()) && (!columns.containsKey(name)) && (null != newColumn)) {
      columns.put(name, newColumn);
    }
  }

  /**
   * This function allows access to the underlying columns' data class.
   *
   * @param name The string value of the name that you want access to.
   * @return Returns the TableColumn matching the name parameter or throws an
   * exception when not found.
   * @throws ServiceException
   *
   * @see TableColumn
   * @see TableColumnBoolean
   * @see TableColumnDouble
   * @see TableColumnInteger
   * @see TableColumnString
   *
   */
  public TableColumn get(String name) throws ServiceException {
    TableColumn ret_val = null;

    if ((null != name) && (columns.containsKey(name))) {
      ret_val = columns.get(name);
    } else {
      throw new ServiceException("Column name, " + name + ", not found in this database_table instance.");
    }

    return ret_val;
  }

  public ArrayList<String> getColumnList() {
    ArrayList<String> ret_val = new ArrayList<>();

    for (String key : columns.keySet()) {
      ret_val.add(key);
    }

    return ret_val;
  }

  /**
   *
   * @param dontUseThis
   */
  public void setDontUseColumn(String dontUseThis) {
    if (null != dontUseThis) {
      TableColumn column = columns.get(dontUseThis);
      if ((null != column) && ((null != getMandatoryColumns()) && (!getMandatoryColumns().contains(dontUseThis)))) {
        column.setUsed(false);
      }
    }
  }

  /**
   *
   * @param dontUseThis
   */
  public void setNonOutputColumn(String dontUseThis) {
    if (null != dontUseThis) {
      TableColumn column = columns.get(dontUseThis);
      if (null != column) {
        column.setIncludeInOutput(false);
      }
    }
  }

  /**
   * This function sets the columns of this table to not be included in output
   * of JSON arrays, if those columns are contained in the parameter
   * dontUseList. The side-effect of this function is that all columns not
   * listed in the parameter dontUseList are set to be used in the output.
   *
   * @param dontUseList
   */
  public void setNonOutputColumns(List<String> dontUseList) {
    for (String key : columns.keySet()) {
      if ((null != dontUseList) && dontUseList.contains(key)) {
        columns.get(key).setIncludeInOutput(false);
      } else {
        columns.get(key).setIncludeInOutput(true);
      }
    }
  }

  /**
   *
   * @param useThis
   */
  public void setOutputColumn(String useThis) {
    if ((null != useThis) && (!useThis.isEmpty())) {
      TableColumn column = columns.get(useThis);
      if (null != column) {
        column.setIncludeInOutput(true);
      }
    }
  }

  /**
   *
   * @param orderedList
   */
  public void setOutputColumnOrdering(List<String> orderedList) {
    if (null != orderedList) {
      if (outputOrderList.size() > 0) {
        outputOrderList.clear();
      }

      for (String key : orderedList) {
        outputOrderList.add(key);
      }

      setOutputColumns(orderedList);
    }
  }

  /**
   *
   * @param usedList
   */
  public void setOutputColumns(List<String> usedList) {
    if (null != usedList) {
      for (String key : columns.keySet()) {
        if ((null != usedList) && usedList.contains(key)) {
          columns.get(key).setIncludeInOutput(true);
        } else {
          columns.get(key).setIncludeInOutput(false);
        }
      }
    }
  }

  /**
   * This function is used to set the columns that are required when
   * <b>reading</b>
   * JSON input only. It has no effect on columns used for SQL statements or for
   * output to JSON. It has the side-effect of setting all table columns that
   * are not included in the parameter requiredList to be <b>not required</b>
   * for reading input JSON.
   *
   * @param requiredList
   */
  public void setRequiredColumns(List<String> requiredList) {
    if (null != requiredList) {
      for (String key : columns.keySet()) {
        if ((null != requiredList) && requiredList.contains(key)) {
          columns.get(key).setRequired(true);
        } else {
          columns.get(key).setRequired(false);
        }
      }
    }
  }

  /**
   *
   * @param dontUseList
   */
  public void setUnusedColumns(List<String> dontUseList) {
    for (String key : columns.keySet()) {
      if ((null != dontUseList) && dontUseList.contains(key)) {
        columns.get(key).setUsed(false);
      } else {
        columns.get(key).setUsed(true);
      }
    }

    if (null != getMandatoryColumns()) {
      for (String key : getMandatoryColumns()) {
        if (columns.contains(key)) {
          columns.get(key).setUsed(true);
        }
      }
    }

  }

  /**
   *
   * @param useThis
   */
  public void setUseColumn(String useThis) {
    if ((null != useThis) && (!useThis.isEmpty())) {
      TableColumn column = columns.get(useThis);
      if (null != column) {
        column.setUsed(true);
      }
    }
  }

  /**
   *
   * @param usedList
   */
  public void setUsedColumns(List<String> usedList) {
    for (String key : columns.keySet()) {
      if ((null != usedList) && usedList.contains(key)) {
        columns.get(key).setUsed(true);
      } else {
        columns.get(key).setUsed(false);
      }
    }
    if (null != getMandatoryColumns()) {
      for (String key : getMandatoryColumns()) {
        if (columns.contains(key)) {
          columns.get(key).setUsed(true);
        }
      }
    }
  }

  /**
   *
   * @param table
   * @throws ServiceException
   */
  public void merge(Table table) throws ServiceException {
    if (null == table) {
      throw new ServiceException("NULL Table specified for merge operation.  Cannot continue.");
    }
    for (String key : columns.keySet()) {
      if (table.get(key).wasSetInJSON()) {
        columns.get(key).setValue(table.get(key).getValue());
        columns.get(key).wasSetInJSON(true);
      }
    }
  }

  /**
   *
   * @param dataJSON
   * @throws ServiceException
   * @throws JSONException
   */
  public final void readValuesFromJSON(JSONArray dataJSON) throws ServiceException, JSONException {
    readValuesFromJSON(preprocess(dataJSON));
  }

  /**
   *
   * @param tableArray
   * @throws ServiceException
   * @throws JSONException
   */
  public final void readValuesFromJSON(Map<String, JSONObject> tableArray) throws ServiceException, JSONException {
    if (null == tableArray) {
      throw new ServiceException("NULL JSON Object Array value was passed to readValuesFromJSON.  Cannot continue.");
    }
    if (tableArray.size() > 0) {
      for (String key : columns.keySet()) {
        if ((columns.get(key).isUsed()) && (tableArray.containsKey(key))) {
          columns.get(key).valueFromJSON(tableArray.get(key));
        } else {
          if ((columns.get(key).isRequired()) && (!tableArray.containsKey(key))) {
            throw new ServiceException("Required variable, " + key + ", was not found in the input JSON.");
          }
        }
      }
    } else {
      throw new ServiceException("An empty JSON Object Array value was passed to readValuesFromJSON.  Cannot continue; no JSON to read.");
    }
  }

  /**
   * This function reads the columns found in the ResultSet passed to it into
   * the Table. If the ResultSet contains columns that were not originally set
   * as "Used" in the column object, those matching columns will be changed to
   * "Used" and the values will be read anyway. The assumption here is that the
   * governing SQL statement executed contains columns for this Table on purpose
   * and therefore, if they were not marked as "Used", i.e. mandatory in reading
   * the ResultSet, then this was a mistake in the calling code for some reason.
   * Ultimately, it is also assumed that reading these values will have no
   * harmful effects on the code using these table functions or else the code
   * would not have specified table columns in the SQL statement that would have
   * reset or otherwise overwritten existing values in the table.
   *
   * @param results
   * @throws ServiceException
   */
  public void readValuesFromSQL(ResultSet results) throws ServiceException {

    for (String key : columns.keySet()) {
      if (columns.get(key).isUsed()) {
        columns.get(key).valueFromSQL(results);
      }
    }

//      for (String key : columns.keySet()) {
//        boolean hasField = true;
//        tr5y {
//          results.findColumn(key);
//        } catch (SQLException ex) {
//          hasField = false;
//        }
//        if (hasField) {
//          columns.get(key).valueFromSQL(results);
//        }
//      }
//        if (null != results) {
//            ResultSetMetaData rsMD;
//            int count = 0;
//            try {
//                rsMD = results.getMetaData();
//
//            } catch (SQLException ex) {
//                throw new ServiceException("Cannot get ResultSetMetaData from the results passed to Table::readValuesFromSQL: " + ex.getMessage(), ex);
//            }
//
//            try {
//                count = rsMD.getColumnCount();
//            } catch (SQLException ex) {
//                throw new ServiceException("Cannot get ResultSetMetaData ColumnCount from the results passed to Table::readValuesFromSQL: " + ex.getMessage(), ex);
//            }
//
//            for (int i = 1; i <= count; i++) {
//                TableColumn tColumn = null;
//                try {
//                    String name = rsMD.getColumnName(i);
//                    if (null != name) {
//                        tColumn = columns.get(name);
//                    }
//                    else{
//                        System.out.println("found a null column name in the returned ResultSet...");
//                    }
//                } catch (SQLException ex) {
//                    throw new ServiceException("Cannot get ColumnName from ResultSetMetaData for index: " + i + ", : " + ex.getMessage(), ex);
//                }
//                if (null != tColumn) {
//                    tColumn.setUsed(true);
//                    tColumn.valueFromSQL(results);
//                }
//            }
//        } else {
//            throw new ServiceException("NULL ResultSet passed to Table::readValuesFromSQL");
//        }
  }

  /**
   *
   * @param outArray
   * @throws JSONException
   */
  public synchronized void toJSON(JSONArray outArray) throws JSONException {
    if (outputOrderList.size() > 0) {
      for (String key : outputOrderList) {
        TableColumn column = columns.get(key);
        if ((null != column) && column.includeInOutput()) {
          outArray.put(columns.get(key).toJSON());
        }
      }
    } else {
      for (String key : columns.keySet()) {
        TableColumn column = columns.get(key);
        if ((null != column) && column.includeInOutput()) {
          outArray.put(columns.get(key).toJSON());
        }
      }
    }

  }

  /**
   *
   * @param outArray
   * @throws JSONException
   */
  public synchronized void toBasicJSON(JSONObject outObject) throws JSONException {
    if (outputOrderList.size() > 0) {
      for (String key : outputOrderList) {
        TableColumn column = columns.get(key);
        if ((null != column) && column.includeInOutput()) {
          outObject.put(key, columns.get(key).toWriteString());
        }
      }
    } else {
      for (String key : columns.keySet()) {
        TableColumn column = columns.get(key);
        if ((null != column) && column.includeInOutput()) {
          outObject.put(key, columns.get(key).toWriteString());
        }
      }
    }

  }

  public boolean isEqual(Table table) throws ServiceException {
    if (!this.tableName.getClass().getName().equals(table.getClass().getName())) {
      return false;
    }
    ArrayList<String> tableKeys = table.getColumnList();
    if (columns.size() != tableKeys.size()) {
      return false;
    }
    for (String key : columns.keySet()) {
      if (!tableKeys.contains(key) || !columns.get(key).isEqual(table.get(key).getValue())) {
        return false;
      }
    }
    return true;
  }
}