/**
  * Returns all {@link InsertRow}s that reference a column in this <code>InsertRow</code>. The rows
  * may well be from other tables.
  *
  * @return all InsertRows that reference a column in this InsertRow
  */
 Collection<InsertRow> getReferencingRows() {
   Set<InsertRow> referencingRows = new HashSet<InsertRow>();
   Iterator<InsertField> iter = this.columnMap.values().iterator();
   while (iter.hasNext()) {
     InsertField field = iter.next();
     referencingRows.addAll(field.getReferencingRows());
   }
   return referencingRows;
 }
 /**
  * Returns all {@link InsertField} that reference a column in this <code>InsertRow</code>. The
  * fields may well be from other tables.
  *
  * @return all InsertFields that reference a column in this InsertRow
  */
 public List<InsertField> getReferencingFields() {
   List<InsertField> referencingFields = new ArrayList<InsertField>();
   Iterator<InsertField> iter = this.columnMap.values().iterator();
   while (iter.hasNext()) {
     InsertField field = iter.next();
     referencingFields.addAll(field.getReferencingFields());
   }
   return referencingFields;
 }
 /**
  * Returns the {@link InsertField} for the primary key column.
  *
  * @return the InsertField for the primary key column
  */
 public InsertField getPKColumn() {
   InsertField pkField = null;
   for (InsertField field : this.columnMap.values()) {
     if (field.isPK()) {
       pkField = field;
       break;
     }
   }
   return pkField;
 }
 /**
  * Returns all {@link InsertRow}s that are referenced by a field from this <code>InsertRow</code>.
  * The rows may well be from other tables.
  *
  * @return all InsertRows that are referenced by a field from this InsertRow
  */
 private Collection<InsertRow> getReferencedRows() {
   Set<InsertRow> referencedRows = new HashSet<InsertRow>();
   Iterator<InsertField> iter = this.columnMap.values().iterator();
   while (iter.hasNext()) {
     InsertField field = iter.next();
     InsertRow referencedRow = field.getReferencedRow();
     if (referencedRow != null) {
       referencedRows.add(referencedRow);
     }
   }
   return referencedRows;
 }
 /**
  * Returns all {@link InsertField}s that are referenced by a field from this <code>InsertRow
  * </code>. The fields may well be from other tables.
  *
  * @return all InsertField that are referenced by a field from this InsertRow
  */
 List<InsertField> getReferencedFields() {
   List<InsertField> referencedFields = new ArrayList<InsertField>();
   Iterator<InsertField> iter = this.columnMap.values().iterator();
   while (iter.hasNext()) {
     InsertField field = iter.next();
     InsertField referencedField = field.getReferencedField();
     if (referencedField != null) {
       referencedFields.add(referencedField);
     }
   }
   return referencedFields;
 }
  /**
   * Sets the value to be inserted in the given table column - the column references the given
   * {@link InsertField} and thus must have the same value as the referenced column.
   *
   * <p>In complex + erroneous mappings (or feature instances), it may happen that different
   * property values (mapped to the same column) imply different values. This is checked for and in
   * case an {@link TransactionException} is thrown.
   *
   * @param column
   * @param referencedField
   * @throws TransactionException if the value for the column clashes
   */
  public void linkColumn(String column, InsertField referencedField) throws TransactionException {

    if (referencedField.getValue() == null) {
      LOG.logError("linkColumn (): referencedField is null");
      throw new TransactionException("linkColumn (): referenced field is null!");
    }

    InsertField presentField = this.columnMap.get(column);
    if (presentField != null
        && (!presentField.getValue().toString().equals(referencedField.getValue().toString()))) {
      Object[] params =
          new Object[] {
            column,
            this.table,
            presentField.getValue().toString(),
            referencedField.getValue().toString()
          };
      String msg = Messages.getMessage("DATASTORE_AMBIGOUS_COLUMN_VALUES", params);
      throw new TransactionException(msg);
    }

    InsertField field = presentField;
    if (presentField != null) {
      presentField.relinkField(referencedField);
    } else {
      field = new InsertField(this, column, referencedField);
      this.columnMap.put(column, field);
    }
    referencedField.addReferencingField(field);
  }
  /**
   * Sets the value to be inserted in the given table column.
   *
   * <p>In complex + erroneous mappings (or feature instances), it may happen that different
   * property values (mapped to the same column) imply different values. This is checked for and in
   * case an {@link TransactionException} is thrown.
   *
   * @param column
   * @param value
   * @param sqlType
   * @param isPK
   * @return column + value to be set
   * @throws TransactionException if the value for the column clashes
   */
  public InsertField setColumn(String column, Object value, int sqlType, boolean isPK)
      throws TransactionException {

    InsertField field = new InsertField(this, column, sqlType, value, isPK);
    InsertField presentField = columnMap.get(column);
    if (presentField != null && (!presentField.getValue().toString().equals(value.toString()))) {
      Object[] params =
          new Object[] {column, this.table, presentField.getValue().toString(), value.toString()};
      String msg = Messages.getMessage("DATASTORE_AMBIGOUS_COLUMN_VALUES", params);
      throw new TransactionException(msg);
    }
    if (presentField == null) {
      this.columnMap.put(column, field);
    }
    // TODO type check
    return field;
  }
  /**
   * Sorts the given <code>InsertRow</code>s topologically (respecting the foreign key constraints),
   * so they can be inserted in the resulting order without causing foreign key violations.
   *
   * <p>Number of precedessors (pre): number of fields that *are referenced by* this row Number of
   * successors (post) : number of fields that *reference* this row
   *
   * @param inserts insert rows to sort
   * @return the nodes of the graph in topological order if no cycle is present, else in arbitrary
   *     order
   */
  public static List<InsertRow> getInsertOrder(List<InsertRow> inserts) {

    List<InsertRow> result = new ArrayList<InsertRow>();

    // key: inserts, value: number of fields that are referenced by this row
    Map<InsertRow, Integer> preMap = new HashMap<InsertRow, Integer>();

    // key: inserts with no foreign key constraints
    List<InsertRow> noPre = new ArrayList<InsertRow>();

    // build map
    Iterator<InsertRow> insertIter = inserts.iterator();
    while (insertIter.hasNext()) {
      InsertRow insertRow = insertIter.next();
      int pre = insertRow.getReferencedFields().size();
      LOG.logDebug("Adding row to preMap: " + insertRow);
      preMap.put(insertRow, pre);
      if (pre == 0) {
        noPre.add(insertRow);
      }
    }

    while (!noPre.isEmpty()) {
      // select an insert row that has no open fk constraints
      InsertRow insertRow = noPre.get(0);
      noPre.remove(0);
      result.add(insertRow);

      // decrease the number of pending fk constraints for all insert rows that
      // reference the currently processed insert row
      Collection<InsertField> postList = insertRow.getReferencingFields();
      Iterator<InsertField> iter = postList.iterator();
      while (iter.hasNext()) {
        InsertField postField = iter.next();
        if (preMap.get(postField.getRow()) == null) {
          LOG.logDebug("No pre info for: " + postField.getRow());
        }
        int pre = preMap.get(postField.getRow());
        preMap.put(postField.getRow(), --pre);
        if (pre == 0) {
          noPre.add(postField.getRow());
        }
      }
    }

    if (result.size() != inserts.size()) {
      Collection<InsertRow> cycle = InsertRow.findCycle(inserts);
      Iterator<InsertRow> cycleIter = cycle.iterator();
      StringBuffer sb = new StringBuffer();
      while (cycleIter.hasNext()) {
        sb.append(cycleIter.next());
        if (cycle.iterator().hasNext()) {
          sb.append(" -> ");
        }
      }
      String msg = Messages.getMessage("DATASTORE_FK_CYCLE", sb.toString());
      LOG.logWarning(msg);
      result.clear();
      result.addAll(inserts);
    }
    return result;
  }