/**
   * Generate statement for getting the size of the container. The order part is only present when
   * an order mapping is used. The discriminator part is only present when the element has a
   * discriminator.
   *
   * <PRE>
   * SELECT COUNT(*) FROM TBL THIS
   * [INNER JOIN ELEM_TBL ELEM ON TBL.COL = ELEM.ID] - when no null
   * [LEFT OUTER JOIN ELEM_TBL ELEM ON TBL.COL = ELEM.ID] - when allows null
   * WHERE THIS.OWNERCOL=?
   * [AND THIS.ORDERCOL IS NOT NULL]
   * [AND (DISCRIMINATOR=? OR DISCRMINATOR=? OR DISCRIMINATOR=? [OR DISCRIMINATOR IS NULL])]
   * [AND RELATION_DISCRIM=?]
   * </PRE>
   *
   * The discriminator part includes all subclasses of the element type. If the element is in a
   * different table to the container then an INNER JOIN will be present to link the two tables, and
   * table aliases will be present also.
   *
   * @return The Statement returning the size of the container.
   */
  protected String getSizeStmt() {
    if (sizeStmt != null) {
      // Statement exists and didn't need any discriminator when setting up the statement so just
      // reuse it
      return sizeStmt;
    }

    synchronized (this) {
      boolean usingDiscriminatorInSizeStmt = false;
      String containerAlias = "THIS";
      StringBuilder stmt = new StringBuilder();
      if (elementInfo == null) {
        // Serialised/embedded elements in a join table
        stmt.append("SELECT COUNT(*) FROM ")
            .append(containerTable.toString())
            .append(" ")
            .append(containerAlias);
        stmt.append(" WHERE ");
        BackingStoreHelper.appendWhereClauseForMapping(stmt, ownerMapping, containerAlias, true);
        if (orderMapping != null) {
          // If an ordering is present, restrict to items where the index is not null to
          // eliminate records that are added but may not be positioned yet.
          for (int i = 0; i < orderMapping.getNumberOfDatastoreMappings(); i++) {
            stmt.append(" AND ");
            stmt.append(containerAlias)
                .append(".")
                .append(orderMapping.getDatastoreMapping(i).getColumn().getIdentifier().toString());
            stmt.append(">=0");
          }
        }
        if (relationDiscriminatorMapping != null) {
          BackingStoreHelper.appendWhereClauseForMapping(
              stmt, relationDiscriminatorMapping, containerAlias, false);
        }
        sizeStmt = stmt.toString();
        return sizeStmt;
      }

      if (usingJoinTable()) {
        // Join table collection/array, so do COUNT of join table
        String joinedElementAlias = "ELEM";
        ElementInfo elemInfo = elementInfo[0];

        stmt.append("SELECT COUNT(*) FROM ")
            .append(containerTable.toString())
            .append(" ")
            .append(containerAlias);

        // Add join to element table if required (only allows for 1 element table currently)
        boolean joinedDiscrim = false;
        if (elemInfo.getDiscriminatorMapping() != null) {
          // Need join to the element table to restrict the discriminator
          joinedDiscrim = true;
          JavaTypeMapping elemIdMapping = elemInfo.getDatastoreClass().getIdMapping();
          stmt.append(allowNulls ? " LEFT OUTER JOIN " : " INNER JOIN ");
          stmt.append(elemInfo.getDatastoreClass().toString())
              .append(" ")
              .append(joinedElementAlias)
              .append(" ON ");
          for (int j = 0; j < elementMapping.getNumberOfDatastoreMappings(); j++) {
            if (j > 0) {
              stmt.append(" AND ");
            }
            stmt.append(containerAlias)
                .append(".")
                .append(elementMapping.getDatastoreMapping(j).getColumn().getIdentifier());
            stmt.append("=");
            stmt.append(joinedElementAlias)
                .append(".")
                .append(elemIdMapping.getDatastoreMapping(j).getColumn().getIdentifier());
          }
        }
        // TODO Add join to owner if ownerMapping is for supertable

        stmt.append(" WHERE ");
        BackingStoreHelper.appendWhereClauseForMapping(stmt, ownerMapping, containerAlias, true);
        if (orderMapping != null) {
          // If an ordering is present, restrict to items where the index is not null to
          // eliminate records that are added but may not be positioned yet.
          for (int j = 0; j < orderMapping.getNumberOfDatastoreMappings(); j++) {
            stmt.append(" AND ");
            stmt.append(containerAlias)
                .append(".")
                .append(orderMapping.getDatastoreMapping(j).getColumn().getIdentifier().toString());
            stmt.append(">=0");
          }
        }

        // Add a discriminator filter for collections with an element discriminator
        StringBuilder discrStmt = new StringBuilder();
        if (elemInfo.getDiscriminatorMapping() != null) {
          usingDiscriminatorInSizeStmt = true;
          JavaTypeMapping discrimMapping = elemInfo.getDiscriminatorMapping();

          Collection<String> classNames =
              storeMgr.getSubClassesForClass(elemInfo.getClassName(), true, clr);
          classNames.add(elemInfo.getClassName());
          for (String className : classNames) {
            Class cls = clr.classForName(className);
            if (!Modifier.isAbstract(cls.getModifiers())) {
              for (int j = 0; j < discrimMapping.getNumberOfDatastoreMappings(); j++) {
                if (discrStmt.length() > 0) {
                  discrStmt.append(" OR ");
                }

                discrStmt.append(joinedDiscrim ? joinedElementAlias : containerAlias);
                discrStmt.append(".");
                discrStmt.append(
                    discrimMapping.getDatastoreMapping(j).getColumn().getIdentifier().toString());
                discrStmt.append("=");
                discrStmt.append(
                    ((AbstractDatastoreMapping) discrimMapping.getDatastoreMapping(j))
                        .getUpdateInputParameter());
              }
            }
          }
        }

        if (discrStmt.length() > 0) {
          stmt.append(" AND (");
          stmt.append(discrStmt);
          if (allowNulls) {
            stmt.append(" OR ");
            stmt.append(
                elemInfo
                    .getDiscriminatorMapping()
                    .getDatastoreMapping(0)
                    .getColumn()
                    .getIdentifier()
                    .toString());
            stmt.append(" IS NULL");
          }
          stmt.append(")");
        }
        if (relationDiscriminatorMapping != null) {
          BackingStoreHelper.appendWhereClauseForMapping(
              stmt, relationDiscriminatorMapping, containerAlias, false);
        }
      } else {
        // ForeignKey collection/array, so UNION all of the element COUNTs
        for (int i = 0; i < elementInfo.length; i++) {
          if (i > 0) {
            stmt.append(" UNION ");
          }
          ElementInfo elemInfo = elementInfo[i];

          stmt.append("SELECT COUNT(*),")
              .append("'" + elemInfo.getAbstractClassMetaData().getName() + "'");
          stmt.append(" FROM ")
              .append(elemInfo.getDatastoreClass().toString())
              .append(" ")
              .append(containerAlias);

          stmt.append(" WHERE ");
          BackingStoreHelper.appendWhereClauseForMapping(stmt, ownerMapping, containerAlias, true);
          if (orderMapping != null) {
            // If an ordering is present, restrict to items where the index is not null to
            // eliminate records that are added but may not be positioned yet.
            for (int j = 0; j < orderMapping.getNumberOfDatastoreMappings(); j++) {
              stmt.append(" AND ");
              stmt.append(containerAlias)
                  .append(".")
                  .append(
                      orderMapping.getDatastoreMapping(j).getColumn().getIdentifier().toString());
              stmt.append(">=0");
            }
          }

          // Add a discriminator filter for collections with an element discriminator
          StringBuilder discrStmt = new StringBuilder();
          if (elemInfo.getDiscriminatorMapping() != null) {
            usingDiscriminatorInSizeStmt = true;
            JavaTypeMapping discrimMapping = elemInfo.getDiscriminatorMapping();

            Collection<String> classNames =
                storeMgr.getSubClassesForClass(elemInfo.getClassName(), true, clr);
            classNames.add(elemInfo.getClassName());
            for (String className : classNames) {
              Class cls = clr.classForName(className);
              if (!Modifier.isAbstract(cls.getModifiers())) {
                for (int j = 0; j < discrimMapping.getNumberOfDatastoreMappings(); j++) {
                  if (discrStmt.length() > 0) {
                    discrStmt.append(" OR ");
                  }

                  discrStmt
                      .append(containerAlias)
                      .append(".")
                      .append(
                          discrimMapping
                              .getDatastoreMapping(j)
                              .getColumn()
                              .getIdentifier()
                              .toString());
                  discrStmt.append("=");
                  discrStmt.append(
                      ((AbstractDatastoreMapping) discrimMapping.getDatastoreMapping(j))
                          .getUpdateInputParameter());
                }
              }
            }
          }

          if (discrStmt.length() > 0) {
            stmt.append(" AND (");
            stmt.append(discrStmt);
            if (allowNulls) {
              stmt.append(" OR ");
              stmt.append(
                  elemInfo
                      .getDiscriminatorMapping()
                      .getDatastoreMapping(0)
                      .getColumn()
                      .getIdentifier()
                      .toString());
              stmt.append(" IS NULL");
            }
            stmt.append(")");
          }
          if (relationDiscriminatorMapping != null) {
            BackingStoreHelper.appendWhereClauseForMapping(
                stmt, relationDiscriminatorMapping, containerAlias, false);
          }
        }
      }

      if (!usingDiscriminatorInSizeStmt) {
        sizeStmt = stmt.toString();
      }
      return stmt.toString();
    }
  }
  /**
   * Generate statement for getting the size of thecontainer. The order part is only present when an
   * order mapping is used. The discriminator part is only present when the element has a
   * discriminator.
   *
   * <PRE>
   * SELECT COUNT(*) FROM TBL THIS
   * [INNER JOIN ELEM_TBL ELEM ON TBL.COL = ELEM.ID] - when no null
   * [LEFT OUTER JOIN ELEM_TBL ELEM ON TBL.COL = ELEM.ID] - when allows null
   * WHERE THIS.OWNERCOL=?
   * [AND THIS.ORDERCOL IS NOT NULL]
   * [AND (DISCRIMINATOR=? OR DISCRMINATOR=? OR DISCRIMINATOR=? [OR DISCRIMINATOR IS NULL])]
   * [AND RELATION_DISCRIM=?]
   * </PRE>
   *
   * The discriminator part includes all subclasses of the element type. If the element is in a
   * different table to the container then an INNER JOIN will be present to link the two tables, and
   * table aliases will be present also. TODO Update this to allow for getting the size when more
   * than 1 element table.
   *
   * @return The Statement returning the size of the container.
   */
  protected String getSizeStmt() {
    if (sizeStmt != null && !usingDiscriminatorInSizeStmt) {
      // Statement exists and didnt need any discriminator when setting up the statement so just
      // reuse it
      return sizeStmt;
    }

    synchronized (this) {
      String containerAlias = "THIS";
      String joinedElementAlias = "ELEM";
      StringBuilder stmt = new StringBuilder("SELECT COUNT(*) FROM ");
      stmt.append(getContainerTable().toString()).append(" ").append(containerAlias);

      // Add join to element table if required (only allows for 1 element table currently)
      boolean joinedDiscrim = false;
      if (getElementInfo() != null
          && getElementInfo().length == 1
          && getElementInfo()[0].getDatastoreClass() != getContainerTable()
          && getElementInfo()[0].getDiscriminatorMapping() != null) {
        // TODO Allow for more than 1 possible element table
        // Need join to the element table to restrict the discriminator
        joinedDiscrim = true;
        JavaTypeMapping elemIdMapping = getElementInfo()[0].getDatastoreClass().getIdMapping();
        if (allowNulls) {
          // User wants to allow for nulls so have to use left outer join
          stmt.append(" LEFT OUTER JOIN ");
        } else {
          // No nulls so use inner join
          stmt.append(" INNER JOIN ");
        }
        stmt.append(getElementInfo()[0].getDatastoreClass().toString())
            .append(" ")
            .append(joinedElementAlias)
            .append(" ON ");
        for (int i = 0; i < getElementMapping().getNumberOfDatastoreMappings(); i++) {
          if (i > 0) {
            stmt.append(" AND ");
          }
          stmt.append(containerAlias)
              .append(".")
              .append(getElementMapping().getDatastoreMapping(i).getColumn().getIdentifier());
          stmt.append("=");
          stmt.append(joinedElementAlias)
              .append(".")
              .append(elemIdMapping.getDatastoreMapping(i).getColumn().getIdentifier());
        }
      }

      stmt.append(" WHERE ");
      BackingStoreHelper.appendWhereClauseForMapping(stmt, ownerMapping, containerAlias, true);
      if (orderMapping != null) {
        // If an ordering is present, restrict to items where the index is not null to
        // eliminate records that are added but may not be positioned yet.
        for (int i = 0; i < orderMapping.getNumberOfDatastoreMappings(); i++) {
          stmt.append(" AND ");
          stmt.append(containerAlias)
              .append(".")
              .append(orderMapping.getDatastoreMapping(i).getColumn().getIdentifier().toString());
          stmt.append(">=0");
        }
      }

      if (getElementInfo() != null && getElementInfo().length == 1) {
        // TODO Support more than one element table
        // Add a discriminator filter for collections with an element discriminator
        StringBuilder discrStmt = new StringBuilder();
        for (int i = 0; i < getElementInfo().length; i++) {
          ElementInfo elemInfo = getElementInfo()[i];

          if (elemInfo.getDiscriminatorMapping() != null) {
            usingDiscriminatorInSizeStmt = true;

            JavaTypeMapping discrimMapping = elemInfo.getDiscriminatorMapping();
            // TODO What if we have the discriminator in a supertable? the mapping will be null so
            // we don't get this clause added!
            Class cls = clr.classForName(elemInfo.getClassName());
            if (!Modifier.isAbstract(cls.getModifiers())) {
              for (int j = 0; j < discrimMapping.getNumberOfDatastoreMappings(); j++) {
                if (discrStmt.length() > 0) {
                  discrStmt.append(" OR ");
                }

                if (joinedDiscrim) {
                  discrStmt.append(joinedElementAlias);
                } else {
                  discrStmt.append(containerAlias);
                }
                discrStmt.append(".");
                discrStmt.append(
                    discrimMapping.getDatastoreMapping(j).getColumn().getIdentifier().toString());
                discrStmt.append("=");
                discrStmt.append(
                    ((AbstractDatastoreMapping) discrimMapping.getDatastoreMapping(j))
                        .getUpdateInputParameter());
              }
            }

            Collection<String> subclasses =
                storeMgr.getSubClassesForClass(elemInfo.getClassName(), true, clr);
            if (subclasses != null && subclasses.size() > 0) {
              for (String subclass : subclasses) {
                cls = clr.classForName(subclass);
                if (!Modifier.isAbstract(cls.getModifiers())) {
                  for (int k = 0; k < discrimMapping.getNumberOfDatastoreMappings(); k++) {
                    if (discrStmt.length() > 0) {
                      discrStmt.append(" OR ");
                    }

                    if (joinedDiscrim) {
                      discrStmt.append(joinedElementAlias);
                    } else {
                      discrStmt.append(containerAlias);
                    }
                    discrStmt.append(".");
                    discrStmt.append(
                        discrimMapping
                            .getDatastoreMapping(k)
                            .getColumn()
                            .getIdentifier()
                            .toString());
                    discrStmt.append("=");
                    discrStmt.append(
                        ((AbstractDatastoreMapping) discrimMapping.getDatastoreMapping(k))
                            .getUpdateInputParameter());
                  }
                }
              }
            }
          }
        }

        if (discrStmt.length() > 0) {
          stmt.append(" AND (");
          stmt.append(discrStmt);
          if (allowNulls) {
            stmt.append(" OR ");
            stmt.append(
                getElementInfo()[0]
                    .getDiscriminatorMapping()
                    .getDatastoreMapping(0)
                    .getColumn()
                    .getIdentifier()
                    .toString());
            stmt.append(" IS NULL");
          }
          stmt.append(")");
        }
      }
      if (relationDiscriminatorMapping != null) {
        BackingStoreHelper.appendWhereClauseForMapping(
            stmt, relationDiscriminatorMapping, containerAlias, false);
      }

      sizeStmt = stmt.toString();
      return sizeStmt;
    }
  }