@Override
 public void checkCompatibility(
     MappedFieldType fieldType, List<String> conflicts, boolean strict) {
   super.checkCompatibility(fieldType, conflicts, strict);
   CompletionFieldType other = (CompletionFieldType) fieldType;
   if (analyzingSuggestLookupProvider.hasPayloads()
       != other.analyzingSuggestLookupProvider.hasPayloads()) {
     conflicts.add("mapper [" + names().fullName() + "] has different payload values");
   }
   if (analyzingSuggestLookupProvider.getPreservePositionsIncrements()
       != other.analyzingSuggestLookupProvider.getPreservePositionsIncrements()) {
     conflicts.add(
         "mapper ["
             + names().fullName()
             + "] has different 'preserve_position_increments' values");
   }
   if (analyzingSuggestLookupProvider.getPreserveSep()
       != other.analyzingSuggestLookupProvider.getPreserveSep()) {
     conflicts.add(
         "mapper [" + names().fullName() + "] has different 'preserve_separators' values");
   }
   if (!ContextMapping.mappingsAreEqual(getContextMapping(), other.getContextMapping())) {
     conflicts.add("mapper [" + names().fullName() + "] has different 'context_mapping' values");
   }
 }
 @Override
 public void merge(Mapper mergeWith, MergeResult mergeResult) throws MergeMappingException {
   super.merge(mergeWith, mergeResult);
   CompletionFieldMapper fieldMergeWith = (CompletionFieldMapper) mergeWith;
   if (fieldType().analyzingSuggestLookupProvider.hasPayloads()
       != fieldMergeWith.fieldType().analyzingSuggestLookupProvider.hasPayloads()) {
     mergeResult.addConflict(
         "mapper [" + fieldType.names().fullName() + "] has different payload values");
   }
   if (fieldType().analyzingSuggestLookupProvider.getPreservePositionsIncrements()
       != fieldMergeWith
           .fieldType()
           .analyzingSuggestLookupProvider
           .getPreservePositionsIncrements()) {
     mergeResult.addConflict(
         "mapper ["
             + fieldType.names().fullName()
             + "] has different 'preserve_position_increments' values");
   }
   if (fieldType().analyzingSuggestLookupProvider.getPreserveSep()
       != fieldMergeWith.fieldType().analyzingSuggestLookupProvider.getPreserveSep()) {
     mergeResult.addConflict(
         "mapper ["
             + fieldType.names().fullName()
             + "] has different 'preserve_separators' values");
   }
   if (!ContextMapping.mappingsAreEqual(
       fieldType().getContextMapping(), fieldMergeWith.fieldType().getContextMapping())) {
     mergeResult.addConflict(
         "mapper [" + fieldType.names().fullName() + "] has different 'context_mapping' values");
   }
   if (!mergeResult.simulate()) {
     this.maxInputLength = fieldMergeWith.maxInputLength;
   }
 }
    /**
     * Parse a set of {@link ContextQuery} according to a given mapping
     *
     * @param mappings List of mapping defined y the suggest field
     * @param parser parser holding the settings of the queries. The parsers current token is
     *     assumed hold an array. The number of elements in this array must match the number of
     *     elements in the mappings.
     * @return List of context queries
     * @throws IOException if something unexpected happened on the underlying stream
     * @throws ElasticsearchParseException if the list of queries could not be parsed
     */
    public static List<ContextQuery> parseQueries(
        Map<String, ContextMapping> mappings, XContentParser parser)
        throws IOException, ElasticsearchParseException {

      Map<String, ContextQuery> querySet = new HashMap<>();
      Token token = parser.currentToken();
      if (token == Token.START_OBJECT) {
        while ((token = parser.nextToken()) != Token.END_OBJECT) {
          String name = parser.text();
          ContextMapping mapping = mappings.get(name);
          if (mapping == null) {
            throw new ElasticsearchParseException("no mapping defined for [" + name + "]");
          }
          parser.nextToken();
          querySet.put(name, mapping.parseQuery(name, parser));
        }
      }

      List<ContextQuery> queries = Lists.newArrayListWithExpectedSize(mappings.size());
      for (ContextMapping mapping : mappings.values()) {
        queries.add(querySet.get(mapping.name));
      }
      return queries;
    }
 @Override
 public void merge(Mapper mergeWith, MergeContext mergeContext) throws MergeMappingException {
   super.merge(mergeWith, mergeContext);
   CompletionFieldMapper fieldMergeWith = (CompletionFieldMapper) mergeWith;
   if (payloads != fieldMergeWith.payloads) {
     mergeContext.addConflict("mapper [" + names.fullName() + "] has different payload values");
   }
   if (preservePositionIncrements != fieldMergeWith.preservePositionIncrements) {
     mergeContext.addConflict(
         "mapper [" + names.fullName() + "] has different 'preserve_position_increments' values");
   }
   if (preserveSeparators != fieldMergeWith.preserveSeparators) {
     mergeContext.addConflict(
         "mapper [" + names.fullName() + "] has different 'preserve_separators' values");
   }
   if (!ContextMapping.mappingsAreEqual(getContextMapping(), fieldMergeWith.getContextMapping())) {
     mergeContext.addConflict(
         "mapper [" + names.fullName() + "] has different 'context_mapping' values");
   }
   if (!mergeContext.mergeFlags().simulate()) {
     this.maxInputLength = fieldMergeWith.maxInputLength;
   }
 }
  @Override
  public Mapper parse(ParseContext context) throws IOException {
    XContentParser parser = context.parser();
    XContentParser.Token token = parser.currentToken();
    if (token == XContentParser.Token.VALUE_NULL) {
      throw new MapperParsingException(
          "completion field [" + fieldType().names().fullName() + "] does not support null values");
    }

    String surfaceForm = null;
    BytesRef payload = null;
    long weight = -1;
    List<String> inputs = Lists.newArrayListWithExpectedSize(4);

    SortedMap<String, ContextConfig> contextConfig = null;

    if (token == XContentParser.Token.VALUE_STRING) {
      inputs.add(parser.text());
      multiFields.parse(this, context);
    } else {
      String currentFieldName = null;
      while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
        if (token == XContentParser.Token.FIELD_NAME) {
          currentFieldName = parser.currentName();
          if (!ALLOWED_CONTENT_FIELD_NAMES.contains(currentFieldName)) {
            throw new IllegalArgumentException(
                "Unknown field name["
                    + currentFieldName
                    + "], must be one of "
                    + ALLOWED_CONTENT_FIELD_NAMES);
          }
        } else if (Fields.CONTEXT.equals(currentFieldName)) {
          SortedMap<String, ContextConfig> configs = Maps.newTreeMap();

          if (token == Token.START_OBJECT) {
            while ((token = parser.nextToken()) != Token.END_OBJECT) {
              String name = parser.text();
              ContextMapping mapping = fieldType().getContextMapping().get(name);
              if (mapping == null) {
                throw new ElasticsearchParseException("context [{}] is not defined", name);
              } else {
                token = parser.nextToken();
                configs.put(name, mapping.parseContext(context, parser));
              }
            }
            contextConfig = Maps.newTreeMap();
            for (ContextMapping mapping : fieldType().getContextMapping().values()) {
              ContextConfig config = configs.get(mapping.name());
              contextConfig.put(mapping.name(), config == null ? mapping.defaultConfig() : config);
            }
          } else {
            throw new ElasticsearchParseException("context must be an object");
          }
        } else if (Fields.CONTENT_FIELD_NAME_PAYLOAD.equals(currentFieldName)) {
          if (!isStoringPayloads()) {
            throw new MapperException("Payloads disabled in mapping");
          }
          if (token == XContentParser.Token.START_OBJECT) {
            XContentBuilder payloadBuilder =
                XContentFactory.contentBuilder(parser.contentType()).copyCurrentStructure(parser);
            payload = payloadBuilder.bytes().toBytesRef();
            payloadBuilder.close();
          } else if (token.isValue()) {
            payload = parser.utf8BytesOrNull();
          } else {
            throw new MapperException("payload doesn't support type " + token);
          }
        } else if (token == XContentParser.Token.VALUE_STRING) {
          if (Fields.CONTENT_FIELD_NAME_OUTPUT.equals(currentFieldName)) {
            surfaceForm = parser.text();
          }
          if (Fields.CONTENT_FIELD_NAME_INPUT.equals(currentFieldName)) {
            inputs.add(parser.text());
          }
          if (Fields.CONTENT_FIELD_NAME_WEIGHT.equals(currentFieldName)) {
            Number weightValue;
            try {
              weightValue = Long.parseLong(parser.text());
            } catch (NumberFormatException e) {
              throw new IllegalArgumentException(
                  "Weight must be a string representing a numeric value, but was ["
                      + parser.text()
                      + "]");
            }
            weight =
                weightValue.longValue(); // always parse a long to make sure we don't get overflow
            checkWeight(weight);
          }
        } else if (token == XContentParser.Token.VALUE_NUMBER) {
          if (Fields.CONTENT_FIELD_NAME_WEIGHT.equals(currentFieldName)) {
            NumberType numberType = parser.numberType();
            if (NumberType.LONG != numberType && NumberType.INT != numberType) {
              throw new IllegalArgumentException(
                  "Weight must be an integer, but was [" + parser.numberValue() + "]");
            }
            weight = parser.longValue(); // always parse a long to make sure we don't get overflow
            checkWeight(weight);
          }
        } else if (token == XContentParser.Token.START_ARRAY) {
          if (Fields.CONTENT_FIELD_NAME_INPUT.equals(currentFieldName)) {
            while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
              inputs.add(parser.text());
            }
          }
        }
      }
    }

    if (contextConfig == null) {
      contextConfig = Maps.newTreeMap();
      for (ContextMapping mapping : fieldType().getContextMapping().values()) {
        contextConfig.put(mapping.name(), mapping.defaultConfig());
      }
    }

    final ContextMapping.Context ctx = new ContextMapping.Context(contextConfig, context.doc());

    payload = payload == null ? EMPTY : payload;
    if (surfaceForm == null) { // no surface form use the input
      for (String input : inputs) {
        if (input.length() == 0) {
          continue;
        }
        BytesRef suggestPayload =
            fieldType()
                .analyzingSuggestLookupProvider
                .buildPayload(new BytesRef(input), weight, payload);
        context.doc().add(getCompletionField(ctx, input, suggestPayload));
      }
    } else {
      BytesRef suggestPayload =
          fieldType()
              .analyzingSuggestLookupProvider
              .buildPayload(new BytesRef(surfaceForm), weight, payload);
      for (String input : inputs) {
        if (input.length() == 0) {
          continue;
        }
        context.doc().add(getCompletionField(ctx, input, suggestPayload));
      }
    }
    return null;
  }