/** Parse the given string, return resulting data if appropriate. */
  ParseResult internalParse(
      String s, Map<String, Integer> targetUnionDecisions, boolean mustConsumeStr) {
    //
    // If there's no target decision, then go ahead and try all branches.
    //
    if (targetUnionDecisions == null || targetUnionDecisions.get(name) == null) {
      for (InferredType subelt : unionTypes) {
        ParseResult pr = subelt.internalParse(s, targetUnionDecisions, false);
        if (pr != null
            && (!mustConsumeStr
                || (mustConsumeStr && pr.getRemainingString().trim().length() == 0))) {
          return new ParseResult(pr.getData(), pr.hasData(), pr.getRemainingString());
        }
      }
      return null;
    }

    //
    // If there is a target decision, then carry it out.
    //
    InferredType subelt = unionTypes.get(targetUnionDecisions.get(name));
    ParseResult pr = subelt.internalParse(s, targetUnionDecisions, false);
    if (pr != null
        && (!mustConsumeStr || (mustConsumeStr && pr.getRemainingString().trim().length() == 0))) {
      return new ParseResult(pr.getData(), pr.hasData(), pr.getRemainingString());
    }
    return null;
  }
  /** Parse the given string, return resulting data if appropriate. */
  ParseResult internalParse(
      String s, Map<String, Integer> targetUnionDecisions, boolean mustConsumeStr) {
    boolean hasData = false;
    GenericData.Record gdr = new GenericData.Record(getAvroSchema());
    String currentStr = s;

    for (InferredType subelt : structTypes) {
      if (currentStr.length() == 0) {
        return null;
      }
      ParseResult pr = subelt.internalParse(currentStr, targetUnionDecisions, false);
      if (pr == null) {
        return null;
      }
      if (pr.hasData()) {
        hasData = true;
        gdr.put(subelt.getName(), pr.getData());
      }
      currentStr = pr.getRemainingString();
    }
    if (mustConsumeStr && currentStr.trim().length() != 0) {
      return null;
    }
    return new ParseResult(gdr, hasData, currentStr);
  }
  public GenericContainer parse(String str) {
    //
    // Try the naive parse
    //
    ParseResult pr = internalParse(str, null, true);
    if (pr != null && pr.hasData()) {
      return (GenericContainer) pr.getData();
    }

    //
    // Otherwise, we need to consider other union-options.
    // Unfold the candidate decisions into a series of target decisions
    //
    Map<String, Set<Integer>> candidateUnionDecisions = findCandidateUnionDecisions();

    List<HashMap<String, Integer>> allUnionDecisions = new ArrayList<HashMap<String, Integer>>();
    for (Map.Entry<String, Set<Integer>> pair : candidateUnionDecisions.entrySet()) {
      String k = pair.getKey();
      Set<Integer> indices = pair.getValue();

      if (allUnionDecisions.size() == 0) {
        for (Integer index : indices) {
          HashMap<String, Integer> newMap = new HashMap<String, Integer>();
          newMap.put(k, index);
          allUnionDecisions.add(newMap);
        }
      } else {
        List<HashMap<String, Integer>> newUnionDecisions =
            new ArrayList<HashMap<String, Integer>>();
        for (HashMap<String, Integer> curUnionDecisions : allUnionDecisions) {
          for (Integer index : indices) {
            HashMap<String, Integer> newMap = (HashMap<String, Integer>) curUnionDecisions.clone();
            newMap.put(k, index);
            newUnionDecisions.add(newMap);
          }
        }
        allUnionDecisions = newUnionDecisions;
      }
    }

    //
    // Now execute all possible union decisions
    //
    for (Map<String, Integer> targetUnionDecisions : allUnionDecisions) {
      pr = internalParse(str, targetUnionDecisions, true);
      if (pr != null && pr.hasData()) {
        return (GenericContainer) pr.getData();
      }
    }
    return null;
  }
  /** Parse the given string, return resulting data if appropriate. */
  ParseResult internalParse(
      String s, Map<String, Integer> targetUnionDecisions, boolean mustConsumeStr) {
    boolean hasData = false;
    Schema localSchema = getAvroSchema();
    GenericData.Array gda = new GenericData.Array(5, localSchema);
    Map<String, Integer> curUnionDecisions = new HashMap<String, Integer>();
    String currentStr = s;

    while (true) {
      ParseResult pr = bodyType.internalParse(currentStr, targetUnionDecisions, false);
      if (pr == null) {
        break;
      }
      assert (pr.hasData());

      gda.add(pr.getData());
      currentStr = pr.getRemainingString();
    }
    if (mustConsumeStr && currentStr.trim().length() != 0) {
      return null;
    }
    return new ParseResult(gda, true, currentStr);
  }