void process(Span span) {
      Endpoint endpoint = span.localEndpoint();
      boolean shared = Boolean.TRUE.equals(span.shared());
      Object key = createKey(span.id(), shared, span.localEndpoint());
      Object noEndpointKey = endpoint != null ? createKey(span.id(), shared, null) : key;

      Object parent = null;
      if (shared) {
        // Shared is a server span. It will very likely be on a different endpoint than the client.
        // Clients are not ambiguous by ID, so we don't need to qualify by endpoint.
        parent = span.id();
      } else if (span.parentId() != null) {
        // We are not a root span, and not a shared server span. Proceed in most specific to least.

        // We could be the child of a shared server span (ex a local (intermediate) span on the same
        // endpoint). This is the most specific case, so we try this first.
        parent = createKey(span.parentId(), true, endpoint);
        if (spanToParent.containsKey(parent)) {
          spanToParent.put(noEndpointKey, parent);
        } else {
          // If there's no shared parent, fall back to normal case which is unqualified beyond ID.
          parent = span.parentId();
        }
      } else { // we are root or don't know our parent
        if (rootSpan != null) {
          if (logger.isLoggable(FINE)) {
            logger.fine(format(
              "attributing span missing parent to root: traceId=%s, rootSpanId=%s, spanId=%s",
              span.traceId(), rootSpan.span().id(), span.id()));
          }
        }
      }

      SpanNode node = new SpanNode(span);
      // special-case root, and attribute missing parents to it. In
      // other words, assume that the first root is the "real" root.
      if (parent == null && rootSpan == null) {
        rootSpan = node;
        spanToParent.remove(noEndpointKey);
      } else if (shared) {
        // In the case of shared server span, we need to address it both ways, in case intermediate
        // spans are lacking endpoint information.
        keyToNode.put(key, node);
        keyToNode.put(noEndpointKey, node);
      } else {
        keyToNode.put(noEndpointKey, node);
      }
    }
Processing is taking a span and placing it at the most appropriate place in the trace tree.
For example, if this is a {@link Span.Kind#SERVER} span, it would be a different node, and a
child of its {@link Span.Kind#CLIENT} even if they share the same span ID.

<p>Processing is defensive of typical problems in span reporting, such as depth-first. For
example, depth-first reporting implies you can see spans missing their parent. Hence, the
result of processing all spans can be a virtual root node.
zipkin/src/main/java/zipkin2/internal/SpanNode.java