class FeatureTableValidator {

  public static void validateSpec(FeatureTableSpec spec) {
    if (spec.getName().isEmpty()) {
      throw new IllegalArgumentException("FeatureTable name must be provided");
    }
    if (spec.getLabelsMap().containsKey("")) {
      throw new IllegalArgumentException("FeatureTable cannot have labels with empty key.");
    }
    if (spec.getEntitiesCount() == 0) {
      throw new IllegalArgumentException("FeatureTable entities list cannot be empty.");
    }
    if (spec.getFeaturesCount() == 0) {
      throw new IllegalArgumentException("FeatureTable features list cannot be empty.");
    }
    if (!spec.hasBatchSource()) {
      throw new IllegalArgumentException("FeatureTable batch source cannot be empty.");
    }

    checkValidCharacters(spec.getName(), "FeatureTable");
    spec.getFeaturesList().forEach(FeatureTableValidator::validateFeatureSpec);

    // Check that BigQuery reference defined for BigQuery source is valid
    if (!spec.getBatchSource().getBigqueryOptions().getTableRef().isEmpty()) {
      checkValidBigQueryTableRef(
          spec.getBatchSource().getBigqueryOptions().getTableRef(), "FeatureTable");
    }

    // Check that features and entities defined in FeatureTable do not use reserved names
    ArrayList<String> fieldNames = new ArrayList<>(spec.getEntitiesList());
    fieldNames.addAll(
        spec.getFeaturesList().stream().map(FeatureSpecV2::getName).collect(Collectors.toList()));
    if (!Collections.disjoint(fieldNames, RESERVED_NAMES)) {
      throw new IllegalArgumentException(
          String.format(
              "Reserved names has been used as Feature(s) names. Reserved: %s", RESERVED_NAMES));
    }

    // Check that Feature and Entity names in FeatureTable do not collide with each other
    if (hasDuplicates(fieldNames)) {
      throw new IllegalArgumentException(
          String.format("Entity and Feature names within a Feature Table should be unique."));
    }
  }

}