@Override
 public void readFrom(StreamInput in) throws IOException {
   super.readFrom(in);
   percolatorTypeId = in.readByte();
   requestedSize = in.readVInt();
   count = in.readVLong();
   matches = new BytesRef[in.readVInt()];
   for (int i = 0; i < matches.length; i++) {
     matches[i] = in.readBytesRef();
   }
   scores = new float[in.readVInt()];
   for (int i = 0; i < scores.length; i++) {
     scores[i] = in.readFloat();
   }
   int size = in.readVInt();
   for (int i = 0; i < size; i++) {
     int mSize = in.readVInt();
     Map<String, HighlightField> fields = new HashMap<>();
     for (int j = 0; j < mSize; j++) {
       fields.put(in.readString(), HighlightField.readHighlightField(in));
     }
     hls.add(fields);
   }
   aggregations = InternalAggregations.readOptionalAggregations(in);
   if (in.readBoolean()) {
     int pipelineAggregatorsSize = in.readVInt();
     List<SiblingPipelineAggregator> pipelineAggregators =
         new ArrayList<>(pipelineAggregatorsSize);
     for (int i = 0; i < pipelineAggregatorsSize; i++) {
       BytesReference type = in.readBytesReference();
       PipelineAggregator pipelineAggregator =
           PipelineAggregatorStreams.stream(type).readResult(in);
       pipelineAggregators.add((SiblingPipelineAggregator) pipelineAggregator);
     }
     this.pipelineAggregators = pipelineAggregators;
   }
 }
  public void writeTo(StreamOutput out, InternalSearchHits.StreamContext context)
      throws IOException {
    out.writeFloat(score);
    out.writeText(id);
    out.writeText(type);
    out.writeOptionalStreamable(nestedIdentity);
    out.writeLong(version);
    out.writeBytesReference(source);
    if (explanation == null) {
      out.writeBoolean(false);
    } else {
      out.writeBoolean(true);
      writeExplanation(out, explanation);
    }
    if (fields == null) {
      out.writeVInt(0);
    } else {
      out.writeVInt(fields.size());
      for (SearchHitField hitField : fields().values()) {
        hitField.writeTo(out);
      }
    }
    if (highlightFields == null) {
      out.writeVInt(0);
    } else {
      out.writeVInt(highlightFields.size());
      for (HighlightField highlightField : highlightFields.values()) {
        highlightField.writeTo(out);
      }
    }

    if (sortValues.length == 0) {
      out.writeVInt(0);
    } else {
      out.writeVInt(sortValues.length);
      for (Object sortValue : sortValues) {
        if (sortValue == null) {
          out.writeByte((byte) 0);
        } else {
          Class type = sortValue.getClass();
          if (type == String.class) {
            out.writeByte((byte) 1);
            out.writeString((String) sortValue);
          } else if (type == Integer.class) {
            out.writeByte((byte) 2);
            out.writeInt((Integer) sortValue);
          } else if (type == Long.class) {
            out.writeByte((byte) 3);
            out.writeLong((Long) sortValue);
          } else if (type == Float.class) {
            out.writeByte((byte) 4);
            out.writeFloat((Float) sortValue);
          } else if (type == Double.class) {
            out.writeByte((byte) 5);
            out.writeDouble((Double) sortValue);
          } else if (type == Byte.class) {
            out.writeByte((byte) 6);
            out.writeByte((Byte) sortValue);
          } else if (type == Short.class) {
            out.writeByte((byte) 7);
            out.writeShort((Short) sortValue);
          } else if (type == Boolean.class) {
            out.writeByte((byte) 8);
            out.writeBoolean((Boolean) sortValue);
          } else if (sortValue instanceof Text) {
            out.writeByte((byte) 9);
            out.writeText((Text) sortValue);
          } else {
            throw new IOException("Can't handle sort field value of type [" + type + "]");
          }
        }
      }
    }

    if (matchedQueries.length == 0) {
      out.writeVInt(0);
    } else {
      out.writeVInt(matchedQueries.length);
      for (String matchedFilter : matchedQueries) {
        out.writeString(matchedFilter);
      }
    }

    if (context.streamShardTarget() == InternalSearchHits.StreamContext.ShardTargetType.STREAM) {
      if (shard == null) {
        out.writeBoolean(false);
      } else {
        out.writeBoolean(true);
        shard.writeTo(out);
      }
    } else if (context.streamShardTarget()
        == InternalSearchHits.StreamContext.ShardTargetType.LOOKUP) {
      if (shard == null) {
        out.writeVInt(0);
      } else {
        out.writeVInt(context.shardHandleLookup().get(shard));
      }
    }

    if (innerHits == null) {
      out.writeVInt(0);
    } else {
      out.writeVInt(innerHits.size());
      for (Map.Entry<String, InternalSearchHits> entry : innerHits.entrySet()) {
        out.writeString(entry.getKey());
        entry
            .getValue()
            .writeTo(
                out,
                InternalSearchHits.streamContext()
                    .streamShardTarget(InternalSearchHits.StreamContext.ShardTargetType.NO_STREAM));
      }
    }
  }
  @Override
  public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
    List<SearchHitField> metaFields = Lists.newArrayList();
    List<SearchHitField> otherFields = Lists.newArrayList();
    if (fields != null && !fields.isEmpty()) {
      for (SearchHitField field : fields.values()) {
        if (field.values().isEmpty()) {
          continue;
        }
        if (field.isMetadataField()) {
          metaFields.add(field);
        } else {
          otherFields.add(field);
        }
      }
    }

    builder.startObject();
    // For inner_hit hits shard is null and that is ok, because the parent search hit has all this
    // information.
    // Even if this was included in the inner_hit hits this would be the same, so better leave it
    // out.
    if (explanation() != null && shard != null) {
      builder.field("_shard", shard.shardId());
      builder.field("_node", shard.nodeIdText());
    }
    if (shard != null) {
      builder.field(Fields._INDEX, shard.indexText());
    }
    builder.field(Fields._TYPE, type);
    builder.field(Fields._ID, id);
    if (nestedIdentity != null) {
      nestedIdentity.toXContent(builder, params);
    }
    if (version != -1) {
      builder.field(Fields._VERSION, version);
    }
    if (Float.isNaN(score)) {
      builder.nullField(Fields._SCORE);
    } else {
      builder.field(Fields._SCORE, score);
    }
    for (SearchHitField field : metaFields) {
      builder.field(field.name(), field.value());
    }
    if (source != null) {
      XContentHelper.writeRawField("_source", source, builder, params);
    }
    if (!otherFields.isEmpty()) {
      builder.startObject(Fields.FIELDS);
      for (SearchHitField field : otherFields) {
        builder.startArray(field.name());
        for (Object value : field.getValues()) {
          builder.value(value);
        }
        builder.endArray();
      }
      builder.endObject();
    }
    if (highlightFields != null && !highlightFields.isEmpty()) {
      builder.startObject(Fields.HIGHLIGHT);
      for (HighlightField field : highlightFields.values()) {
        builder.field(field.name());
        if (field.fragments() == null) {
          builder.nullValue();
        } else {
          builder.startArray();
          for (Text fragment : field.fragments()) {
            builder.value(fragment);
          }
          builder.endArray();
        }
      }
      builder.endObject();
    }
    if (sortValues != null && sortValues.length > 0) {
      builder.startArray(Fields.SORT);
      for (Object sortValue : sortValues) {
        if (sortValue != null && sortValue.equals(MAX_TERM_AS_TEXT)) {
          // We don't display MAX_TERM in JSON responses in case some clients have UTF-8 parsers
          // that wouldn't accept a
          // non-character in the response, even though this is valid UTF-8
          builder.nullValue();
        } else {
          builder.value(sortValue);
        }
      }
      builder.endArray();
    }
    if (matchedQueries.length > 0) {
      builder.startArray(Fields.MATCHED_QUERIES);
      for (String matchedFilter : matchedQueries) {
        builder.value(matchedFilter);
      }
      builder.endArray();
    }
    if (explanation() != null) {
      builder.field(Fields._EXPLANATION);
      buildExplanation(builder, explanation());
    }
    if (innerHits != null) {
      builder.startObject(Fields.INNER_HITS);
      for (Map.Entry<String, InternalSearchHits> entry : innerHits.entrySet()) {
        builder.startObject(entry.getKey());
        entry.getValue().toXContent(builder, params);
        builder.endObject();
      }
      builder.endObject();
    }
    builder.endObject();
    return builder;
  }
  public void readFrom(StreamInput in, InternalSearchHits.StreamContext context)
      throws IOException {
    score = in.readFloat();
    id = in.readText();
    type = in.readText();
    nestedIdentity = in.readOptionalStreamable(new InternalNestedIdentity());
    version = in.readLong();
    source = in.readBytesReference();
    if (source.length() == 0) {
      source = null;
    }
    if (in.readBoolean()) {
      explanation = readExplanation(in);
    }
    int size = in.readVInt();
    if (size == 0) {
      fields = ImmutableMap.of();
    } else if (size == 1) {
      SearchHitField hitField = readSearchHitField(in);
      fields = ImmutableMap.of(hitField.name(), hitField);
    } else if (size == 2) {
      SearchHitField hitField1 = readSearchHitField(in);
      SearchHitField hitField2 = readSearchHitField(in);
      fields = ImmutableMap.of(hitField1.name(), hitField1, hitField2.name(), hitField2);
    } else if (size == 3) {
      SearchHitField hitField1 = readSearchHitField(in);
      SearchHitField hitField2 = readSearchHitField(in);
      SearchHitField hitField3 = readSearchHitField(in);
      fields =
          ImmutableMap.of(
              hitField1.name(),
              hitField1,
              hitField2.name(),
              hitField2,
              hitField3.name(),
              hitField3);
    } else if (size == 4) {
      SearchHitField hitField1 = readSearchHitField(in);
      SearchHitField hitField2 = readSearchHitField(in);
      SearchHitField hitField3 = readSearchHitField(in);
      SearchHitField hitField4 = readSearchHitField(in);
      fields =
          ImmutableMap.of(
              hitField1.name(),
              hitField1,
              hitField2.name(),
              hitField2,
              hitField3.name(),
              hitField3,
              hitField4.name(),
              hitField4);
    } else if (size == 5) {
      SearchHitField hitField1 = readSearchHitField(in);
      SearchHitField hitField2 = readSearchHitField(in);
      SearchHitField hitField3 = readSearchHitField(in);
      SearchHitField hitField4 = readSearchHitField(in);
      SearchHitField hitField5 = readSearchHitField(in);
      fields =
          ImmutableMap.of(
              hitField1.name(),
              hitField1,
              hitField2.name(),
              hitField2,
              hitField3.name(),
              hitField3,
              hitField4.name(),
              hitField4,
              hitField5.name(),
              hitField5);
    } else {
      ImmutableMap.Builder<String, SearchHitField> builder = ImmutableMap.builder();
      for (int i = 0; i < size; i++) {
        SearchHitField hitField = readSearchHitField(in);
        builder.put(hitField.name(), hitField);
      }
      fields = builder.build();
    }

    size = in.readVInt();
    if (size == 0) {
      highlightFields = ImmutableMap.of();
    } else if (size == 1) {
      HighlightField field = readHighlightField(in);
      highlightFields = ImmutableMap.of(field.name(), field);
    } else if (size == 2) {
      HighlightField field1 = readHighlightField(in);
      HighlightField field2 = readHighlightField(in);
      highlightFields = ImmutableMap.of(field1.name(), field1, field2.name(), field2);
    } else if (size == 3) {
      HighlightField field1 = readHighlightField(in);
      HighlightField field2 = readHighlightField(in);
      HighlightField field3 = readHighlightField(in);
      highlightFields =
          ImmutableMap.of(field1.name(), field1, field2.name(), field2, field3.name(), field3);
    } else if (size == 4) {
      HighlightField field1 = readHighlightField(in);
      HighlightField field2 = readHighlightField(in);
      HighlightField field3 = readHighlightField(in);
      HighlightField field4 = readHighlightField(in);
      highlightFields =
          ImmutableMap.of(
              field1.name(),
              field1,
              field2.name(),
              field2,
              field3.name(),
              field3,
              field4.name(),
              field4);
    } else {
      ImmutableMap.Builder<String, HighlightField> builder = ImmutableMap.builder();
      for (int i = 0; i < size; i++) {
        HighlightField field = readHighlightField(in);
        builder.put(field.name(), field);
      }
      highlightFields = builder.build();
    }

    size = in.readVInt();
    if (size > 0) {
      sortValues = new Object[size];
      for (int i = 0; i < sortValues.length; i++) {
        byte type = in.readByte();
        if (type == 0) {
          sortValues[i] = null;
        } else if (type == 1) {
          sortValues[i] = in.readString();
        } else if (type == 2) {
          sortValues[i] = in.readInt();
        } else if (type == 3) {
          sortValues[i] = in.readLong();
        } else if (type == 4) {
          sortValues[i] = in.readFloat();
        } else if (type == 5) {
          sortValues[i] = in.readDouble();
        } else if (type == 6) {
          sortValues[i] = in.readByte();
        } else if (type == 7) {
          sortValues[i] = in.readShort();
        } else if (type == 8) {
          sortValues[i] = in.readBoolean();
        } else if (type == 9) {
          sortValues[i] = in.readText();
        } else {
          throw new IOException("Can't match type [" + type + "]");
        }
      }
    }

    size = in.readVInt();
    if (size > 0) {
      matchedQueries = new String[size];
      for (int i = 0; i < size; i++) {
        matchedQueries[i] = in.readString();
      }
    }

    if (context.streamShardTarget() == InternalSearchHits.StreamContext.ShardTargetType.STREAM) {
      if (in.readBoolean()) {
        shard = readSearchShardTarget(in);
      }
    } else if (context.streamShardTarget()
        == InternalSearchHits.StreamContext.ShardTargetType.LOOKUP) {
      int lookupId = in.readVInt();
      if (lookupId > 0) {
        shard = context.handleShardLookup().get(lookupId);
      }
    }

    size = in.readVInt();
    if (size > 0) {
      innerHits = new HashMap<>(size);
      for (int i = 0; i < size; i++) {
        String key = in.readString();
        InternalSearchHits value =
            InternalSearchHits.readSearchHits(
                in,
                InternalSearchHits.streamContext()
                    .streamShardTarget(InternalSearchHits.StreamContext.ShardTargetType.NO_STREAM));
        innerHits.put(key, value);
      }
    }
  }
  public void readFrom(StreamInput in, InternalSearchHits.StreamContext context)
      throws IOException {
    score = in.readFloat();
    id = in.readText();
    type = in.readText();
    nestedIdentity = in.readOptionalStreamable(InternalNestedIdentity::new);
    version = in.readLong();
    source = in.readBytesReference();
    if (source.length() == 0) {
      source = null;
    }
    if (in.readBoolean()) {
      explanation = readExplanation(in);
    }
    int size = in.readVInt();
    if (size == 0) {
      fields = emptyMap();
    } else if (size == 1) {
      SearchHitField hitField = readSearchHitField(in);
      fields = singletonMap(hitField.name(), hitField);
    } else {
      Map<String, SearchHitField> fields = new HashMap<>();
      for (int i = 0; i < size; i++) {
        SearchHitField hitField = readSearchHitField(in);
        fields.put(hitField.name(), hitField);
      }
      this.fields = unmodifiableMap(fields);
    }

    size = in.readVInt();
    if (size == 0) {
      highlightFields = emptyMap();
    } else if (size == 1) {
      HighlightField field = readHighlightField(in);
      highlightFields = singletonMap(field.name(), field);
    } else {
      Map<String, HighlightField> highlightFields = new HashMap<>();
      for (int i = 0; i < size; i++) {
        HighlightField field = readHighlightField(in);
        highlightFields.put(field.name(), field);
      }
      this.highlightFields = unmodifiableMap(highlightFields);
    }

    size = in.readVInt();
    if (size > 0) {
      sortValues = new Object[size];
      for (int i = 0; i < sortValues.length; i++) {
        byte type = in.readByte();
        if (type == 0) {
          sortValues[i] = null;
        } else if (type == 1) {
          sortValues[i] = in.readString();
        } else if (type == 2) {
          sortValues[i] = in.readInt();
        } else if (type == 3) {
          sortValues[i] = in.readLong();
        } else if (type == 4) {
          sortValues[i] = in.readFloat();
        } else if (type == 5) {
          sortValues[i] = in.readDouble();
        } else if (type == 6) {
          sortValues[i] = in.readByte();
        } else if (type == 7) {
          sortValues[i] = in.readShort();
        } else if (type == 8) {
          sortValues[i] = in.readBoolean();
        } else {
          throw new IOException("Can't match type [" + type + "]");
        }
      }
    }

    size = in.readVInt();
    if (size > 0) {
      matchedQueries = new String[size];
      for (int i = 0; i < size; i++) {
        matchedQueries[i] = in.readString();
      }
    }

    if (context.streamShardTarget() == ShardTargetType.STREAM) {
      if (in.readBoolean()) {
        shard = new SearchShardTarget(in);
      }
    } else if (context.streamShardTarget() == ShardTargetType.LOOKUP) {
      int lookupId = in.readVInt();
      if (lookupId > 0) {
        shard = context.handleShardLookup().get(lookupId);
      }
    }

    size = in.readVInt();
    if (size > 0) {
      innerHits = new HashMap<>(size);
      for (int i = 0; i < size; i++) {
        String key = in.readString();
        ShardTargetType shardTarget = context.streamShardTarget();
        InternalSearchHits value =
            InternalSearchHits.readSearchHits(
                in, context.streamShardTarget(ShardTargetType.NO_STREAM));
        context.streamShardTarget(shardTarget);
        innerHits.put(key, value);
      }
    }
  }