// TODO also substract place number in out
 // TODO also contains (or more comparison)
 // TODO also contains (or more)
 private List<CandidateField> getFromOutByPathAndValue(
     HashMap<String, CandidateField> foundOutFields, CandidateField inQueryField) {
   ArrayList<CandidateField> res = new ArrayList<CandidateField>();
   CandidateField found = foundOutFields.get(inQueryField.getPath());
   if (found != null && inQueryField.getValue().equals(found.getValue())) {
     res.add(found);
   }
   return res;
 }
 // exact only
 // TODO substract depth
 private List<CandidateField> getFromOutByValue(
     HashMap<String, CandidateField> foundOutFields, CandidateField inPathField) {
   ArrayList<CandidateField> res = new ArrayList<CandidateField>();
   for (CandidateField outField : foundOutFields.values()) {
     if (outField != null && inPathField.getValue().equals(outField.getValue())) {
       res.add(outField);
     }
   }
   return res;
 }
  /**
   * XML differs from JSON in : - typing, which is like "byPath", in a perfect way when it's on
   * concrete (business) types, and in a less accurate way when it matches only on extended
   * (including primitive) types (ex. similar to a getBySubpath, or even exactly a getByValue if
   * primitive type). They can be handled in a good way by prefixing by ns. NB. without typing & ns
   * (or empty ns everywhere) it is similar to JSON & "byPath". - attributes, which are typed fields
   * like others, or field-typed fields if no typing. They can be handled in a good way by prefixing
   * by ns and "@".
   *
   * @param xmlInFields
   * @param xmlOutFields
   */
  private void correlateXML(
      HashMap<String, CandidateField> xmlInFields, HashMap<String, CandidateField> xmlOutFields) {
    // looking for correlations :
    int level = 16;
    // if in content, value and path correlations
    ArrayList<Object[]> correlations = new ArrayList<Object[]>();

    for (CandidateField inContentField : xmlInFields.values()) {
      List<CandidateField> foundByTypeAndValueFields =
          getFromOutByTypeAndValue(xmlOutFields, inContentField);
      if (foundByTypeAndValueFields.size() != 0) {
        for (CandidateField field : foundByTypeAndValueFields) {
          // TODO also contains (or more comparison)
          correlations.add(new Object[] {level, inContentField, field, "byTypeAndValue"});
        }
        level = level / 4;
      }
      List<CandidateField> foundByPathAndValueFields =
          getFromOutByPathAndValue(xmlOutFields, inContentField);
      if (foundByPathAndValueFields.size() != 0) {
        for (CandidateField field : foundByPathAndValueFields) {
          // TODO also contains (or more comparison)
          correlations.add(
              new Object[] {
                level, inContentField, field, "byPathAndValue"
              }); // not really interesting since already found by type ; else containing path ??
        }
        level = level / 4;
      }
      List<CandidateField> foundByNameAndValueFields =
          getFromOutByNameAndValue(xmlOutFields, inContentField);
      foundByNameAndValueFields.removeAll(
          foundByPathAndValueFields); // removing fields already found by path
      if (foundByNameAndValueFields.size() != 0) {
        for (CandidateField field : foundByNameAndValueFields) {
          // TODO also contains (or more comparison)
          // substract depth to level
          correlations.add(
              new Object[] {
                level - field.getPath().split("/").length * 2,
                inContentField,
                field,
                "byNameAndValue"
              });
        }
        // level = level / 2;
      }
    }

    System.out.println("Found correlations :");
    for (Object[] correlation : correlations) {
      System.out.println(
          correlation[0] + "\t" + correlation[1] + "\t" + correlation[2] + "\t" + correlation[3]);
    }
  }
 // TODO also substract place number in out
 // TODO also contains (or more comparison)
 // TODO also contains (or more)
 private List<CandidateField> getFromOutByTypeAndValue(
     HashMap<String, CandidateField> xmlOutFields, CandidateField xmlInField) {
   ArrayList<CandidateField> res = new ArrayList<CandidateField>();
   for (CandidateField outField : xmlOutFields.values()) {
     if (outField != null
         && xmlInField.getType().equals(outField.getType())
         && xmlInField.getValue().equals(outField.getValue())) {
       res.add(outField);
     }
   }
   return res;
 }
  /**
   * First correlation algorithm : tries to find correlation - if in content, value and path
   * correlations - else (otherwise lowered) if in query, value and (path or name) correlations -
   * else (otherwise lowered) for all path elements, value correlations
   *
   * @param jsonExchange
   * @param inPathFields
   * @param inQueryFields
   * @param inContentFields
   * @param foundOutFields
   */
  private void correlate(
      ExchangeRecord jsonExchange,
      HashMap<String, CandidateField> inPathFields,
      HashMap<String, CandidateField> inQueryFields,
      HashMap<String, CandidateField> inContentFields,
      HashMap<String, CandidateField> foundOutFields) {
    // looking for correlations :
    int level = 16;
    // if in content, value and path correlations
    // ArrayList<ReqResFieldCorrelation> correlations = new ArrayList<ReqResFieldCorrelation>();
    HashMap<Integer, CorrelationLevel> correlationLevels = new HashMap<Integer, CorrelationLevel>();

    if (jsonExchange.getInMessage().getPostData() != null) {
      addCorrelationsFromOutByPathOrNameAndValue(
          correlationLevels, level, foundOutFields, inContentFields);
      level = level / 4;
    }

    // else (otherwise lowered) if in query, value and path correlations
    if (jsonExchange.getInMessage().getQueryString() != null) {
      addCorrelationsFromOutByPathOrNameAndValue(
          correlationLevels, level, foundOutFields, inQueryFields);
      level = level / 4;
    }

    // else (otherwise lowered) for all path elements, value correlations
    for (CandidateField inPathField : inPathFields.values()) {
      List<CandidateField> foundByValueFields = getFromOutByValue(foundOutFields, inPathField);
      for (CandidateField field : foundByValueFields) {
        // exact only
        // correlations.add(new ReqResFieldCorrelation(level - field.getPath().split("/").length*2,
        // inPathField, field, "byValue"));
        // correlationLevels.putCorrelation(new ReqResFieldCorrelation(level -
        // field.getPath().split("/").length*2, inPathField, field, "byValue"));
        putCorrelation(
            correlationLevels,
            new ReqResFieldCorrelation(
                level - field.getPath().split("/").length * 2, inPathField, field, "byValue"));
      }
    }

    // printCorrelations(correlations);
    int inFieldNb = inPathFields.size() + inQueryFields.size() + inContentFields.size();
    int outFieldNb = foundOutFields.size();
    printCorrelations(correlationLevels, inFieldNb, outFieldNb);
  }
  /**
   * Second correlation algorithm : tries to find correlation - if in content, value and (sub)path
   * correlations - else (otherwise lowered) if in query, value and (sub)path correlations - else
   * (otherwise lowered) for all path elements, value correlations
   *
   * @param jsonExchange
   * @param inPathFields
   * @param inQueryFields
   * @param inContentFields
   * @param foundOutFields
   */
  private void correlateWithSubpath(
      ExchangeRecord jsonExchange,
      HashMap<String, CandidateField> inPathFields,
      HashMap<String, CandidateField> inQueryFields,
      HashMap<String, CandidateField> inContentFields,
      HashMap<String, CandidateField> foundOutFields) {
    // looking for correlations :
    int level = 16;
    // if in content, value and path correlations
    ArrayList<Object[]> correlations = new ArrayList<Object[]>();

    if (jsonExchange.getInMessage().getPostData() != null) {
      addCorrelationsFromOutBySubpathAndValue(correlations, level, foundOutFields, inContentFields);
      level = level / 4;
    }

    // else (otherwise lowered) if in query, value and path correlations
    if (jsonExchange.getInMessage().getQueryString() != null) {
      addCorrelationsFromOutBySubpathAndValue(correlations, level, foundOutFields, inQueryFields);
      level = level / 4;
    }

    // else (otherwise lowered) for all path elements, value correlations
    for (CandidateField inPathField : inPathFields.values()) {
      List<CandidateField> foundByValueFields = getFromOutByValue(foundOutFields, inPathField);
      for (CandidateField field : foundByValueFields) {
        // exact only
        correlations.add(
            new Object[] {
              level - field.getPath().split("/").length * 2, inPathField, field, "byValue"
            });
      }
    }

    System.out.println("Found correlations with subpath :");
    for (Object[] correlation : correlations) {
      System.out.println(
          correlation[0] + "\t" + correlation[1] + "\t" + correlation[2] + "\t" + correlation[3]);
    }
    System.out.println();
  }
 private void addCorrelationsFromOutByPathOrNameAndValue(
     HashMap<Integer, CorrelationLevel> correlationLevels,
     int level,
     HashMap<String, CandidateField> foundOutFields,
     HashMap<String, CandidateField> inFields) {
   for (CandidateField inField : inFields.values()) {
     int queryLevel = level;
     List<CandidateField> foundByPathAndValueFields =
         getFromOutByPathAndValue(foundOutFields, inField);
     if (foundByPathAndValueFields.size() != 0) {
       for (CandidateField field : foundByPathAndValueFields) {
         // TODO also contains (or more comparison)
         ReqResFieldCorrelation reqResFieldCorrelation =
             new ReqResFieldCorrelation(queryLevel, inField, field, "byPathAndValue");
         putCorrelation(correlationLevels, reqResFieldCorrelation);
       }
       queryLevel = queryLevel / 4;
     }
     List<CandidateField> foundByNameAndValueFields =
         getFromOutByNameAndValue(foundOutFields, inField);
     foundByNameAndValueFields.removeAll(
         foundByPathAndValueFields); // removing fields already found by path
     if (foundByNameAndValueFields.size() != 0) {
       for (CandidateField field : foundByNameAndValueFields) {
         // TODO also contains (or more comparison)
         // substract depth to level
         ReqResFieldCorrelation reqResFieldCorrelation =
             new ReqResFieldCorrelation(
                 queryLevel - field.getPath().split("/").length * 2,
                 inField,
                 field,
                 "byNameAndValue");
         putCorrelation(correlationLevels, reqResFieldCorrelation);
       }
       // queryLevel = queryLevel / 2;
     }
   }
 }
 /**
  * @param obj
  * @param foundFields
  * @param pathStack
  */
 private void putCandidateFields(
     Object obj, HashMap<String, CandidateField> foundFields, Stack<Object> pathStack) {
   CandidateField candidateField = new CandidateField(toPath(pathStack), String.valueOf(obj));
   foundFields.put(candidateField.getPath(), candidateField);
 }
  /**
   * TODO also contains (or more comparison) TODO merge fullpath with subpath, once
   *
   * @param correlations
   * @param level
   * @param foundOutFields
   * @param inField
   * @return
   */
  private int addCorrelationsFromOutBySubpathAndValue(
      ArrayList<Object[]> correlations,
      int level,
      HashMap<String, CandidateField> foundOutFields,
      CandidateField inField) {
    // ArrayList<CandidateField> res = new ArrayList<CandidateField>();

    // full path :
    CandidateField fullPathOutField = foundOutFields.get(inField.getPath());
    if (fullPathOutField != null) {
      if (inField.getValue().equals(fullPathOutField.getValue())) {
        // res.add(found);
        // TODO also more comparison
        correlations.add(new Object[] {level, inField, fullPathOutField, "byPathAndValue"});

        // either hit hard on level or even stop there
        level = level - 6; // 4
        // return level;

      } else {
        // path only, trying out ?!?
        correlations.add(new Object[] {level / 4, inField, fullPathOutField, "byPath"}); // ?!?
      }
    }

    int matchingInFieldSubPathDepth = inField.getPath().split("/").length;

    // full path partially :
    boolean foundAtLeastOne = false;
    for (CandidateField outField : foundOutFields.values()) {
      if (outField.getPath().endsWith('/' + inField.getPath())
          && inField.getValue().equals(outField.getValue())) {
        // res.add(outField);
        // TODO also more comparison
        // outDepth is bad (at least 1), more than 1 matchingInFieldSubPathDepth is good
        int outDepth = outField.getPath().split("/").length - matchingInFieldSubPathDepth;
        correlations.add(
            new Object[] {
              level - outDepth * 2 + 2 * (matchingInFieldSubPathDepth - 1),
              inField,
              outField,
              "byPartialPathAndValue"
            });
        foundAtLeastOne = true;
      }
    }
    if (foundAtLeastOne) {
      // either hit hard on level or even stop there
      level = level - 6; // 4
      // return level;
    }

    int inDepth = 1; // = nonMatchingInFieldSubpathDepth
    int indexOfSlashPlusOne = 0;
    while ((indexOfSlashPlusOne = inField.getPath().indexOf('/', indexOfSlashPlusOne) + 1) != 0) {
      String subpath = inField.getPath().substring(indexOfSlashPlusOne);
      matchingInFieldSubPathDepth--;
      inDepth++;
      boolean foundAtLeastOneInSubpath = false;

      for (CandidateField outField : foundOutFields.values()) {
        if (outField.getPath().equals(subpath)
            || outField.getPath().endsWith('/' + subpath)
                && inField.getValue().equals(outField.getValue())) {
          // res.add(outField);
          // TODO also more comparison
          // outDepth is bad, inDepth worse (but already >= 1), more than 1
          // matchingInFieldSubPathDepth is good
          int outDepth = outField.getPath().split("/").length - matchingInFieldSubPathDepth;
          correlations.add(
              new Object[] {
                level - outDepth * 2 - inDepth * 2 + 2 * (matchingInFieldSubPathDepth - 1),
                inField,
                outField,
                "bySubpathAndValue:" + subpath
              });
          foundAtLeastOneInSubpath = true;
        }
      }
      if (foundAtLeastOneInSubpath) {
        // either hit hard on level or even stop there
        level = level - 6; // 4
        // return level;
      }
    }
    return level;
  }
 private void putField(HashMap<String, CandidateField> fields, CandidateField candidateField) {
   fields.put(candidateField.getPath(), candidateField);
 }