public void cartesianProductOrUnionIfSameHeaders(Table t) {

    int t1Cols = this.getColumnsCount();
    int t2Cols = t.getColumnsCount();

    if (t2Cols == 0) return;
    else if (t1Cols == 0) {

      if (this.headers == null) this.headers = new ArrayList<Attribute>();

      for (Attribute att : t.getHeaders()) this.headers.add(new Attribute(att));

      if (this.values == null) this.values = new ArrayList<List<String>>();

      if (t.getValues() != null)
        for (List<String> v : t.getValues()) if (v != null) values.add(new ArrayList<String>(v));
    } else {

      if (sameHeaders(t)) {
        List<Table> tables = new ArrayList<Table>();
        tables.add(this);
        tables.add(t);
        Table result = union(tables);
        this.setHeaders(result.getHeaders());
        this.setValues(result.getValues());
        return;
      }

      for (Attribute att : t.getHeaders()) headers.add(new Attribute(att));

      int t1Rows = this.getRowsCount();
      int t2Rows = t.getRowsCount();
      int totalRows = t1Rows == 0 || t2Rows == 0 ? t1Rows + t2Rows : t1Rows * t2Rows;

      List<List<String>> values = new ArrayList<List<String>>();

      for (int i = 0; i < totalRows; i++) {

        List<String> row = new ArrayList<String>();

        for (int j = 0; j < t1Cols; j++) {
          int index = t1Rows == 0 ? -1 : i % t1Rows;
          if (index == -1 || this.values == null || this.values.get(index) == null) row.add(null);
          else row.add(this.values.get(index).get(j));
        }
        for (int j = 0; j < t2Cols; j++) {
          int index = t2Rows == 0 ? -1 : i % t2Rows;
          if (index == -1 || t.getValues() == null || t.getValues().get(index) == null)
            row.add(null);
          else row.add(t.getValues().get(index).get(j));
        }

        values.add(row);
      }

      this.values = values;
    }
  }
  /**
   * Each service invocation might have different columns than other other invocations. This method
   * integrates all results into one table. For the invocations that don't have a specific column,
   * we put null values in corresponding column.
   */
  public static Table union(List<Table> srcTables) {

    if (srcTables == null) return null;

    Table resultTable = new Table();

    String attributeId = "";
    List<String> uniqueAttributeIDs = new ArrayList<String>();

    List<List<Attribute>> srcAttributes = new ArrayList<List<Attribute>>();
    List<List<List<String>>> srcValues = new ArrayList<List<List<String>>>();
    List<List<String>> srcRowIds = new ArrayList<List<String>>();

    List<Attribute> resultAttributes = new ArrayList<Attribute>();
    List<List<String>> resultValues = new ArrayList<List<String>>();
    List<String> resultRowIds = new ArrayList<String>();

    for (Table t : srcTables) {
      srcAttributes.add(t.getHeaders());
      srcRowIds.add(t.getRowIds());
      srcValues.add(t.getValues());
    }

    for (int i = 0; i < srcAttributes.size(); i++) {
      for (int j = 0; j < srcAttributes.get(i).size(); j++) {
        attributeId = srcAttributes.get(i).get(j).getId().toString();
        if (uniqueAttributeIDs.indexOf(attributeId) == -1) {
          uniqueAttributeIDs.add(attributeId);
          resultAttributes.add(srcAttributes.get(i).get(j));
        }
      }
    }

    List<Attribute> rawAttributes = null;
    List<String> rawAttributeIDs = new ArrayList<String>();
    List<String> rawValues = null;
    String singleValue = null;
    for (int i = 0; i < srcAttributes.size(); i++) {

      rawAttributes = srcAttributes.get(i);
      rawAttributeIDs.clear();
      for (Attribute p : rawAttributes) rawAttributeIDs.add(p.getId());

      //			logger.debug("table " + i);
      for (int j = 0; j < srcValues.get(i).size(); j++) {

        List<String> populatedValues = new ArrayList<String>();
        rawValues = srcValues.get(i).get(j);

        //				logger.debug("\t row " + j);
        for (int k = 0; k < resultAttributes.size(); k++) {
          //					logger.debug("\t\t column " + k);
          int index = rawAttributeIDs.indexOf(resultAttributes.get(k).getId());
          if (index == -1) singleValue = null;
          //						singleValue = "";
          else singleValue = rawValues.get(index);
          populatedValues.add(singleValue);
        }

        if (srcRowIds != null
            && srcRowIds.size() > 0
            && srcRowIds.get(i) != null
            && srcRowIds.get(i).size() > 0
            && srcRowIds.get(i).get(j) != null) resultRowIds.add(srcRowIds.get(i).get(j));
        resultValues.add(populatedValues);
      }
    }

    resultTable.setHeaders(resultAttributes);
    resultTable.setRowIds(resultRowIds);
    resultTable.setValues(resultValues);

    return resultTable;
  }