void processAnnotations(V1Span source) {
    for (int i = 0, length = source.annotations.size(); i < length; i++) {
      V1Annotation a = source.annotations.get(i);
      Span.Builder currentSpan = forEndpoint(source, a.endpoint);
      // core annotations require an endpoint. Don't give special treatment when that's missing
      if (a.value.length() == 2 && a.endpoint != null) {
        if (a.value.equals("cs")) {
          currentSpan.kind(Kind.CLIENT);
          cs = a;
        } else if (a.value.equals("sr")) {
          currentSpan.kind(Kind.SERVER);
          sr = a;
        } else if (a.value.equals("ss")) {
          currentSpan.kind(Kind.SERVER);
          ss = a;
        } else if (a.value.equals("cr")) {
          currentSpan.kind(Kind.CLIENT);
          cr = a;
        } else if (a.value.equals("ms")) {
          currentSpan.kind(Kind.PRODUCER);
          ms = a;
        } else if (a.value.equals("mr")) {
          currentSpan.kind(Kind.CONSUMER);
          mr = a;
        } else if (a.value.equals("ws")) {
          ws = a;
        } else if (a.value.equals("wr")) {
          wr = a;
        } else {
          currentSpan.addAnnotation(a.timestamp, a.value);
        }
      } else {
        currentSpan.addAnnotation(a.timestamp, a.value);
      }
    }

    // When bridging between event and span model, you can end up missing a start annotation
    if (cs == null && endTimestampReflectsSpanDuration(cr, source)) {
      cs = V1Annotation.create(source.timestamp, "cs", cr.endpoint);
    }
    if (sr == null && endTimestampReflectsSpanDuration(ss, source)) {
      sr = V1Annotation.create(source.timestamp, "sr", ss.endpoint);
    }

    if (cs != null && sr != null) {
      // in a shared span, the client side owns span duration by annotations or explicit timestamp
      maybeTimestampDuration(source, cs, cr);

      // special-case loopback: We need to make sure on loopback there are two span2s
      Span.Builder client = forEndpoint(source, cs.endpoint);
      Span.Builder server;
      if (hasSameServiceName(cs.endpoint, sr.endpoint)) {
        client.kind(Kind.CLIENT);
        // fork a new span for the server side
        server = newSpanBuilder(source, sr.endpoint).kind(Kind.SERVER);
      } else {
        server = forEndpoint(source, sr.endpoint);
      }

      // the server side is smaller than that, we have to read annotations to find out
      server.shared(true).timestamp(sr.timestamp);
      if (ss != null) server.duration(ss.timestamp - sr.timestamp);
      if (cr == null && source.duration == 0) client.duration(null); // one-way has no duration
    } else if (cs != null && cr != null) {
      maybeTimestampDuration(source, cs, cr);
    } else if (sr != null && ss != null) {
      maybeTimestampDuration(source, sr, ss);
    } else { // otherwise, the span is incomplete. revert special-casing
      handleIncompleteRpc(source);
    }

    // Span v1 format did not have a shared flag. By convention, span.timestamp being absent
    // implied shared. When we only see the server-side, carry this signal over.
    if (cs == null && sr != null &&
      // We use a signal of either the authoritative timestamp being unset, or the duration is unset
      // eventhough we have the server send result. The latter clarifies an edge case in MySQL
      // where a span row is shared between client and server. The presence of timestamp in this
      // case could be due to the client-side of that RPC.
      (source.timestamp == 0 || (ss != null && source.duration == 0))) {
      forEndpoint(source, sr.endpoint).shared(true);
    }

    // ms and mr are not supposed to be in the same span, but in case they are..
    if (ms != null && mr != null) {
      // special-case loopback: We need to make sure on loopback there are two span2s
      Span.Builder producer = forEndpoint(source, ms.endpoint);
      Span.Builder consumer;
      if (hasSameServiceName(ms.endpoint, mr.endpoint)) {
        producer.kind(Kind.PRODUCER);
        // fork a new span for the consumer side
        consumer = newSpanBuilder(source, mr.endpoint).kind(Kind.CONSUMER);
      } else {
        consumer = forEndpoint(source, mr.endpoint);
      }

      consumer.shared(true);
      if (wr != null) {
        consumer.timestamp(wr.timestamp).duration(mr.timestamp - wr.timestamp);
      } else {
        consumer.timestamp(mr.timestamp);
      }

      producer.timestamp(ms.timestamp).duration(ws != null ? ws.timestamp - ms.timestamp : null);
    } else if (ms != null) {
      maybeTimestampDuration(source, ms, ws);
    } else if (mr != null) {
      if (wr != null) {
        maybeTimestampDuration(source, wr, mr);
      } else {
        maybeTimestampDuration(source, mr, null);
      }
    } else {
      if (ws != null) forEndpoint(source, ws.endpoint).addAnnotation(ws.timestamp, ws.value);
      if (wr != null) forEndpoint(source, wr.endpoint).addAnnotation(wr.timestamp, wr.value);
    }
  }
Allows you to split a v1 span when necessary. This can be the case when reading merged
client+server spans from storage or parsing old data formats.

<p>This type isn't thread-safe: it re-uses state to avoid re-allocations in conversion loops.
zipkin/src/main/java/zipkin2/v1/V1SpanConverter.java