/**
   * Tests the group (or message) against a list of profile descriptions that are either a {@link
   * org.openehealth.ipf.gazelle.validation.core.stub.SegmentType} or a {@link
   * org.openehealth.ipf.gazelle.validation.core.stub.HL7V2XStaticDef.SegGroup}.
   *
   * @param group current message/group element
   * @param profile available profile objcts to test the element against
   * @return a list with identified violations against the profile(s)
   */
  protected List<ValidationException> testGroup(Group group, List<Object> profile) {
    List<ValidationException> exList = new ArrayList<>();
    List<String> allowedStructures = new ArrayList<>();

    for (Object struct : profile) {
      UsageInfo usage = new UsageInfo(struct);

      if (!usage.disallowed()) {
        allowedStructures.add(usage.name);

        try {
          List<Structure> nonEmptyStructures = nonEmptyStructure(group.getAll(usage.name));
          exList.addAll(testCardinality(nonEmptyStructures.size(), usage));

          // test children on instances with content
          if (validateChildren) {
            for (Structure structure : nonEmptyStructures) {
              exList.addAll(testStructure(structure, struct));
            }
          }
        } catch (HL7Exception he) {
          profileNotHL7Compliant(exList, PROFILE_STRUCTURE_NOT_EXIST_IN_JAVA_CLASS, usage.name);
        }
      }
    }

    // complain about X structures that have content
    exList.addAll(checkForExtraStructures(group, allowedStructures));
    return exList;
  }
  protected List<ValidationException> testField(
      Type type, SegmentType.Field profile, boolean escape) {
    List<ValidationException> exList = new ArrayList<>();

    UsageInfo usage = new UsageInfo(profile);

    // account for MSH 1 & 2 which aren't escaped
    String encoded = null;
    if (!escape && Primitive.class.isAssignableFrom(type.getClass()))
      encoded = ((Primitive) type).getValue();

    exList.addAll(testType(type, profile.getDatatype(), usage, encoded, false));

    // test children
    if (validateChildren) {
      if (profile.getComponents().size() > 0 && !usage.disallowed()) {
        if (Composite.class.isAssignableFrom(type.getClass())) {
          Composite comp = (Composite) type;
          int i = 1;
          boolean nullContext = false;
          for (SegmentType.Field.Component component : profile.getComponents()) {
            try {
              SegmentType.Field.Component component2;
              if (nullContext) {
                component2 = new SegmentType.Field.Component();
                try {
                  BeanUtils.copyProperties(component2, component);
                } catch (InvocationTargetException | IllegalAccessException e) {
                  // nop
                }
                component2.setUsage("NULL");
              } else {
                component2 = component;
                if ((i == 1)
                    && profile.isNullable()
                    && PipeParser.encode(comp.getComponent(0), this.enc).equals("\"\"")) {
                  nullContext = true;
                }
              }

              exList.addAll(testComponent(comp.getComponent(i - 1), component2));
            } catch (DataTypeException de) {
              profileNotHL7Compliant(exList, COMPONENT_TYPE_MISMATCH, type.getName(), i);
            }
            ++i;
          }
          exList.addAll(checkUndefinedComponents(comp, profile.getComponents().size()));
        } else {
          profileNotHL7Compliant(exList, WRONG_FIELD_TYPE, type.getClass().getName());
        }
      }
    }
    return exList;
  }
  protected List<ValidationException> testSegment(Segment segment, SegmentType profile) {
    List<ValidationException> exList = new ArrayList<>();
    List<Integer> allowedFields = new ArrayList<>();
    int i = 1;
    for (SegmentType.Field field : profile.getFields()) {
      UsageInfo usage = new UsageInfo(field);
      // only test a field in detail if it isn't X
      if (!usage.disallowed()) {
        allowedFields.add(i);

        // see which instances have content
        try {
          Collection<Type> nonEmptyFields = nonEmptyField(segment.getField(i));
          exList.addAll(testCardinality(nonEmptyFields.size(), usage));

          // test field instances with content
          if (validateChildren) {
            for (Type type : nonEmptyFields) {
              boolean escape = true; // escape field value when checking length
              if (profile.getName().equalsIgnoreCase("MSH") && i < 3) {
                escape = false;
              }
              List<ValidationException> childExceptions = testField(type, field, escape);
              for (ValidationException ex : childExceptions) {
                ex.setFieldPosition(i);
              }
              exList.addAll(childExceptions);
            }
          }
        } catch (HL7Exception he) {
          profileNotHL7Compliant(exList, FIELD_NOT_FOUND, i);
        }
      }
      ++i;
    }
    // complain about X fields with content
    exList.addAll(checkForExtraFields(segment, allowedFields));

    for (ValidationException ex : exList) {
      ex.setSegmentName(profile.getName());
    }
    return exList;
  }
 /**
  * Checks cardinality and creates an appropriate exception if out of bounds. The usage code is
  * needed because if min cardinality is > 0, the min # of reps is only required if the usage code
  * is 'R' (see HL7 v2.5 section 2.12.6.4).
  *
  * @param reps the number of reps
  * @param usage usage info
  * @return exceptions
  */
 protected List<ValidationException> testCardinality(int reps, UsageInfo usage) {
   List<ValidationException> violations = new ArrayList<>();
   profileViolatedWhen(
       reps < usage.min && usage.required(),
       violations,
       LESS_THAN_MINIMUM_CARDINALITY,
       usage.name,
       usage.min,
       reps);
   profileViolatedWhen(
       usage.max > 0 && reps > usage.max,
       violations,
       MORE_THAN_MAXIMUM_CARDINALITY,
       usage.name,
       usage.max,
       reps);
   profileViolatedWhen(
       reps > 0 && usage.disallowed(), violations, NOT_SUPPORTED_ELEMENT_PRESENT, usage.name);
   return violations;
 }
 /**
  * Tests an element against the corresponding usage code. The element is required in its encoded
  * form.
  *
  * @param encoded the pipe-encoded message element
  * @param usage the usage code (e.g. "CE")
  */
 protected void testUsage(List<ValidationException> exList, String encoded, UsageInfo usage) {
   if (usage.required()) {
     profileViolatedWhen(encoded.isEmpty(), exList, REQUIRED_ELEMENT_MISSING, usage.name);
   } else if (usage.disallowed()) {
     profileViolatedWhen(!encoded.isEmpty(), exList, NOT_SUPPORTED_ELEMENT_PRESENT, usage.name);
   } else if (usage.nullContext()) {
     profileViolatedWhen(!encoded.isEmpty(), exList, NO_ELEMENTS_AFTER_NULL, usage.name);
   }
   /*
   else if (usage.equalsIgnoreCase("RE")) {
       // can't test anything
   } else if (usage.equalsIgnoreCase("O")) {
       // can't test anything
   } else if (usage.equalsIgnoreCase("C")) {
       // can't test anything yet -- wait for condition syntax in v2.6
   } else if (usage.equalsIgnoreCase("CE")) {
       // can't test anything
   } else if (usage.equalsIgnoreCase("B")) {
       // can't test anything
   }
   */
 }
  protected List<ValidationException> testComponent(
      Type type, SegmentType.Field.Component profile) {
    List<ValidationException> exList = new ArrayList<>();
    UsageInfo usage = new UsageInfo(profile);
    exList.addAll(testType(type, profile.getDatatype(), usage, null));

    // test children
    try {
      if (profile.getSubComponents().size() > 0 && !usage.disallowed() && !isEmpty(type)) {
        if (Composite.class.isAssignableFrom(type.getClass())) {
          Composite comp = (Composite) type;

          if (validateChildren) {
            int i = 1;
            for (SegmentType.Field.Component.SubComponent subComponent :
                profile.getSubComponents()) {
              UsageInfo scUsage = new UsageInfo(subComponent);
              try {
                Type sub = comp.getComponent(i - 1);
                exList.addAll(testType(sub, subComponent.getDatatype(), scUsage, null));
              } catch (DataTypeException de) {
                profileNotHL7Compliant(exList, SUBCOMPONENT_TYPE_MISMATCH, type.getName(), i);
              }
              ++i;
            }
          }

          exList.addAll(checkUndefinedComponents(comp, profile.getSubComponents().size()));
        } else {
          profileViolatedWhen(true, exList, WRONG_COMPONENT_TYPE, type.getClass().getName());
        }
      }
    } catch (HL7Exception e) {
      exList.add(new ValidationException(e));
    }

    return exList;
  }
  /**
   * Tests a Type against the corresponding section of a profile.
   *
   * @param encoded optional encoded form of type (if you want to specify this -- if null, default
   *     pipe-encoded form is used to check length and constant val)
   */
  protected List<ValidationException> testType(
      Type type, String dataType, UsageInfo usage, String encoded, boolean testUsage) {
    ArrayList<ValidationException> exList = new ArrayList<>();
    if (encoded == null) encoded = PipeParser.encode(type, this.enc);

    if (testUsage) {
      testUsage(exList, encoded, usage);
    }

    if (!usage.disallowed() && !encoded.isEmpty()) {
      // check datatype
      if ((type instanceof ca.uhn.hl7v2.model.v231.datatype.TSComponentOne
              || type instanceof ca.uhn.hl7v2.model.v24.datatype.TSComponentOne)
          && !dataType.equals("ST")) {
        profileNotHL7Compliant(exList, HL7_DATATYPE_MISMATCH, type.getName(), dataType);
      } else if (!(type instanceof TSComponentOne) && !type.getName().contains(dataType)) {
        profileViolatedWhen(
            !(type.getClass().getSimpleName().equals("Varies")
                || type.getClass().getSimpleName().equals("QIP")),
            exList,
            HL7_DATATYPE_MISMATCH,
            type.getName(),
            dataType);
      }

      // check length
      profileViolatedWhen(
          encoded.length() > usage.length,
          exList,
          LENGTH_EXCEEDED,
          usage.name,
          encoded.length(),
          usage.length);

      // check constant value
      if (usage.constantValue != null && usage.constantValue.length() > 0) {
        profileViolatedWhen(
            !encoded.equals(usage.constantValue),
            exList,
            WRONG_CONSTANT_VALUE,
            encoded,
            usage.constantValue);
      }

      // TODO : check against table, or do we need this check?
      // Gazelle checks code system and issues a WARNING if a check fails
    }

    return exList;
  }