private void getPrefixTerms(
      ObjectHashSet<Term> terms, final Term prefix, final IndexReader reader) throws IOException {
    // SlowCompositeReaderWrapper could be used... but this would merge all terms from each segment
    // into one terms
    // instance, which is very expensive. Therefore I think it is better to iterate over each leaf
    // individually.
    List<LeafReaderContext> leaves = reader.leaves();
    for (LeafReaderContext leaf : leaves) {
      Terms _terms = leaf.reader().terms(field);
      if (_terms == null) {
        continue;
      }

      TermsEnum termsEnum = _terms.iterator();
      TermsEnum.SeekStatus seekStatus = termsEnum.seekCeil(prefix.bytes());
      if (TermsEnum.SeekStatus.END == seekStatus) {
        continue;
      }

      for (BytesRef term = termsEnum.term(); term != null; term = termsEnum.next()) {
        if (!StringHelper.startsWith(term, prefix.bytes())) {
          break;
        }

        terms.add(new Term(field, BytesRef.deepCopyOf(term)));
        if (terms.size() >= maxExpansions) {
          return;
        }
      }
    }
  }
 @Override
 public Query rewrite(IndexReader reader) throws IOException {
   Query rewritten = super.rewrite(reader);
   if (rewritten != this) {
     return rewritten;
   }
   if (termArrays.isEmpty()) {
     return new MatchNoDocsQuery();
   }
   MultiPhraseQuery.Builder query = new MultiPhraseQuery.Builder();
   query.setSlop(slop);
   int sizeMinus1 = termArrays.size() - 1;
   for (int i = 0; i < sizeMinus1; i++) {
     query.add(termArrays.get(i), positions.get(i));
   }
   Term[] suffixTerms = termArrays.get(sizeMinus1);
   int position = positions.get(sizeMinus1);
   ObjectHashSet<Term> terms = new ObjectHashSet<>();
   for (Term term : suffixTerms) {
     getPrefixTerms(terms, term, reader);
     if (terms.size() > maxExpansions) {
       break;
     }
   }
   if (terms.isEmpty()) {
     return Queries.newMatchNoDocsQuery();
   }
   query.add(terms.toArray(Term.class), position);
   return query.build();
 }
  /** All entries to lowercase. */
  private static ObjectHashSet<MutableCharArray> toLower(Set<String> input) {
    ObjectHashSet<MutableCharArray> cloned = new ObjectHashSet<MutableCharArray>(input.size());

    for (String entry : input) {
      char[] chars = entry.toCharArray();
      CharArrayUtils.toLowerCaseInPlace(chars);
      cloned.add(new MutableCharArray(chars));
    }

    return cloned;
  }
 @Override
 public BytesRef binaryValue() {
   final byte[] bytes = new byte[points.size() * 16];
   int off = 0;
   for (Iterator<ObjectCursor<GeoPoint>> it = points.iterator(); it.hasNext(); ) {
     final GeoPoint point = it.next().value;
     ByteUtils.writeDoubleLE(point.getLat(), bytes, off);
     ByteUtils.writeDoubleLE(point.getLon(), bytes, off + 8);
     off += 16;
   }
   return new BytesRef(bytes);
 }
  /*
   * Finds all mappings for types and concrete indices. Types are expanded to
   * include all types that match the glob patterns in the types array. Empty
   * types array, null or {"_all"} will be expanded to all types available for
   * the given indices.
   */
  public ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> findMappings(
      String[] concreteIndices, final String[] types) {
    assert types != null;
    assert concreteIndices != null;
    if (concreteIndices.length == 0) {
      return ImmutableOpenMap.of();
    }

    ImmutableOpenMap.Builder<String, ImmutableOpenMap<String, MappingMetaData>> indexMapBuilder =
        ImmutableOpenMap.builder();
    Iterable<String> intersection =
        HppcMaps.intersection(ObjectHashSet.from(concreteIndices), indices.keys());
    for (String index : intersection) {
      IndexMetaData indexMetaData = indices.get(index);
      ImmutableOpenMap.Builder<String, MappingMetaData> filteredMappings;
      if (isAllTypes(types)) {
        indexMapBuilder.put(
            index, indexMetaData.getMappings()); // No types specified means get it all

      } else {
        filteredMappings = ImmutableOpenMap.builder();
        for (ObjectObjectCursor<String, MappingMetaData> cursor : indexMetaData.getMappings()) {
          if (Regex.simpleMatch(types, cursor.key)) {
            filteredMappings.put(cursor.key, cursor.value);
          }
        }
        if (!filteredMappings.isEmpty()) {
          indexMapBuilder.put(index, filteredMappings.build());
        }
      }
    }
    return indexMapBuilder.build();
  }
  /** Reload all lexical resources associated with the given key. */
  private static HashMap<LanguageCode, ILexicalData> reloadResources(
      ResourceLookup resourceLookup) {
    // Load lexical resources.
    ObjectHashSet<MutableCharArray> mergedStopwords = new ObjectHashSet<>();
    ArrayList<Pattern> mergedStoplabels = Lists.newArrayList();

    HashMap<LanguageCode, ILexicalData> resourceMap = Maps.newHashMap();
    for (LanguageCode languageCode : LanguageCode.values()) {
      final String isoCode = languageCode.getIsoCode();

      ObjectHashSet<MutableCharArray> stopwords =
          toLower(load(resourceLookup, "stopwords." + isoCode));
      ArrayList<Pattern> stoplabels = compile(load(resourceLookup, "stoplabels." + isoCode));

      mergedStopwords.addAll(stopwords);
      mergedStoplabels.addAll(stoplabels);

      resourceMap.put(languageCode, new DefaultLexicalData(stopwords, stoplabels));
    }
    resourceMap.put(null, new DefaultLexicalData(mergedStopwords, mergedStoplabels));

    return resourceMap;
  }
  /**
   * Finds the specific index aliases that match with the specified aliases directly or partially
   * via wildcards and that point to the specified concrete indices or match partially with the
   * indices via wildcards.
   *
   * @param aliases The names of the index aliases to find
   * @param concreteIndices The concrete indexes the index aliases must point to order to be
   *     returned.
   * @return the found index aliases grouped by index
   */
  public ImmutableOpenMap<String, List<AliasMetaData>> findAliases(
      final String[] aliases, String[] concreteIndices) {
    assert aliases != null;
    assert concreteIndices != null;
    if (concreteIndices.length == 0) {
      return ImmutableOpenMap.of();
    }

    boolean matchAllAliases = matchAllAliases(aliases);
    ImmutableOpenMap.Builder<String, List<AliasMetaData>> mapBuilder = ImmutableOpenMap.builder();
    Iterable<String> intersection =
        HppcMaps.intersection(ObjectHashSet.from(concreteIndices), indices.keys());
    for (String index : intersection) {
      IndexMetaData indexMetaData = indices.get(index);
      List<AliasMetaData> filteredValues = new ArrayList<>();
      for (ObjectCursor<AliasMetaData> cursor : indexMetaData.getAliases().values()) {
        AliasMetaData value = cursor.value;
        if (matchAllAliases || Regex.simpleMatch(aliases, value.alias())) {
          filteredValues.add(value);
        }
      }

      if (!filteredValues.isEmpty()) {
        // Make the list order deterministic
        CollectionUtil.timSort(
            filteredValues,
            new Comparator<AliasMetaData>() {
              @Override
              public int compare(AliasMetaData o1, AliasMetaData o2) {
                return o1.alias().compareTo(o2.alias());
              }
            });
        mapBuilder.put(index, Collections.unmodifiableList(filteredValues));
      }
    }
    return mapBuilder.build();
  }
  /**
   * Checks if at least one of the specified aliases exists in the specified concrete indices.
   * Wildcards are supported in the alias names for partial matches.
   *
   * @param aliases The names of the index aliases to find
   * @param concreteIndices The concrete indexes the index aliases must point to order to be
   *     returned.
   * @return whether at least one of the specified aliases exists in one of the specified concrete
   *     indices.
   */
  public boolean hasAliases(final String[] aliases, String[] concreteIndices) {
    assert aliases != null;
    assert concreteIndices != null;
    if (concreteIndices.length == 0) {
      return false;
    }

    Iterable<String> intersection =
        HppcMaps.intersection(ObjectHashSet.from(concreteIndices), indices.keys());
    for (String index : intersection) {
      IndexMetaData indexMetaData = indices.get(index);
      List<AliasMetaData> filteredValues = new ArrayList<>();
      for (ObjectCursor<AliasMetaData> cursor : indexMetaData.getAliases().values()) {
        AliasMetaData value = cursor.value;
        if (Regex.simpleMatch(aliases, value.alias())) {
          filteredValues.add(value);
        }
      }
      if (!filteredValues.isEmpty()) {
        return true;
      }
    }
    return false;
  }
 public static String[] getAllMetaFields() {
   return META_FIELDS.toArray(String.class);
 }
 /** @return Whether a field is a metadata field. */
 public static boolean isMetadataField(String fieldName) {
   return META_FIELDS.contains(fieldName);
 }
public class MapperService extends AbstractIndexComponent {

  /** The reason why a mapping is being merged. */
  public enum MergeReason {
    /** Create or update a mapping. */
    MAPPING_UPDATE,
    /**
     * Recovery of an existing mapping, for instance because of a restart, if a shard was moved to a
     * different node or for administrative purposes.
     */
    MAPPING_RECOVERY;
  }

  public static final String DEFAULT_MAPPING = "_default_";
  public static final Setting<Long> INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING =
      Setting.longSetting(
          "index.mapping.nested_fields.limit", 50L, 0, Property.Dynamic, Property.IndexScope);
  public static final Setting<Long> INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING =
      Setting.longSetting(
          "index.mapping.total_fields.limit", 1000L, 0, Property.Dynamic, Property.IndexScope);
  public static final Setting<Long> INDEX_MAPPING_DEPTH_LIMIT_SETTING =
      Setting.longSetting(
          "index.mapping.depth.limit", 20L, 1, Property.Dynamic, Property.IndexScope);
  public static final boolean INDEX_MAPPER_DYNAMIC_DEFAULT = true;
  public static final Setting<Boolean> INDEX_MAPPER_DYNAMIC_SETTING =
      Setting.boolSetting(
          "index.mapper.dynamic", INDEX_MAPPER_DYNAMIC_DEFAULT, Property.IndexScope);
  private static ObjectHashSet<String> META_FIELDS =
      ObjectHashSet.from(
          "_uid",
          "_id",
          "_type",
          "_all",
          "_parent",
          "_routing",
          "_index",
          "_size",
          "_timestamp",
          "_ttl");

  private final AnalysisService analysisService;

  /** Will create types automatically if they do not exists in the mapping definition yet */
  private final boolean dynamic;

  private volatile String defaultMappingSource;

  private volatile Map<String, DocumentMapper> mappers = emptyMap();

  private volatile FieldTypeLookup fieldTypes;
  private volatile Map<String, ObjectMapper> fullPathObjectMappers = new HashMap<>();
  private boolean hasNested = false; // updated dynamically to true when a nested object is added

  private final DocumentMapperParser documentParser;

  private final MapperAnalyzerWrapper indexAnalyzer;
  private final MapperAnalyzerWrapper searchAnalyzer;
  private final MapperAnalyzerWrapper searchQuoteAnalyzer;

  private volatile Map<String, MappedFieldType> unmappedFieldTypes = emptyMap();

  private volatile Set<String> parentTypes = emptySet();

  final MapperRegistry mapperRegistry;

  public MapperService(
      IndexSettings indexSettings,
      AnalysisService analysisService,
      SimilarityService similarityService,
      MapperRegistry mapperRegistry,
      Supplier<QueryShardContext> queryShardContextSupplier) {
    super(indexSettings);
    this.analysisService = analysisService;
    this.fieldTypes = new FieldTypeLookup();
    this.documentParser =
        new DocumentMapperParser(
            indexSettings,
            this,
            analysisService,
            similarityService,
            mapperRegistry,
            queryShardContextSupplier);
    this.indexAnalyzer =
        new MapperAnalyzerWrapper(analysisService.defaultIndexAnalyzer(), p -> p.indexAnalyzer());
    this.searchAnalyzer =
        new MapperAnalyzerWrapper(analysisService.defaultSearchAnalyzer(), p -> p.searchAnalyzer());
    this.searchQuoteAnalyzer =
        new MapperAnalyzerWrapper(
            analysisService.defaultSearchQuoteAnalyzer(), p -> p.searchQuoteAnalyzer());
    this.mapperRegistry = mapperRegistry;

    this.dynamic = this.indexSettings.getValue(INDEX_MAPPER_DYNAMIC_SETTING);
    if (index().getName().equals(ScriptService.SCRIPT_INDEX)) {
      defaultMappingSource =
          "{"
              + "\"_default_\": {"
              + "\"properties\": {"
              + "\"script\": { \"enabled\": false },"
              + "\"template\": { \"enabled\": false }"
              + "}"
              + "}"
              + "}";
    } else {
      defaultMappingSource = "{\"_default_\":{}}";
    }

    if (logger.isTraceEnabled()) {
      logger.trace("using dynamic[{}], default mapping source[{}]", dynamic, defaultMappingSource);
    } else if (logger.isDebugEnabled()) {
      logger.debug("using dynamic[{}]", dynamic);
    }
  }

  public boolean hasNested() {
    return this.hasNested;
  }

  /**
   * returns an immutable iterator over current document mappers.
   *
   * @param includingDefaultMapping indicates whether the iterator should contain the {@link
   *     #DEFAULT_MAPPING} document mapper. As is this not really an active type, you would
   *     typically set this to false
   */
  public Iterable<DocumentMapper> docMappers(final boolean includingDefaultMapping) {
    return () -> {
      final Collection<DocumentMapper> documentMappers;
      if (includingDefaultMapping) {
        documentMappers = mappers.values();
      } else {
        documentMappers =
            mappers
                .values()
                .stream()
                .filter(mapper -> !DEFAULT_MAPPING.equals(mapper.type()))
                .collect(Collectors.toList());
      }
      return Collections.unmodifiableCollection(documentMappers).iterator();
    };
  }

  public AnalysisService analysisService() {
    return this.analysisService;
  }

  public DocumentMapperParser documentMapperParser() {
    return this.documentParser;
  }

  public DocumentMapper merge(
      String type, CompressedXContent mappingSource, MergeReason reason, boolean updateAllTypes) {
    if (DEFAULT_MAPPING.equals(type)) {
      // verify we can parse it
      // NOTE: never apply the default here
      DocumentMapper mapper = documentParser.parse(type, mappingSource);
      // still add it as a document mapper so we have it registered and, for example, persisted back
      // into
      // the cluster meta data if needed, or checked for existence
      synchronized (this) {
        mappers = newMapBuilder(mappers).put(type, mapper).map();
      }
      try {
        defaultMappingSource = mappingSource.string();
      } catch (IOException e) {
        throw new ElasticsearchGenerationException("failed to un-compress", e);
      }
      return mapper;
    } else {
      synchronized (this) {
        final boolean applyDefault =
            // the default was already applied if we are recovering
            reason != MergeReason.MAPPING_RECOVERY
                // only apply the default mapping if we don't have the type yet
                && mappers.containsKey(type) == false;
        DocumentMapper mergeWith = parse(type, mappingSource, applyDefault);
        return merge(mergeWith, reason, updateAllTypes);
      }
    }
  }

  private synchronized DocumentMapper merge(
      DocumentMapper mapper, MergeReason reason, boolean updateAllTypes) {
    if (mapper.type().length() == 0) {
      throw new InvalidTypeNameException("mapping type name is empty");
    }
    if (mapper.type().length() > 255) {
      throw new InvalidTypeNameException(
          "mapping type name ["
              + mapper.type()
              + "] is too long; limit is length 255 but was ["
              + mapper.type().length()
              + "]");
    }
    if (mapper.type().charAt(0) == '_') {
      throw new InvalidTypeNameException(
          "mapping type name [" + mapper.type() + "] can't start with '_'");
    }
    if (mapper.type().contains("#")) {
      throw new InvalidTypeNameException(
          "mapping type name [" + mapper.type() + "] should not include '#' in it");
    }
    if (mapper.type().contains(",")) {
      throw new InvalidTypeNameException(
          "mapping type name [" + mapper.type() + "] should not include ',' in it");
    }
    if (mapper.type().equals(mapper.parentFieldMapper().type())) {
      throw new IllegalArgumentException("The [_parent.type] option can't point to the same type");
    }
    if (typeNameStartsWithIllegalDot(mapper)) {
      throw new IllegalArgumentException(
          "mapping type name [" + mapper.type() + "] must not start with a '.'");
    }

    // 1. compute the merged DocumentMapper
    DocumentMapper oldMapper = mappers.get(mapper.type());
    DocumentMapper newMapper;
    if (oldMapper != null) {
      newMapper = oldMapper.merge(mapper.mapping(), updateAllTypes);
    } else {
      newMapper = mapper;
    }

    // 2. check basic sanity of the new mapping
    List<ObjectMapper> objectMappers = new ArrayList<>();
    List<FieldMapper> fieldMappers = new ArrayList<>();
    Collections.addAll(fieldMappers, newMapper.mapping().metadataMappers);
    MapperUtils.collect(newMapper.mapping().root(), objectMappers, fieldMappers);
    checkFieldUniqueness(newMapper.type(), objectMappers, fieldMappers);
    checkObjectsCompatibility(newMapper.type(), objectMappers, fieldMappers, updateAllTypes);

    // 3. update lookup data-structures
    // this will in particular make sure that the merged fields are compatible with other types
    FieldTypeLookup fieldTypes =
        this.fieldTypes.copyAndAddAll(newMapper.type(), fieldMappers, updateAllTypes);

    boolean hasNested = this.hasNested;
    Map<String, ObjectMapper> fullPathObjectMappers = new HashMap<>(this.fullPathObjectMappers);
    for (ObjectMapper objectMapper : objectMappers) {
      fullPathObjectMappers.put(objectMapper.fullPath(), objectMapper);
      if (objectMapper.nested().isNested()) {
        hasNested = true;
      }
    }
    fullPathObjectMappers = Collections.unmodifiableMap(fullPathObjectMappers);

    if (reason == MergeReason.MAPPING_UPDATE) {
      // this check will only be performed on the master node when there is
      // a call to the update mapping API. For all other cases like
      // the master node restoring mappings from disk or data nodes
      // deserializing cluster state that was sent by the master node,
      // this check will be skipped.
      checkNestedFieldsLimit(fullPathObjectMappers);
      checkTotalFieldsLimit(objectMappers.size() + fieldMappers.size());
      checkDepthLimit(fullPathObjectMappers.keySet());
      checkPercolatorFieldLimit(fieldTypes);
    }

    Set<String> parentTypes = this.parentTypes;
    if (oldMapper == null && newMapper.parentFieldMapper().active()) {
      parentTypes = new HashSet<>(parentTypes.size() + 1);
      parentTypes.addAll(this.parentTypes);
      parentTypes.add(mapper.parentFieldMapper().type());
      parentTypes = Collections.unmodifiableSet(parentTypes);
    }

    Map<String, DocumentMapper> mappers = new HashMap<>(this.mappers);
    mappers.put(newMapper.type(), newMapper);
    for (Map.Entry<String, DocumentMapper> entry : mappers.entrySet()) {
      if (entry.getKey().equals(DEFAULT_MAPPING)) {
        continue;
      }
      DocumentMapper m = entry.getValue();
      // apply changes to the field types back
      m = m.updateFieldType(fieldTypes.fullNameToFieldType);
      entry.setValue(m);
    }
    mappers = Collections.unmodifiableMap(mappers);

    // 4. commit the change
    this.mappers = mappers;
    this.fieldTypes = fieldTypes;
    this.hasNested = hasNested;
    this.fullPathObjectMappers = fullPathObjectMappers;
    this.parentTypes = parentTypes;

    assert assertSerialization(newMapper);
    assert assertMappersShareSameFieldType();

    return newMapper;
  }

  private boolean assertMappersShareSameFieldType() {
    for (DocumentMapper mapper : docMappers(false)) {
      List<FieldMapper> fieldMappers = new ArrayList<>();
      Collections.addAll(fieldMappers, mapper.mapping().metadataMappers);
      MapperUtils.collect(mapper.root(), new ArrayList<ObjectMapper>(), fieldMappers);
      for (FieldMapper fieldMapper : fieldMappers) {
        assert fieldMapper.fieldType() == fieldTypes.get(fieldMapper.name()) : fieldMapper.name();
      }
    }
    return true;
  }

  private boolean typeNameStartsWithIllegalDot(DocumentMapper mapper) {
    boolean legacyIndex =
        getIndexSettings().getIndexVersionCreated().before(Version.V_5_0_0_alpha1);
    if (legacyIndex) {
      return mapper.type().startsWith(".")
          && !PercolatorFieldMapper.LEGACY_TYPE_NAME.equals(mapper.type());
    } else {
      return mapper.type().startsWith(".");
    }
  }

  private boolean assertSerialization(DocumentMapper mapper) {
    // capture the source now, it may change due to concurrent parsing
    final CompressedXContent mappingSource = mapper.mappingSource();
    DocumentMapper newMapper = parse(mapper.type(), mappingSource, false);

    if (newMapper.mappingSource().equals(mappingSource) == false) {
      throw new IllegalStateException(
          "DocumentMapper serialization result is different from source. \n--> Source ["
              + mappingSource
              + "]\n--> Result ["
              + newMapper.mappingSource()
              + "]");
    }
    return true;
  }

  private void checkFieldUniqueness(
      String type, Collection<ObjectMapper> objectMappers, Collection<FieldMapper> fieldMappers) {
    assert Thread.holdsLock(this);

    // first check within mapping
    final Set<String> objectFullNames = new HashSet<>();
    for (ObjectMapper objectMapper : objectMappers) {
      final String fullPath = objectMapper.fullPath();
      if (objectFullNames.add(fullPath) == false) {
        throw new IllegalArgumentException(
            "Object mapper [" + fullPath + "] is defined twice in mapping for type [" + type + "]");
      }
    }

    final Set<String> fieldNames = new HashSet<>();
    for (FieldMapper fieldMapper : fieldMappers) {
      final String name = fieldMapper.name();
      if (objectFullNames.contains(name)) {
        throw new IllegalArgumentException(
            "Field [" + name + "] is defined both as an object and a field in [" + type + "]");
      } else if (fieldNames.add(name) == false) {
        throw new IllegalArgumentException(
            "Field [" + name + "] is defined twice in [" + type + "]");
      }
    }

    // then check other types
    for (String fieldName : fieldNames) {
      if (fullPathObjectMappers.containsKey(fieldName)) {
        throw new IllegalArgumentException(
            "["
                + fieldName
                + "] is defined as a field in mapping ["
                + type
                + "] but this name is already used for an object in other types");
      }
    }

    for (String objectPath : objectFullNames) {
      if (fieldTypes.get(objectPath) != null) {
        throw new IllegalArgumentException(
            "["
                + objectPath
                + "] is defined as an object in mapping ["
                + type
                + "] but this name is already used for a field in other types");
      }
    }
  }

  private void checkObjectsCompatibility(
      String type,
      Collection<ObjectMapper> objectMappers,
      Collection<FieldMapper> fieldMappers,
      boolean updateAllTypes) {
    assert Thread.holdsLock(this);

    for (ObjectMapper newObjectMapper : objectMappers) {
      ObjectMapper existingObjectMapper = fullPathObjectMappers.get(newObjectMapper.fullPath());
      if (existingObjectMapper != null) {
        // simulate a merge and ignore the result, we are just interested
        // in exceptions here
        existingObjectMapper.merge(newObjectMapper, updateAllTypes);
      }
    }
  }

  private void checkNestedFieldsLimit(Map<String, ObjectMapper> fullPathObjectMappers) {
    long allowedNestedFields = indexSettings.getValue(INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING);
    long actualNestedFields = 0;
    for (ObjectMapper objectMapper : fullPathObjectMappers.values()) {
      if (objectMapper.nested().isNested()) {
        actualNestedFields++;
      }
    }
    if (actualNestedFields > allowedNestedFields) {
      throw new IllegalArgumentException(
          "Limit of nested fields ["
              + allowedNestedFields
              + "] in index ["
              + index().getName()
              + "] has been exceeded");
    }
  }

  private void checkTotalFieldsLimit(long totalMappers) {
    long allowedTotalFields = indexSettings.getValue(INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING);
    if (allowedTotalFields < totalMappers) {
      throw new IllegalArgumentException(
          "Limit of total fields ["
              + allowedTotalFields
              + "] in index ["
              + index().getName()
              + "] has been exceeded");
    }
  }

  private void checkDepthLimit(Collection<String> objectPaths) {
    final long maxDepth = indexSettings.getValue(INDEX_MAPPING_DEPTH_LIMIT_SETTING);
    for (String objectPath : objectPaths) {
      checkDepthLimit(objectPath, maxDepth);
    }
  }

  private void checkDepthLimit(String objectPath, long maxDepth) {
    int numDots = 0;
    for (int i = 0; i < objectPath.length(); ++i) {
      if (objectPath.charAt(i) == '.') {
        numDots += 1;
      }
    }
    final int depth = numDots + 2;
    if (depth > maxDepth) {
      throw new IllegalArgumentException(
          "Limit of mapping depth ["
              + maxDepth
              + "] in index ["
              + index().getName()
              + "] has been exceeded due to object field ["
              + objectPath
              + "]");
    }
  }

  /**
   * We only allow upto 1 percolator field per index.
   *
   * <p>Reasoning here is that the PercolatorQueryCache only supports a single document having a
   * percolator query. Also specifying multiple queries per document feels like an anti pattern
   */
  private void checkPercolatorFieldLimit(Iterable<MappedFieldType> fieldTypes) {
    List<String> percolatorFieldTypes = new ArrayList<>();
    for (MappedFieldType fieldType : fieldTypes) {
      if (fieldType instanceof PercolatorFieldMapper.PercolatorFieldType) {
        percolatorFieldTypes.add(fieldType.name());
      }
    }
    if (percolatorFieldTypes.size() > 1) {
      throw new IllegalArgumentException(
          "Up to one percolator field type is allowed per index, "
              + "found the following percolator fields ["
              + percolatorFieldTypes
              + "]");
    }
  }

  public DocumentMapper parse(
      String mappingType, CompressedXContent mappingSource, boolean applyDefault)
      throws MapperParsingException {
    return documentParser.parse(
        mappingType, mappingSource, applyDefault ? defaultMappingSource : null);
  }

  public boolean hasMapping(String mappingType) {
    return mappers.containsKey(mappingType);
  }

  /**
   * Return the set of concrete types that have a mapping. NOTE: this does not return the default
   * mapping.
   */
  public Collection<String> types() {
    final Set<String> types = new HashSet<>(mappers.keySet());
    types.remove(DEFAULT_MAPPING);
    return Collections.unmodifiableSet(types);
  }

  /**
   * Return the {@link DocumentMapper} for the given type. By using the special {@value
   * #DEFAULT_MAPPING} type, you can get a {@link DocumentMapper} for the default mapping.
   */
  public DocumentMapper documentMapper(String type) {
    return mappers.get(type);
  }

  /**
   * Returns the document mapper created, including a mapping update if the type has been
   * dynamically created.
   */
  public DocumentMapperForType documentMapperWithAutoCreate(String type) {
    DocumentMapper mapper = mappers.get(type);
    if (mapper != null) {
      return new DocumentMapperForType(mapper, null);
    }
    if (!dynamic) {
      throw new TypeMissingException(
          index(), type, "trying to auto create mapping, but dynamic mapping is disabled");
    }
    mapper = parse(type, null, true);
    return new DocumentMapperForType(mapper, mapper.mapping());
  }

  /**
   * Returns the {@link MappedFieldType} for the give fullName.
   *
   * <p>If multiple types have fields with the same full name, the first is returned.
   */
  public MappedFieldType fullName(String fullName) {
    return fieldTypes.get(fullName);
  }

  /**
   * Returns all the fields that match the given pattern. If the pattern is prefixed with a type
   * then the fields will be returned with a type prefix.
   */
  public Collection<String> simpleMatchToIndexNames(String pattern) {
    if (Regex.isSimpleMatchPattern(pattern) == false) {
      // no wildcards
      return Collections.singletonList(pattern);
    }
    return fieldTypes.simpleMatchToFullName(pattern);
  }

  public ObjectMapper getObjectMapper(String name) {
    return fullPathObjectMappers.get(name);
  }

  /**
   * Given a type (eg. long, string, ...), return an anonymous field mapper that can be used for
   * search operations.
   */
  public MappedFieldType unmappedFieldType(String type) {
    if (type.equals("string")) {
      deprecationLogger.deprecated(
          "[unmapped_type:string] should be replaced with [unmapped_type:keyword]");
      type = "keyword";
    }
    MappedFieldType fieldType = unmappedFieldTypes.get(type);
    if (fieldType == null) {
      final Mapper.TypeParser.ParserContext parserContext =
          documentMapperParser().parserContext(type);
      Mapper.TypeParser typeParser = parserContext.typeParser(type);
      if (typeParser == null) {
        throw new IllegalArgumentException("No mapper found for type [" + type + "]");
      }
      final Mapper.Builder<?, ?> builder =
          typeParser.parse("__anonymous_" + type, emptyMap(), parserContext);
      final BuilderContext builderContext =
          new BuilderContext(indexSettings.getSettings(), new ContentPath(1));
      fieldType = ((FieldMapper) builder.build(builderContext)).fieldType();

      // There is no need to synchronize writes here. In the case of concurrent access, we could
      // just
      // compute some mappers several times, which is not a big deal
      Map<String, MappedFieldType> newUnmappedFieldTypes = new HashMap<>();
      newUnmappedFieldTypes.putAll(unmappedFieldTypes);
      newUnmappedFieldTypes.put(type, fieldType);
      unmappedFieldTypes = unmodifiableMap(newUnmappedFieldTypes);
    }
    return fieldType;
  }

  public Analyzer indexAnalyzer() {
    return this.indexAnalyzer;
  }

  public Analyzer searchAnalyzer() {
    return this.searchAnalyzer;
  }

  public Analyzer searchQuoteAnalyzer() {
    return this.searchQuoteAnalyzer;
  }

  public Set<String> getParentTypes() {
    return parentTypes;
  }

  /** @return Whether a field is a metadata field. */
  public static boolean isMetadataField(String fieldName) {
    return META_FIELDS.contains(fieldName);
  }

  public static String[] getAllMetaFields() {
    return META_FIELDS.toArray(String.class);
  }

  /** An analyzer wrapper that can lookup fields within the index mappings */
  final class MapperAnalyzerWrapper extends DelegatingAnalyzerWrapper {

    private final Analyzer defaultAnalyzer;
    private final Function<MappedFieldType, Analyzer> extractAnalyzer;

    MapperAnalyzerWrapper(
        Analyzer defaultAnalyzer, Function<MappedFieldType, Analyzer> extractAnalyzer) {
      super(Analyzer.PER_FIELD_REUSE_STRATEGY);
      this.defaultAnalyzer = defaultAnalyzer;
      this.extractAnalyzer = extractAnalyzer;
    }

    @Override
    protected Analyzer getWrappedAnalyzer(String fieldName) {
      MappedFieldType fieldType = fullName(fieldName);
      if (fieldType != null) {
        Analyzer analyzer = extractAnalyzer.apply(fieldType);
        if (analyzer != null) {
          return analyzer;
        }
      }
      return defaultAnalyzer;
    }
  }
}
 public void add(double lat, double lon) {
   points.add(new GeoPoint(lat, lon));
 }
 public CustomGeoPointDocValuesField(String name, double lat, double lon) {
   super(name);
   points = new ObjectHashSet<>(2);
   points.add(new GeoPoint(lat, lon));
 }
public class MapperService extends AbstractIndexComponent implements Closeable {

  public static final String DEFAULT_MAPPING = "_default_";
  private static ObjectHashSet<String> META_FIELDS =
      ObjectHashSet.from(
          "_uid",
          "_id",
          "_type",
          "_all",
          "_parent",
          "_routing",
          "_index",
          "_size",
          "_timestamp",
          "_ttl");

  private static final Function<MappedFieldType, Analyzer> INDEX_ANALYZER_EXTRACTOR =
      new Function<MappedFieldType, Analyzer>() {
        public Analyzer apply(MappedFieldType fieldType) {
          return fieldType.indexAnalyzer();
        }
      };
  private static final Function<MappedFieldType, Analyzer> SEARCH_ANALYZER_EXTRACTOR =
      new Function<MappedFieldType, Analyzer>() {
        public Analyzer apply(MappedFieldType fieldType) {
          return fieldType.searchAnalyzer();
        }
      };
  private static final Function<MappedFieldType, Analyzer> SEARCH_QUOTE_ANALYZER_EXTRACTOR =
      new Function<MappedFieldType, Analyzer>() {
        public Analyzer apply(MappedFieldType fieldType) {
          return fieldType.searchQuoteAnalyzer();
        }
      };

  private final AnalysisService analysisService;

  /** Will create types automatically if they do not exists in the mapping definition yet */
  private final boolean dynamic;

  private volatile String defaultMappingSource;
  private volatile String defaultPercolatorMappingSource;

  private volatile Map<String, DocumentMapper> mappers = ImmutableMap.of();

  // A lock for mappings: modifications (put mapping) need to be performed
  // under the write lock and read operations (document parsing) need to be
  // performed under the read lock
  final ReentrantReadWriteLock mappingLock = new ReentrantReadWriteLock();
  private final ReleasableLock mappingWriteLock = new ReleasableLock(mappingLock.writeLock());

  private volatile FieldTypeLookup fieldTypes;
  private volatile ImmutableOpenMap<String, ObjectMapper> fullPathObjectMappers =
      ImmutableOpenMap.of();
  private boolean hasNested = false; // updated dynamically to true when a nested object is added

  private final DocumentMapperParser documentParser;

  private final MapperAnalyzerWrapper indexAnalyzer;
  private final MapperAnalyzerWrapper searchAnalyzer;
  private final MapperAnalyzerWrapper searchQuoteAnalyzer;

  private final List<DocumentTypeListener> typeListeners = new CopyOnWriteArrayList<>();

  private volatile ImmutableMap<String, MappedFieldType> unmappedFieldTypes = ImmutableMap.of();

  private volatile ImmutableSet<String> parentTypes = ImmutableSet.of();

  @Inject
  public MapperService(
      Index index,
      @IndexSettings Settings indexSettings,
      AnalysisService analysisService,
      SimilarityLookupService similarityLookupService,
      ScriptService scriptService) {
    super(index, indexSettings);
    this.analysisService = analysisService;
    this.fieldTypes = new FieldTypeLookup();
    this.documentParser =
        new DocumentMapperParser(
            indexSettings, this, analysisService, similarityLookupService, scriptService);
    this.indexAnalyzer =
        new MapperAnalyzerWrapper(analysisService.defaultIndexAnalyzer(), INDEX_ANALYZER_EXTRACTOR);
    this.searchAnalyzer =
        new MapperAnalyzerWrapper(
            analysisService.defaultSearchAnalyzer(), SEARCH_ANALYZER_EXTRACTOR);
    this.searchQuoteAnalyzer =
        new MapperAnalyzerWrapper(
            analysisService.defaultSearchQuoteAnalyzer(), SEARCH_QUOTE_ANALYZER_EXTRACTOR);

    this.dynamic = indexSettings.getAsBoolean("index.mapper.dynamic", true);
    defaultPercolatorMappingSource =
        "{\n"
            + "\"_default_\":{\n"
            + "\"properties\" : {\n"
            + "\"query\" : {\n"
            + "\"type\" : \"object\",\n"
            + "\"enabled\" : false\n"
            + "}\n"
            + "}\n"
            + "}\n"
            + "}";
    if (index.getName().equals(ScriptService.SCRIPT_INDEX)) {
      defaultMappingSource =
          "{"
              + "\"_default_\": {"
              + "\"properties\": {"
              + "\"script\": { \"enabled\": false },"
              + "\"template\": { \"enabled\": false }"
              + "}"
              + "}"
              + "}";
    } else {
      defaultMappingSource = "{\"_default_\":{}}";
    }

    if (logger.isTraceEnabled()) {
      logger.trace(
          "using dynamic[{}], default mapping source[{}], default percolator mapping source[{}]",
          dynamic,
          defaultMappingSource,
          defaultPercolatorMappingSource);
    } else if (logger.isDebugEnabled()) {
      logger.debug("using dynamic[{}]", dynamic);
    }
  }

  public void close() {
    for (DocumentMapper documentMapper : mappers.values()) {
      documentMapper.close();
    }
  }

  public boolean hasNested() {
    return this.hasNested;
  }

  /**
   * returns an immutable iterator over current document mappers.
   *
   * @param includingDefaultMapping indicates whether the iterator should contain the {@link
   *     #DEFAULT_MAPPING} document mapper. As is this not really an active type, you would
   *     typically set this to false
   */
  public Iterable<DocumentMapper> docMappers(final boolean includingDefaultMapping) {
    return new Iterable<DocumentMapper>() {
      @Override
      public Iterator<DocumentMapper> iterator() {
        final Iterator<DocumentMapper> iterator;
        if (includingDefaultMapping) {
          iterator = mappers.values().iterator();
        } else {
          iterator = Iterators.filter(mappers.values().iterator(), NOT_A_DEFAULT_DOC_MAPPER);
        }
        return Iterators.unmodifiableIterator(iterator);
      }
    };
  }

  private static final Predicate<DocumentMapper> NOT_A_DEFAULT_DOC_MAPPER =
      new Predicate<DocumentMapper>() {
        @Override
        public boolean apply(DocumentMapper input) {
          return !DEFAULT_MAPPING.equals(input.type());
        }
      };

  public AnalysisService analysisService() {
    return this.analysisService;
  }

  public DocumentMapperParser documentMapperParser() {
    return this.documentParser;
  }

  public void addTypeListener(DocumentTypeListener listener) {
    typeListeners.add(listener);
  }

  public void removeTypeListener(DocumentTypeListener listener) {
    typeListeners.remove(listener);
  }

  public DocumentMapper merge(
      String type, CompressedXContent mappingSource, boolean applyDefault, boolean updateAllTypes) {
    if (DEFAULT_MAPPING.equals(type)) {
      // verify we can parse it
      DocumentMapper mapper = documentParser.parseCompressed(type, mappingSource);
      // still add it as a document mapper so we have it registered and, for example, persisted back
      // into
      // the cluster meta data if needed, or checked for existence
      try (ReleasableLock lock = mappingWriteLock.acquire()) {
        mappers = newMapBuilder(mappers).put(type, mapper).map();
      }
      try {
        defaultMappingSource = mappingSource.string();
      } catch (IOException e) {
        throw new ElasticsearchGenerationException("failed to un-compress", e);
      }
      return mapper;
    } else {
      return merge(parse(type, mappingSource, applyDefault), updateAllTypes);
    }
  }

  // never expose this to the outside world, we need to reparse the doc mapper so we get fresh
  // instances of field mappers to properly remove existing doc mapper
  private DocumentMapper merge(DocumentMapper mapper, boolean updateAllTypes) {
    try (ReleasableLock lock = mappingWriteLock.acquire()) {
      if (mapper.type().length() == 0) {
        throw new InvalidTypeNameException("mapping type name is empty");
      }
      if (Version.indexCreated(indexSettings).onOrAfter(Version.V_2_0_0_beta1)
          && mapper.type().length() > 255) {
        throw new InvalidTypeNameException(
            "mapping type name ["
                + mapper.type()
                + "] is too long; limit is length 255 but was ["
                + mapper.type().length()
                + "]");
      }
      if (mapper.type().charAt(0) == '_') {
        throw new InvalidTypeNameException(
            "mapping type name [" + mapper.type() + "] can't start with '_'");
      }
      if (mapper.type().contains("#")) {
        throw new InvalidTypeNameException(
            "mapping type name [" + mapper.type() + "] should not include '#' in it");
      }
      if (mapper.type().contains(",")) {
        throw new InvalidTypeNameException(
            "mapping type name [" + mapper.type() + "] should not include ',' in it");
      }
      if (Version.indexCreated(indexSettings).onOrAfter(Version.V_2_0_0_beta1)
          && mapper.type().equals(mapper.parentFieldMapper().type())) {
        throw new IllegalArgumentException(
            "The [_parent.type] option can't point to the same type");
      }
      if (typeNameStartsWithIllegalDot(mapper)) {
        if (Version.indexCreated(indexSettings).onOrAfter(Version.V_2_0_0_beta1)) {
          throw new IllegalArgumentException(
              "mapping type name [" + mapper.type() + "] must not start with a '.'");
        } else {
          logger.warn(
              "Type [{}] starts with a '.', it is recommended not to start a type name with a '.'",
              mapper.type());
        }
      }
      // we can add new field/object mappers while the old ones are there
      // since we get new instances of those, and when we remove, we remove
      // by instance equality
      DocumentMapper oldMapper = mappers.get(mapper.type());

      if (oldMapper != null) {
        MergeResult result = oldMapper.merge(mapper.mapping(), false, updateAllTypes);
        if (result.hasConflicts()) {
          // TODO: What should we do???
          if (logger.isDebugEnabled()) {
            logger.debug(
                "merging mapping for type [{}] resulted in conflicts: [{}]",
                mapper.type(),
                Arrays.toString(result.buildConflicts()));
          }
        }
        return oldMapper;
      } else {
        List<ObjectMapper> newObjectMappers = new ArrayList<>();
        List<FieldMapper> newFieldMappers = new ArrayList<>();
        for (MetadataFieldMapper metadataMapper : mapper.mapping().metadataMappers) {
          newFieldMappers.add(metadataMapper);
        }
        MapperUtils.collect(mapper.mapping().root, newObjectMappers, newFieldMappers);
        checkNewMappersCompatibility(newObjectMappers, newFieldMappers, updateAllTypes);
        addMappers(newObjectMappers, newFieldMappers);

        for (DocumentTypeListener typeListener : typeListeners) {
          typeListener.beforeCreate(mapper);
        }
        mappers = newMapBuilder(mappers).put(mapper.type(), mapper).map();
        if (mapper.parentFieldMapper().active()) {
          ImmutableSet.Builder<String> parentTypesCopy = ImmutableSet.builder();
          parentTypesCopy.addAll(parentTypes);
          parentTypesCopy.add(mapper.parentFieldMapper().type());
          parentTypes = parentTypesCopy.build();
        }
        assert assertSerialization(mapper);
        return mapper;
      }
    }
  }

  private boolean typeNameStartsWithIllegalDot(DocumentMapper mapper) {
    return mapper.type().startsWith(".") && !PercolatorService.TYPE_NAME.equals(mapper.type());
  }

  private boolean assertSerialization(DocumentMapper mapper) {
    // capture the source now, it may change due to concurrent parsing
    final CompressedXContent mappingSource = mapper.mappingSource();
    DocumentMapper newMapper = parse(mapper.type(), mappingSource, false);

    if (newMapper.mappingSource().equals(mappingSource) == false) {
      throw new IllegalStateException(
          "DocumentMapper serialization result is different from source. \n--> Source ["
              + mappingSource
              + "]\n--> Result ["
              + newMapper.mappingSource()
              + "]");
    }
    return true;
  }

  protected void checkNewMappersCompatibility(
      Collection<ObjectMapper> newObjectMappers,
      Collection<FieldMapper> newFieldMappers,
      boolean updateAllTypes) {
    assert mappingLock.isWriteLockedByCurrentThread();
    for (ObjectMapper newObjectMapper : newObjectMappers) {
      ObjectMapper existingObjectMapper = fullPathObjectMappers.get(newObjectMapper.fullPath());
      if (existingObjectMapper != null) {
        MergeResult result = new MergeResult(true, updateAllTypes);
        existingObjectMapper.merge(newObjectMapper, result);
        if (result.hasConflicts()) {
          throw new IllegalArgumentException(
              "Mapper for ["
                  + newObjectMapper.fullPath()
                  + "] conflicts with existing mapping in other types"
                  + Arrays.toString(result.buildConflicts()));
        }
      }
    }
    fieldTypes.checkCompatibility(newFieldMappers, updateAllTypes);
  }

  protected void addMappers(
      Collection<ObjectMapper> objectMappers, Collection<FieldMapper> fieldMappers) {
    assert mappingLock.isWriteLockedByCurrentThread();
    ImmutableOpenMap.Builder<String, ObjectMapper> fullPathObjectMappers =
        ImmutableOpenMap.builder(this.fullPathObjectMappers);
    for (ObjectMapper objectMapper : objectMappers) {
      fullPathObjectMappers.put(objectMapper.fullPath(), objectMapper);
      if (objectMapper.nested().isNested()) {
        hasNested = true;
      }
    }
    this.fullPathObjectMappers = fullPathObjectMappers.build();
    this.fieldTypes = this.fieldTypes.copyAndAddAll(fieldMappers);
  }

  public DocumentMapper parse(
      String mappingType, CompressedXContent mappingSource, boolean applyDefault)
      throws MapperParsingException {
    String defaultMappingSource;
    if (PercolatorService.TYPE_NAME.equals(mappingType)) {
      defaultMappingSource = this.defaultPercolatorMappingSource;
    } else {
      defaultMappingSource = this.defaultMappingSource;
    }
    return documentParser.parseCompressed(
        mappingType, mappingSource, applyDefault ? defaultMappingSource : null);
  }

  public boolean hasMapping(String mappingType) {
    return mappers.containsKey(mappingType);
  }

  public Collection<String> types() {
    return mappers.keySet();
  }

  public DocumentMapper documentMapper(String type) {
    return mappers.get(type);
  }

  /**
   * Returns the document mapper created, including a mapping update if the type has been
   * dynamically created.
   */
  public DocumentMapperForType documentMapperWithAutoCreate(String type) {
    DocumentMapper mapper = mappers.get(type);
    if (mapper != null) {
      return new DocumentMapperForType(mapper, null);
    }
    if (!dynamic) {
      throw new TypeMissingException(
          index, type, "trying to auto create mapping, but dynamic mapping is disabled");
    }
    mapper = parse(type, null, true);
    return new DocumentMapperForType(mapper, mapper.mapping());
  }

  /**
   * A filter for search. If a filter is required, will return it, otherwise, will return
   * <tt>null</tt>.
   */
  @Nullable
  public Query searchFilter(String... types) {
    boolean filterPercolateType = hasMapping(PercolatorService.TYPE_NAME);
    if (types != null && filterPercolateType) {
      for (String type : types) {
        if (PercolatorService.TYPE_NAME.equals(type)) {
          filterPercolateType = false;
          break;
        }
      }
    }
    Query percolatorType = null;
    if (filterPercolateType) {
      percolatorType = documentMapper(PercolatorService.TYPE_NAME).typeFilter();
    }

    if (types == null || types.length == 0) {
      if (hasNested && filterPercolateType) {
        BooleanQuery bq = new BooleanQuery();
        bq.add(percolatorType, Occur.MUST_NOT);
        bq.add(Queries.newNonNestedFilter(), Occur.MUST);
        return new ConstantScoreQuery(bq);
      } else if (hasNested) {
        return Queries.newNonNestedFilter();
      } else if (filterPercolateType) {
        return new ConstantScoreQuery(Queries.not(percolatorType));
      } else {
        return null;
      }
    }
    // if we filter by types, we don't need to filter by non nested docs
    // since they have different types (starting with __)
    if (types.length == 1) {
      DocumentMapper docMapper = documentMapper(types[0]);
      Query filter =
          docMapper != null
              ? docMapper.typeFilter()
              : new TermQuery(new Term(TypeFieldMapper.NAME, types[0]));
      if (filterPercolateType) {
        BooleanQuery bq = new BooleanQuery();
        bq.add(percolatorType, Occur.MUST_NOT);
        bq.add(filter, Occur.MUST);
        return new ConstantScoreQuery(bq);
      } else {
        return filter;
      }
    }
    // see if we can use terms filter
    boolean useTermsFilter = true;
    for (String type : types) {
      DocumentMapper docMapper = documentMapper(type);
      if (docMapper == null) {
        useTermsFilter = false;
        break;
      }
      if (docMapper.typeMapper().fieldType().indexOptions() == IndexOptions.NONE) {
        useTermsFilter = false;
        break;
      }
    }

    // We only use terms filter is there is a type filter, this means we don't need to check for
    // hasNested here
    if (useTermsFilter) {
      BytesRef[] typesBytes = new BytesRef[types.length];
      for (int i = 0; i < typesBytes.length; i++) {
        typesBytes[i] = new BytesRef(types[i]);
      }
      TermsQuery termsFilter = new TermsQuery(TypeFieldMapper.NAME, typesBytes);
      if (filterPercolateType) {
        BooleanQuery bq = new BooleanQuery();
        bq.add(percolatorType, Occur.MUST_NOT);
        bq.add(termsFilter, Occur.MUST);
        return new ConstantScoreQuery(bq);
      } else {
        return termsFilter;
      }
    } else {
      // Current bool filter requires that at least one should clause matches, even with a must
      // clause.
      BooleanQuery bool = new BooleanQuery();
      for (String type : types) {
        DocumentMapper docMapper = documentMapper(type);
        if (docMapper == null) {
          bool.add(new TermQuery(new Term(TypeFieldMapper.NAME, type)), BooleanClause.Occur.SHOULD);
        } else {
          bool.add(docMapper.typeFilter(), BooleanClause.Occur.SHOULD);
        }
      }
      if (filterPercolateType) {
        bool.add(percolatorType, BooleanClause.Occur.MUST_NOT);
      }
      if (hasNested) {
        bool.add(Queries.newNonNestedFilter(), BooleanClause.Occur.MUST);
      }

      return new ConstantScoreQuery(bool);
    }
  }

  /**
   * Returns an {@link MappedFieldType} which has the given index name.
   *
   * <p>If multiple types have fields with the same index name, the first is returned.
   */
  public MappedFieldType indexName(String indexName) {
    return fieldTypes.getByIndexName(indexName);
  }

  /**
   * Returns the {@link MappedFieldType} for the give fullName.
   *
   * <p>If multiple types have fields with the same full name, the first is returned.
   */
  public MappedFieldType fullName(String fullName) {
    return fieldTypes.get(fullName);
  }

  /**
   * Returns all the fields that match the given pattern. If the pattern is prefixed with a type
   * then the fields will be returned with a type prefix.
   */
  public Collection<String> simpleMatchToIndexNames(String pattern) {
    if (Regex.isSimpleMatchPattern(pattern) == false) {
      // no wildcards
      return Collections.singletonList(pattern);
    }
    return fieldTypes.simpleMatchToIndexNames(pattern);
  }

  // TODO: remove this since the underlying index names are now the same across all types
  public Collection<String> simpleMatchToIndexNames(String pattern, @Nullable String[] types) {
    return simpleMatchToIndexNames(pattern);
  }

  // TODO: remove types param, since the object mapper must be the same across all types
  public ObjectMapper getObjectMapper(String name, @Nullable String[] types) {
    return fullPathObjectMappers.get(name);
  }

  public MappedFieldType smartNameFieldType(String smartName) {
    MappedFieldType fieldType = fullName(smartName);
    if (fieldType != null) {
      return fieldType;
    }
    return indexName(smartName);
  }

  // TODO: remove this since the underlying index names are now the same across all types
  public MappedFieldType smartNameFieldType(String smartName, @Nullable String[] types) {
    return smartNameFieldType(smartName);
  }

  /**
   * Given a type (eg. long, string, ...), return an anonymous field mapper that can be used for
   * search operations.
   */
  public MappedFieldType unmappedFieldType(String type) {
    final ImmutableMap<String, MappedFieldType> unmappedFieldMappers = this.unmappedFieldTypes;
    MappedFieldType fieldType = unmappedFieldMappers.get(type);
    if (fieldType == null) {
      final Mapper.TypeParser.ParserContext parserContext = documentMapperParser().parserContext();
      Mapper.TypeParser typeParser = parserContext.typeParser(type);
      if (typeParser == null) {
        throw new IllegalArgumentException("No mapper found for type [" + type + "]");
      }
      final Mapper.Builder<?, ?> builder =
          typeParser.parse("__anonymous_" + type, ImmutableMap.<String, Object>of(), parserContext);
      final BuilderContext builderContext = new BuilderContext(indexSettings, new ContentPath(1));
      fieldType = ((FieldMapper) builder.build(builderContext)).fieldType();

      // There is no need to synchronize writes here. In the case of concurrent access, we could
      // just
      // compute some mappers several times, which is not a big deal
      this.unmappedFieldTypes =
          ImmutableMap.<String, MappedFieldType>builder()
              .putAll(unmappedFieldMappers)
              .put(type, fieldType)
              .build();
    }
    return fieldType;
  }

  public Analyzer indexAnalyzer() {
    return this.indexAnalyzer;
  }

  public Analyzer searchAnalyzer() {
    return this.searchAnalyzer;
  }

  public Analyzer searchQuoteAnalyzer() {
    return this.searchQuoteAnalyzer;
  }

  /** Resolves the closest inherited {@link ObjectMapper} that is nested. */
  public ObjectMapper resolveClosestNestedObjectMapper(String fieldName) {
    int indexOf = fieldName.lastIndexOf('.');
    if (indexOf == -1) {
      return null;
    } else {
      do {
        String objectPath = fieldName.substring(0, indexOf);
        ObjectMapper objectMapper = fullPathObjectMappers.get(objectPath);
        if (objectMapper == null) {
          indexOf = objectPath.lastIndexOf('.');
          continue;
        }

        if (objectMapper.nested().isNested()) {
          return objectMapper;
        }

        indexOf = objectPath.lastIndexOf('.');
      } while (indexOf != -1);
    }

    return null;
  }

  public ImmutableSet<String> getParentTypes() {
    return parentTypes;
  }

  /** @return Whether a field is a metadata field. */
  public static boolean isMetadataField(String fieldName) {
    return META_FIELDS.contains(fieldName);
  }

  public static String[] getAllMetaFields() {
    return META_FIELDS.toArray(String.class);
  }

  /** An analyzer wrapper that can lookup fields within the index mappings */
  final class MapperAnalyzerWrapper extends DelegatingAnalyzerWrapper {

    private final Analyzer defaultAnalyzer;
    private final Function<MappedFieldType, Analyzer> extractAnalyzer;

    MapperAnalyzerWrapper(
        Analyzer defaultAnalyzer, Function<MappedFieldType, Analyzer> extractAnalyzer) {
      super(Analyzer.PER_FIELD_REUSE_STRATEGY);
      this.defaultAnalyzer = defaultAnalyzer;
      this.extractAnalyzer = extractAnalyzer;
    }

    @Override
    protected Analyzer getWrappedAnalyzer(String fieldName) {
      MappedFieldType fieldType = smartNameFieldType(fieldName);
      if (fieldType != null) {
        Analyzer analyzer = extractAnalyzer.apply(fieldType);
        if (analyzer != null) {
          return analyzer;
        }
      }
      return defaultAnalyzer;
    }
  }
}