/**
  * Filters out list of available indices based on the list of selected indices.
  *
  * @param availableIndices list of available indices
  * @param selectedIndices list of selected indices
  * @param indicesOptions ignore indices flag
  * @return filtered out indices
  */
 public static List<String> filterIndices(
     List<String> availableIndices, String[] selectedIndices, IndicesOptions indicesOptions) {
   if (selectedIndices == null || selectedIndices.length == 0) {
     return availableIndices;
   }
   Set<String> result = null;
   for (int i = 0; i < selectedIndices.length; i++) {
     String indexOrPattern = selectedIndices[i];
     boolean add = true;
     if (!indexOrPattern.isEmpty()) {
       if (availableIndices.contains(indexOrPattern)) {
         if (result != null) {
           result.add(indexOrPattern);
         }
         continue;
       }
       if (indexOrPattern.charAt(0) == '+') {
         add = true;
         indexOrPattern = indexOrPattern.substring(1);
         // if its the first, add empty set
         if (i == 0) {
           result = new HashSet<>();
         }
       } else if (indexOrPattern.charAt(0) == '-') {
         // if its the first, fill it with all the indices...
         if (i == 0) {
           result = new HashSet<>(availableIndices);
         }
         add = false;
         indexOrPattern = indexOrPattern.substring(1);
       }
     }
     if (indexOrPattern.isEmpty() || !Regex.isSimpleMatchPattern(indexOrPattern)) {
       if (!availableIndices.contains(indexOrPattern)) {
         if (!indicesOptions.ignoreUnavailable()) {
           throw new IndexMissingException(new Index(indexOrPattern));
         } else {
           if (result == null) {
             // add all the previous ones...
             result = new HashSet<>();
             result.addAll(availableIndices.subList(0, i));
           }
         }
       } else {
         if (result != null) {
           if (add) {
             result.add(indexOrPattern);
           } else {
             result.remove(indexOrPattern);
           }
         }
       }
       continue;
     }
     if (result == null) {
       // add all the previous ones...
       result = new HashSet<>();
       result.addAll(availableIndices.subList(0, i));
     }
     boolean found = false;
     for (String index : availableIndices) {
       if (Regex.simpleMatch(indexOrPattern, index)) {
         found = true;
         if (add) {
           result.add(index);
         } else {
           result.remove(index);
         }
       }
     }
     if (!found && !indicesOptions.allowNoIndices()) {
       throw new IndexMissingException(new Index(indexOrPattern));
     }
   }
   if (result == null) {
     return ImmutableList.copyOf(selectedIndices);
   }
   return ImmutableList.copyOf(result);
 }
 private static void assertOptionsMatch(IndicesOptions in, IndicesOptions out) {
   assertThat(in.ignoreUnavailable(), equalTo(out.ignoreUnavailable()));
   assertThat(in.expandWildcardsClosed(), equalTo(out.expandWildcardsClosed()));
   assertThat(in.expandWildcardsOpen(), equalTo(out.expandWildcardsOpen()));
   assertThat(in.allowNoIndices(), equalTo(out.allowNoIndices()));
 }
  public MultiSearchRequest add(
      BytesReference data,
      boolean contentUnsafe,
      @Nullable String[] indices,
      @Nullable String[] types,
      @Nullable String searchType,
      @Nullable String routing,
      IndicesOptions indicesOptions,
      boolean allowExplicitIndex)
      throws Exception {
    XContent xContent = XContentFactory.xContent(data);
    int from = 0;
    int length = data.length();
    byte marker = xContent.streamSeparator();
    while (true) {
      int nextMarker = findNextMarker(marker, from, data, length);
      if (nextMarker == -1) {
        break;
      }
      // support first line with \n
      if (nextMarker == 0) {
        from = nextMarker + 1;
        continue;
      }

      SearchRequest searchRequest = new SearchRequest();
      if (indices != null) {
        searchRequest.indices(indices);
      }
      if (indicesOptions != null) {
        searchRequest.indicesOptions(indicesOptions);
      }
      if (types != null && types.length > 0) {
        searchRequest.types(types);
      }
      if (routing != null) {
        searchRequest.routing(routing);
      }
      searchRequest.searchType(searchType);

      IndicesOptions defaultOptions = IndicesOptions.strictExpandOpenAndForbidClosed();
      boolean ignoreUnavailable = defaultOptions.ignoreUnavailable();
      boolean allowNoIndices = defaultOptions.allowNoIndices();
      boolean expandWildcardsOpen = defaultOptions.expandWildcardsOpen();
      boolean expandWildcardsClosed = defaultOptions.expandWildcardsClosed();

      // now parse the action
      if (nextMarker - from > 0) {
        try (XContentParser parser = xContent.createParser(data.slice(from, nextMarker - from))) {
          // Move to START_OBJECT, if token is null, its an empty data
          XContentParser.Token token = parser.nextToken();
          if (token != null) {
            assert token == XContentParser.Token.START_OBJECT;
            String currentFieldName = null;
            while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
              if (token == XContentParser.Token.FIELD_NAME) {
                currentFieldName = parser.currentName();
              } else if (token.isValue()) {
                if ("index".equals(currentFieldName) || "indices".equals(currentFieldName)) {
                  if (!allowExplicitIndex) {
                    throw new ElasticsearchIllegalArgumentException(
                        "explicit index in multi search is not allowed");
                  }
                  searchRequest.indices(Strings.splitStringByCommaToArray(parser.text()));
                } else if ("type".equals(currentFieldName) || "types".equals(currentFieldName)) {
                  searchRequest.types(Strings.splitStringByCommaToArray(parser.text()));
                } else if ("search_type".equals(currentFieldName)
                    || "searchType".equals(currentFieldName)) {
                  searchRequest.searchType(parser.text());
                } else if ("query_cache".equals(currentFieldName)
                    || "queryCache".equals(currentFieldName)) {
                  searchRequest.queryCache(parser.booleanValue());
                } else if ("preference".equals(currentFieldName)) {
                  searchRequest.preference(parser.text());
                } else if ("routing".equals(currentFieldName)) {
                  searchRequest.routing(parser.text());
                } else if ("ignore_unavailable".equals(currentFieldName)
                    || "ignoreUnavailable".equals(currentFieldName)) {
                  ignoreUnavailable = parser.booleanValue();
                } else if ("allow_no_indices".equals(currentFieldName)
                    || "allowNoIndices".equals(currentFieldName)) {
                  allowNoIndices = parser.booleanValue();
                } else if ("expand_wildcards".equals(currentFieldName)
                    || "expandWildcards".equals(currentFieldName)) {
                  String[] wildcards = Strings.splitStringByCommaToArray(parser.text());
                  for (String wildcard : wildcards) {
                    if ("open".equals(wildcard)) {
                      expandWildcardsOpen = true;
                    } else if ("closed".equals(wildcard)) {
                      expandWildcardsClosed = true;
                    } else {
                      throw new ElasticsearchIllegalArgumentException(
                          "No valid expand wildcard value [" + wildcard + "]");
                    }
                  }
                }
              } else if (token == XContentParser.Token.START_ARRAY) {
                if ("index".equals(currentFieldName) || "indices".equals(currentFieldName)) {
                  if (!allowExplicitIndex) {
                    throw new ElasticsearchIllegalArgumentException(
                        "explicit index in multi search is not allowed");
                  }
                  searchRequest.indices(parseArray(parser));
                } else if ("type".equals(currentFieldName) || "types".equals(currentFieldName)) {
                  searchRequest.types(parseArray(parser));
                } else if ("expand_wildcards".equals(currentFieldName)
                    || "expandWildcards".equals(currentFieldName)) {
                  String[] wildcards = parseArray(parser);
                  for (String wildcard : wildcards) {
                    if ("open".equals(wildcard)) {
                      expandWildcardsOpen = true;
                    } else if ("closed".equals(wildcard)) {
                      expandWildcardsClosed = true;
                    } else {
                      throw new ElasticsearchIllegalArgumentException(
                          "No valid expand wildcard value [" + wildcard + "]");
                    }
                  }
                } else {
                  throw new ElasticsearchParseException(
                      currentFieldName + " doesn't support arrays");
                }
              }
            }
          }
        }
      }
      searchRequest.indicesOptions(
          IndicesOptions.fromOptions(
              ignoreUnavailable,
              allowNoIndices,
              expandWildcardsOpen,
              expandWildcardsClosed,
              defaultOptions));

      // move pointers
      from = nextMarker + 1;
      // now for the body
      nextMarker = findNextMarker(marker, from, data, length);
      if (nextMarker == -1) {
        break;
      }

      searchRequest.source(data.slice(from, nextMarker - from), contentUnsafe);
      // move pointers
      from = nextMarker + 1;

      add(searchRequest);
    }

    return this;
  }