Check.java [src/csip] Revision:   Date:
/*
 * $Id: 2.7+14 Check.java 773d4dd2e419 2022-10-27 od $
 *
 * This file is part of the Cloud Services Integration Platform (CSIP),
 * a Model-as-a-Service framework, API and application suite.
 *
 * 2012-2022, Olaf David and others, OMSLab, Colorado State University.
 *
 * OMSLab licenses this file to you under the MIT license.
 * See the LICENSE file in the project root for more information.
 */
package csip;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;

/**
 * Check for data correctness.
 *
 * @author od
 */
public class Check {

  static final double conus_top = 49.3457868; // north lat
  static final double conus_left = -124.7844079; // west long
  static final double conus_right = -66.9513812; // east long
  static final double conus_bottom = 24.7433195; // south lat

  // generic Object, can be Number, String or date
  List<Function<Object, String>> checks = new ArrayList<>();

  public Number forNumber(Number n) throws IllegalArgumentException {
    return forNumber(n, "");
  }

  public Number forNumber(Number n, String prefix) throws IllegalArgumentException {
    return (Number) forObject(n, prefix);
  }

  public LocalDate forLocalDate(LocalDate n) throws IllegalArgumentException {
    return forLocalDate(n, "");
  }

  public LocalDate forLocalDate(LocalDate n, String prefix) throws IllegalArgumentException {
    return (LocalDate) forObject(n, prefix);
  }

  public String forString(String n) throws IllegalArgumentException {
    return forString(n, "");
  }

  public String forString(String n, String prefix) throws IllegalArgumentException {
    return (String) forObject(n, prefix);
  }

  Object forObject(Object n, String prefix) throws IllegalArgumentException {
    if (n == null)
      throw new NullPointerException(prefix + " for argument.");
    for (Function<Object, String> check : checks) {
      String err = check.apply(n);
      if (err != null)
        throw new IllegalArgumentException(prefix + err);
    }
    return n;
  }

  public Check eq(double val) {
    checks.add(n -> {
      if (Double.compare(toDouble(n), val) == 0)
        return null;
      return n + " not equal to " + val;
    });
    return this;
  }

  public Check lt(double val) {
    checks.add(n -> {
      if (toDouble(n) < val)
        return null;
      return n + " not lower than " + val;
    });
    return this;
  }

  public Check gt(double val) {
    checks.add(n -> {
      if (toDouble(n) > val)
        return null;
      return n + " not greater than " + val;
    });
    return this;
  }

  public Check positive() {
    checks.add(n -> {
      if (!(toDouble(n) > 0.0))
        return " not positive: " + n;
      return null;
    });
    return this;
  }

  public Check negative() {
    checks.add(n -> {
      if (!(toDouble(n) < 0.0))
        return " not negative: " + n;
      return null;
    });
    return this;
  }

  public Check notNaN() {
    checks.add(n -> {
      if (Double.isNaN(toDouble(n)))
        return " is NaN";
      return null;
    });
    return this;
  }

  public Check isConusLat() {
    checks.add(n -> {
      double d = toDouble(n);
      if (d < conus_top && d > conus_bottom)
        return null;
      return "Not a CONUS lat: " + d;
    });
    return this;
  }

  public Check isConusLon() {
    checks.add(n -> {
      double d = toDouble(n);
      if (d > conus_left && d < conus_right)
        return null;
      return "Not a CONUS lat: " + d;
    });
    return this;
  }

  public Check isLatitude() {
    checks.add(n -> {
      double d = toDouble(n);
      if (d > -90d && d < 90d)
        return null;
      return "Not a latitude: " + d;
    });
    return this;
  }

  public Check isLongitude() {
    checks.add(n -> {
      double d = toDouble(n);
      if (d > -180d && d < 180d)
        return null;
      return "Not a longitude: " + d;
    });
    return this;
  }

  public Check isCMZ() {
    checks.add(n -> {
      String cmz = n.toString().trim();
      if (cmz.indexOf(' ') > 0) {
        if (cmz.equals("72 AK") || cmz.equals("73 HI")
            || cmz.equals("74 PB") || cmz.equals("75 PR"))
          return null;
      }
      try {
        float val = Float.parseFloat(cmz);
        if (val == 4.1f || val == 15.1f || val == 37.1f || val == 38.1f)
          return null;
        if (val >= 1 && val <= 71 && (val == (int) val))
          return null;
        return "Invalid CMZ: " + cmz;
      } catch (NumberFormatException E) {
        return "Invalid CMZ: " + cmz;
      }
    });
    return this;
  }

  public Check isEcoclassId() {
    checks.add(n -> {
      String ecId = n.toString();
      if (ecId.length() == 11 && (ecId.charAt(0) == 'R' || ecId.charAt(0) == 'F'))
        return null;
      return "Invalid Ecoclass ID: " + ecId;
    });
    return this;
  }

  public Check isISODateString() {
    checks.add(n -> {
      String date = n.toString();
      try {
        LocalDate.parse(date, DateTimeFormatter.ISO_DATE);
        return null;
      } catch (DateTimeParseException E) {
        try {
          LocalDate.parse(date, DateTimeFormatter.ISO_LOCAL_DATE);
          return null;
        } catch (DateTimeParseException E1) {
          return "Invalid Date: " + date;
        }
      }
    });
    return this;
  }

  public Check inRange(double min, double max) {
    checks.add(n -> {
      double d = toDouble(n);
      if (d < min || d > max)
        return d + " not in range " + min + "..." + max;
      return null;
    });
    return this;
  }

  public Check inRange(int min, int max) {
    checks.add(n -> {
      int d = toInt(n);
      if (d < min || d > max)
        return d + " not in range " + min + "..." + max;
      return null;
    });
    return this;
  }

  public Check inBetween(LocalDate start, LocalDate end) {
    checks.add(n -> {
      LocalDate d = (LocalDate) n;
      if (!(d.isAfter(start) && d.isBefore(end)))
        return d + " not in date range " + start + "..." + end;
      return null;
    });
    return this;
  }

  public Check inSet(int... vals) {
    checks.add(i -> {
      int ii = toInt(i);
      for (int val : vals) {
        if (val == ii)
          return null;
      }
      return i + " not in: " + Arrays.toString(vals);
    });
    return this;
  }

  public Check inSet(Object... vals) {
    checks.add(s -> {
      for (Object val : vals) {
        if (val.equals(s))
          return null;
      }
      return s + " not found in: " + Arrays.toString(vals);
    });
    return this;
  }

  public Check inSetIngnoreCase(String... vals) {
    checks.add(s -> {
      for (String val : vals) {
        if (val.equalsIgnoreCase(s.toString()))
          return null;
      }
      return s + " not found in: " + Arrays.toString(vals);
    });
    return this;
  }

  public Check notInSet(Object... vals) {
    checks.add(s -> {
      for (Object val : vals) {
        if (val.equals(s))
          return s + " in: " + Arrays.toString(vals);
      }
      return null;
    });
    return this;
  }

  private double toDouble(Object o) {
    if (o instanceof Number)
      return ((Number) o).doubleValue();
    if (o instanceof String) {
      return Double.parseDouble((String) o);
    }
    throw new IllegalArgumentException(o + " cannot be converted into double.");
  }

  private int toInt(Object o) {
    if (o instanceof Number)
      return ((Number) o).intValue();
    if (o instanceof String) {
      return Integer.parseInt((String) o);
    }
    throw new IllegalArgumentException(o + " cannot be converted into int.");
  }

}