/**
  * Parse a string GapPolicy into the byte enum
  *
  * @param context SearchContext this is taking place in
  * @param text GapPolicy in string format (e.g. "ignore")
  * @return GapPolicy enum
  */
 public static GapPolicy parse(
     QueryParseContext context, String text, XContentLocation tokenLocation) {
   GapPolicy result = null;
   for (GapPolicy policy : values()) {
     if (context.getParseFieldMatcher().match(text, policy.parseField)) {
       if (result == null) {
         result = policy;
       } else {
         throw new IllegalStateException(
             "Text can be parsed to 2 different gap policies: text=["
                 + text
                 + "], "
                 + "policies="
                 + Arrays.asList(result, policy));
       }
     }
   }
   if (result == null) {
     final List<String> validNames = new ArrayList<>();
     for (GapPolicy policy : values()) {
       validNames.add(policy.getName());
     }
     throw new ParsingException(
         tokenLocation, "Invalid gap policy: [" + text + "], accepted values: " + validNames);
   }
   return result;
 }
 public static IncludeExclude parseInclude(XContentParser parser, QueryParseContext context)
     throws IOException {
   XContentParser.Token token = parser.currentToken();
   if (token == XContentParser.Token.VALUE_STRING) {
     return new IncludeExclude(parser.text(), null);
   } else if (token == XContentParser.Token.START_ARRAY) {
     return new IncludeExclude(new TreeSet<>(parseArrayToSet(parser)), null);
   } else if (token == XContentParser.Token.START_OBJECT) {
     ParseFieldMatcher parseFieldMatcher = context.getParseFieldMatcher();
     String currentFieldName = null;
     Integer partition = null, numPartitions = null;
     while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
       if (token == XContentParser.Token.FIELD_NAME) {
         currentFieldName = parser.currentName();
       } else
       // This "include":{"pattern":"foo.*"} syntax is undocumented since 2.0
       // Regexes should be "include":"foo.*"
       if (parseFieldMatcher.match(currentFieldName, PATTERN_FIELD)) {
         return new IncludeExclude(parser.text(), null);
       } else if (parseFieldMatcher.match(currentFieldName, NUM_PARTITIONS_FIELD)) {
         numPartitions = parser.intValue();
       } else if (parseFieldMatcher.match(currentFieldName, PARTITION_FIELD)) {
         partition = parser.intValue();
       } else {
         throw new ElasticsearchParseException(
             "Unknown parameter in Include/Exclude clause: " + currentFieldName);
       }
     }
     if (partition == null) {
       throw new IllegalArgumentException(
           "Missing ["
               + PARTITION_FIELD.getPreferredName()
               + "] parameter for partition-based include");
     }
     if (numPartitions == null) {
       throw new IllegalArgumentException(
           "Missing ["
               + NUM_PARTITIONS_FIELD.getPreferredName()
               + "] parameter for partition-based include");
     }
     return new IncludeExclude(partition, numPartitions);
   } else {
     throw new IllegalArgumentException("Unrecognized token for an include [" + token + "]");
   }
 }
  public static NestedAggregationBuilder parse(String aggregationName, QueryParseContext context)
      throws IOException {
    String path = null;

    XContentParser.Token token;
    String currentFieldName = null;
    XContentParser parser = context.parser();
    while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
      if (token == XContentParser.Token.FIELD_NAME) {
        currentFieldName = parser.currentName();
      } else if (token == XContentParser.Token.VALUE_STRING) {
        if (context.getParseFieldMatcher().match(currentFieldName, NestedAggregator.PATH_FIELD)) {
          path = parser.text();
        } else {
          throw new ParsingException(
              parser.getTokenLocation(),
              "Unknown key for a "
                  + token
                  + " in ["
                  + aggregationName
                  + "]: ["
                  + currentFieldName
                  + "].");
        }
      } else {
        throw new ParsingException(
            parser.getTokenLocation(),
            "Unexpected token " + token + " in [" + aggregationName + "].");
      }
    }

    if (path == null) {
      // "field" doesn't exist, so we fall back to the context of the ancestors
      throw new ParsingException(
          parser.getTokenLocation(),
          "Missing [path] field for nested aggregation [" + aggregationName + "]");
    }

    return new NestedAggregationBuilder(aggregationName, path);
  }
  public static ScriptedMetricAggregationBuilder parse(
      String aggregationName, QueryParseContext context) throws IOException {
    Script initScript = null;
    Script mapScript = null;
    Script combineScript = null;
    Script reduceScript = null;
    Map<String, Object> params = null;
    XContentParser.Token token;
    String currentFieldName = null;
    Set<String> scriptParameters = new HashSet<>();
    scriptParameters.add(INIT_SCRIPT_FIELD.getPreferredName());
    scriptParameters.add(MAP_SCRIPT_FIELD.getPreferredName());
    scriptParameters.add(COMBINE_SCRIPT_FIELD.getPreferredName());
    scriptParameters.add(REDUCE_SCRIPT_FIELD.getPreferredName());

    XContentParser parser = context.parser();
    while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
      if (token == XContentParser.Token.FIELD_NAME) {
        currentFieldName = parser.currentName();
      } else if (token == XContentParser.Token.START_OBJECT
          || token == XContentParser.Token.VALUE_STRING) {
        if (context.getParseFieldMatcher().match(currentFieldName, INIT_SCRIPT_FIELD)) {
          initScript =
              Script.parse(
                  parser, context.getParseFieldMatcher(), context.getDefaultScriptLanguage());
        } else if (context.getParseFieldMatcher().match(currentFieldName, MAP_SCRIPT_FIELD)) {
          mapScript =
              Script.parse(
                  parser, context.getParseFieldMatcher(), context.getDefaultScriptLanguage());
        } else if (context.getParseFieldMatcher().match(currentFieldName, COMBINE_SCRIPT_FIELD)) {
          combineScript =
              Script.parse(
                  parser, context.getParseFieldMatcher(), context.getDefaultScriptLanguage());
        } else if (context.getParseFieldMatcher().match(currentFieldName, REDUCE_SCRIPT_FIELD)) {
          reduceScript =
              Script.parse(
                  parser, context.getParseFieldMatcher(), context.getDefaultScriptLanguage());
        } else if (token == XContentParser.Token.START_OBJECT
            && context.getParseFieldMatcher().match(currentFieldName, PARAMS_FIELD)) {
          params = parser.map();
        } else {
          throw new ParsingException(
              parser.getTokenLocation(),
              "Unknown key for a "
                  + token
                  + " in ["
                  + aggregationName
                  + "]: ["
                  + currentFieldName
                  + "].");
        }
      } else {
        throw new ParsingException(
            parser.getTokenLocation(),
            "Unexpected token " + token + " in [" + aggregationName + "].");
      }
    }

    if (mapScript == null) {
      throw new ParsingException(
          parser.getTokenLocation(), "map_script field is required in [" + aggregationName + "].");
    }

    ScriptedMetricAggregationBuilder factory =
        new ScriptedMetricAggregationBuilder(aggregationName);
    if (initScript != null) {
      factory.initScript(initScript);
    }
    if (mapScript != null) {
      factory.mapScript(mapScript);
    }
    if (combineScript != null) {
      factory.combineScript(combineScript);
    }
    if (reduceScript != null) {
      factory.reduceScript(reduceScript);
    }
    if (params != null) {
      factory.params(params);
    }
    return factory;
  }
  public static BucketSelectorPipelineAggregationBuilder parse(
      String reducerName, QueryParseContext context) throws IOException {
    XContentParser parser = context.parser();
    XContentParser.Token token;
    Script script = null;
    String currentFieldName = null;
    Map<String, String> bucketsPathsMap = null;
    GapPolicy gapPolicy = null;

    while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
      if (token == XContentParser.Token.FIELD_NAME) {
        currentFieldName = parser.currentName();
      } else if (token == XContentParser.Token.VALUE_STRING) {
        if (context.getParseFieldMatcher().match(currentFieldName, BUCKETS_PATH)) {
          bucketsPathsMap = new HashMap<>();
          bucketsPathsMap.put("_value", parser.text());
        } else if (context.getParseFieldMatcher().match(currentFieldName, GAP_POLICY)) {
          gapPolicy = GapPolicy.parse(context, parser.text(), parser.getTokenLocation());
        } else if (context.getParseFieldMatcher().match(currentFieldName, ScriptField.SCRIPT)) {
          script = Script.parse(parser, context.getParseFieldMatcher());
        } else {
          throw new ParsingException(
              parser.getTokenLocation(),
              "Unknown key for a "
                  + token
                  + " in ["
                  + reducerName
                  + "]: ["
                  + currentFieldName
                  + "].");
        }
      } else if (token == XContentParser.Token.START_ARRAY) {
        if (context.getParseFieldMatcher().match(currentFieldName, BUCKETS_PATH)) {
          List<String> paths = new ArrayList<>();
          while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
            String path = parser.text();
            paths.add(path);
          }
          bucketsPathsMap = new HashMap<>();
          for (int i = 0; i < paths.size(); i++) {
            bucketsPathsMap.put("_value" + i, paths.get(i));
          }
        } else {
          throw new ParsingException(
              parser.getTokenLocation(),
              "Unknown key for a "
                  + token
                  + " in ["
                  + reducerName
                  + "]: ["
                  + currentFieldName
                  + "].");
        }
      } else if (token == XContentParser.Token.START_OBJECT) {
        if (context.getParseFieldMatcher().match(currentFieldName, ScriptField.SCRIPT)) {
          script = Script.parse(parser, context.getParseFieldMatcher());
        } else if (context.getParseFieldMatcher().match(currentFieldName, BUCKETS_PATH)) {
          Map<String, Object> map = parser.map();
          bucketsPathsMap = new HashMap<>();
          for (Map.Entry<String, Object> entry : map.entrySet()) {
            bucketsPathsMap.put(entry.getKey(), String.valueOf(entry.getValue()));
          }
        } else {
          throw new ParsingException(
              parser.getTokenLocation(),
              "Unknown key for a "
                  + token
                  + " in ["
                  + reducerName
                  + "]: ["
                  + currentFieldName
                  + "].");
        }
      } else {
        throw new ParsingException(
            parser.getTokenLocation(), "Unexpected token " + token + " in [" + reducerName + "].");
      }
    }

    if (bucketsPathsMap == null) {
      throw new ParsingException(
          parser.getTokenLocation(),
          "Missing required field ["
              + BUCKETS_PATH.getPreferredName()
              + "] for bucket_selector aggregation ["
              + reducerName
              + "]");
    }

    if (script == null) {
      throw new ParsingException(
          parser.getTokenLocation(),
          "Missing required field ["
              + ScriptField.SCRIPT.getPreferredName()
              + "] for bucket_selector aggregation ["
              + reducerName
              + "]");
    }

    BucketSelectorPipelineAggregationBuilder factory =
        new BucketSelectorPipelineAggregationBuilder(reducerName, bucketsPathsMap, script);
    if (gapPolicy != null) {
      factory.gapPolicy(gapPolicy);
    }
    return factory;
  }
  static TermSuggestionBuilder innerFromXContent(QueryParseContext parseContext)
      throws IOException {
    XContentParser parser = parseContext.parser();
    TermSuggestionBuilder tmpSuggestion = new TermSuggestionBuilder("_na_");
    ParseFieldMatcher parseFieldMatcher = parseContext.getParseFieldMatcher();
    XContentParser.Token token;
    String currentFieldName = null;
    String fieldname = null;
    while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
      if (token == XContentParser.Token.FIELD_NAME) {
        currentFieldName = parser.currentName();
      } else if (token.isValue()) {
        if (parseFieldMatcher.match(currentFieldName, SuggestionBuilder.ANALYZER_FIELD)) {
          tmpSuggestion.analyzer(parser.text());
        } else if (parseFieldMatcher.match(currentFieldName, SuggestionBuilder.FIELDNAME_FIELD)) {
          fieldname = parser.text();
        } else if (parseFieldMatcher.match(currentFieldName, SuggestionBuilder.SIZE_FIELD)) {
          tmpSuggestion.size(parser.intValue());
        } else if (parseFieldMatcher.match(currentFieldName, SuggestionBuilder.SHARDSIZE_FIELD)) {
          tmpSuggestion.shardSize(parser.intValue());
        } else if (parseFieldMatcher.match(currentFieldName, SUGGESTMODE_FIELD)) {
          tmpSuggestion.suggestMode(SuggestMode.resolve(parser.text()));
        } else if (parseFieldMatcher.match(currentFieldName, ACCURACY_FIELD)) {
          tmpSuggestion.accuracy(parser.floatValue());
        } else if (parseFieldMatcher.match(currentFieldName, SORT_FIELD)) {
          tmpSuggestion.sort(SortBy.resolve(parser.text()));
        } else if (parseFieldMatcher.match(currentFieldName, STRING_DISTANCE_FIELD)) {
          tmpSuggestion.stringDistance(StringDistanceImpl.resolve(parser.text()));
        } else if (parseFieldMatcher.match(currentFieldName, MAX_EDITS_FIELD)) {
          tmpSuggestion.maxEdits(parser.intValue());
        } else if (parseFieldMatcher.match(currentFieldName, MAX_INSPECTIONS_FIELD)) {
          tmpSuggestion.maxInspections(parser.intValue());
        } else if (parseFieldMatcher.match(currentFieldName, MAX_TERM_FREQ_FIELD)) {
          tmpSuggestion.maxTermFreq(parser.floatValue());
        } else if (parseFieldMatcher.match(currentFieldName, PREFIX_LENGTH_FIELD)) {
          tmpSuggestion.prefixLength(parser.intValue());
        } else if (parseFieldMatcher.match(currentFieldName, MIN_WORD_LENGTH_FIELD)) {
          tmpSuggestion.minWordLength(parser.intValue());
        } else if (parseFieldMatcher.match(currentFieldName, MIN_DOC_FREQ_FIELD)) {
          tmpSuggestion.minDocFreq(parser.floatValue());
        } else if (parseFieldMatcher.match(currentFieldName, EXACT_MATCH_FIELD)) {
          tmpSuggestion.exactMatch(parser.booleanValue());
        } else {
          throw new ParsingException(
              parser.getTokenLocation(),
              "suggester[term] doesn't support field [" + currentFieldName + "]");
        }
      } else {
        throw new ParsingException(
            parser.getTokenLocation(),
            "suggester[term] parsing failed on [" + currentFieldName + "]");
      }
    }

    // now we should have field name, check and copy fields over to the suggestion builder we return
    if (fieldname == null) {
      throw new ElasticsearchParseException(
          "the required field option [" + FIELDNAME_FIELD.getPreferredName() + "] is missing");
    }
    return new TermSuggestionBuilder(fieldname, tmpSuggestion);
  }
  @Override
  public final MultiValuesSourceAggregationBuilder<VS, ?> parse(
      String aggregationName, QueryParseContext context) throws IOException {

    XContentParser parser = context.parser();
    List<String> fields = null;
    ValueType valueType = null;
    String format = null;
    Map<String, Object> missingMap = null;
    Map<ParseField, Object> otherOptions = new HashMap<>();
    final ParseFieldMatcher parseFieldMatcher = context.getParseFieldMatcher();

    XContentParser.Token token;
    String currentFieldName = null;
    while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
      if (token == XContentParser.Token.FIELD_NAME) {
        currentFieldName = parser.currentName();
      } else if (token == XContentParser.Token.VALUE_STRING) {
        if (parseFieldMatcher.match(currentFieldName, CommonFields.FIELDS)) {
          fields = Collections.singletonList(parser.text());
        } else if (formattable && parseFieldMatcher.match(currentFieldName, CommonFields.FORMAT)) {
          format = parser.text();
        } else if (parseFieldMatcher.match(currentFieldName, CommonFields.VALUE_TYPE)) {
          throw new ParsingException(
              parser.getTokenLocation(),
              "Unexpected token "
                  + token
                  + " ["
                  + currentFieldName
                  + "] in ["
                  + aggregationName
                  + "]. "
                  + "Multi-field aggregations do not support scripts.");
        } else if (!token(
            aggregationName,
            currentFieldName,
            token,
            parser,
            context.getParseFieldMatcher(),
            otherOptions)) {
          throw new ParsingException(
              parser.getTokenLocation(),
              "Unexpected token "
                  + token
                  + " ["
                  + currentFieldName
                  + "] in ["
                  + aggregationName
                  + "].");
        }
      } else if (token == XContentParser.Token.START_OBJECT) {
        if (parseFieldMatcher.match(currentFieldName, CommonFields.MISSING)) {
          missingMap = new HashMap<>();
          while (parser.nextToken() != XContentParser.Token.END_OBJECT) {
            parseMissingAndAdd(aggregationName, currentFieldName, parser, missingMap);
          }
        } else if (context.getParseFieldMatcher().match(currentFieldName, ScriptField.SCRIPT)) {
          throw new ParsingException(
              parser.getTokenLocation(),
              "Unexpected token "
                  + token
                  + " ["
                  + currentFieldName
                  + "] in ["
                  + aggregationName
                  + "]. "
                  + "Multi-field aggregations do not support scripts.");

        } else if (!token(
            aggregationName,
            currentFieldName,
            token,
            parser,
            context.getParseFieldMatcher(),
            otherOptions)) {
          throw new ParsingException(
              parser.getTokenLocation(),
              "Unexpected token "
                  + token
                  + " ["
                  + currentFieldName
                  + "] in ["
                  + aggregationName
                  + "].");
        }
      } else if (token == XContentParser.Token.START_ARRAY) {
        if (context.getParseFieldMatcher().match(currentFieldName, ScriptField.SCRIPT)) {
          throw new ParsingException(
              parser.getTokenLocation(),
              "Unexpected token "
                  + token
                  + " ["
                  + currentFieldName
                  + "] in ["
                  + aggregationName
                  + "]. "
                  + "Multi-field aggregations do not support scripts.");
        } else if (parseFieldMatcher.match(currentFieldName, CommonFields.FIELDS)) {
          fields = new ArrayList<>();
          while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
            if (token == XContentParser.Token.VALUE_STRING) {
              fields.add(parser.text());
            } else {
              throw new ParsingException(
                  parser.getTokenLocation(),
                  "Unexpected token "
                      + token
                      + " ["
                      + currentFieldName
                      + "] in ["
                      + aggregationName
                      + "].");
            }
          }
        } else if (!token(
            aggregationName,
            currentFieldName,
            token,
            parser,
            context.getParseFieldMatcher(),
            otherOptions)) {
          throw new ParsingException(
              parser.getTokenLocation(),
              "Unexpected token "
                  + token
                  + " ["
                  + currentFieldName
                  + "] in ["
                  + aggregationName
                  + "].");
        }
      } else if (!token(
          aggregationName,
          currentFieldName,
          token,
          parser,
          context.getParseFieldMatcher(),
          otherOptions)) {
        throw new ParsingException(
            parser.getTokenLocation(),
            "Unexpected token "
                + token
                + " ["
                + currentFieldName
                + "] in ["
                + aggregationName
                + "].");
      }
    }

    MultiValuesSourceAggregationBuilder<VS, ?> factory =
        createFactory(aggregationName, this.valuesSourceType, this.targetValueType, otherOptions);
    if (fields != null) {
      factory.fields(fields);
    }
    if (valueType != null) {
      factory.valueType(valueType);
    }
    if (format != null) {
      factory.format(format);
    }
    if (missingMap != null) {
      factory.missingMap(missingMap);
    }
    return factory;
  }