docs: public void search(
      Object rootValue,
      MatchSpaceKey msg,
      EvalCache cache,
      SearchResults result)
      throws MatchingException, BadMessageFormatMatchingException
    {
      if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled())
        tc.entry(
          this,cclass,
          "search",
          new Object[] {rootValue, msg, cache, result});
  
      // Update rootValue from msg if necessary and possible
      if (rootValue == null && rootId != null)
        rootValue = msg.getIdentifierValue(rootId, false, null, false);
      
      // Set up a root (document) context for evaluation. This will be 
      // exploited by XPath selectors.
      SetValEvaluationContext evalContext = new SetValEvaluationContext(); 
   
      // Obtain the relevant cache entry.  If it does not exist, we immediately punt to
      // pessimisticGet.  If cacheing is inactive we effectively skip this step.
      CacheEntry e = null;
      if (rootId != null) {
        e = getCacheEntry(rootValue, false);
        if (e == null)
        {
          puntsDueToCache++;
          pessimisticGet(rootValue, msg, cache, evalContext, result);
    
          if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled())
            tc.exit(this,cclass, "search");
    
          return;
        }
      }
  
      // Read the two volatile generation counters which, together, express the current
      // version of the subscription set under this rootId value.  The two are (1) the
      // matchTree generation counter, (2) the exact generation counter for the CacheEntry e.
  
      long wildG = matchTreeGeneration;
      long exactG = (e == null) ? 0 : e.exactGeneration;
  
      // If the present version is unstable, do pessimisticGet.  The present version is
      // unstable if any of the generation counters is odd.
      if ((wildG & 1) == 1 || (exactG & 1) == 1)
      {
        if (e == null)
          // uncached case:  trivial logic, bypass pessimisticGet
          synchronized(this) {
            matchTree.get(rootValue, msg, cache, evalContext, result);
          }
        else
          pessimisticGet(rootValue, msg, cache, evalContext, result);
  
        if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled())
          tc.exit(this,cclass, "search");
        return;
      }
  
      // If the cache will have to be updated, we do pessimisticGet.  The cache will have to
      // be updated if (1) its otherMatchers are invalid or (2) it is still eligible for
      // result caching and its result cache is invalid (assuming cacheing is active
      // at all).
  
      if (e != null && (wildG != e.matchTreeGeneration
        || ((!e.noResultCache) && (e.cachedResults == null))))
      {
        puntsDueToCache++;
        pessimisticGet(rootValue, msg, cache, evalContext, result);
  
        if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled())
          tc.exit(this,cclass, "search");
  
        return;
      }
  
      // Looks like we can do this get optimistically.  Before continuing, we synchronize
      // vacuously just for the side-effect of forcing a read barrier.  We want to ensure
      // that any variable we read hereafter cannot show a value that the processor cached
      // before we read the volatile generation counters.  We separately ensure that all
      // even-odd transitions of the generation counters appear in memory before updating
      // the associated structure and odd-even transitions appear after such an update.
  
      synchronized (result)
      {
      }
  
      // An optimistic get can conceivably get an exception due only to MatchSpace being in
      // an inconsistent state.  We guard against that by using try/catch.
  
      Throwable oops = null;
      try
      {
  
        // Look in the cache for a usable cachedResults, using them to satisfy the request
        // iff the results object is willing to accept them.
        if (e != null && e.cachedResults != null && result.acceptCacheable(e.cachedResults))
        {
          // We are actually done with the match, but we need to do the final version check
          // below to make sure the answer is valid.
          resultCacheHitGets++;
        }
        else
        { // result cache not valid
          // Prepare the EvalCache, since we'll be running real matchers.
          cache.prepareCache(subExpr.evalCacheSize());
          if (e == null)
            // No cacheing.  Just run the matchTree
            matchTree.get(rootValue, msg, cache, evalContext, result);
          else {
            // Cacheing is active.
            // Run the other matchers, if any.  We already know that the otherMatchers cache
            // is valid, so we don't look in the real matchTree at all.
            if (e.otherMatchers != null)
            {
              for (int i = 0; i < e.otherMatchers.length; i++)
                e.otherMatchers[i].get(null, msg, cache, evalContext, result);
              wildCacheHitGets++;
            }
            // Now do exact match (if any)
            if (e.exactMatcher != null)
            {
              e.exactMatcher.get(null, msg, cache, evalContext, result);
              exactMatches++;
            } 
          } // end else "Cacheing is active"
        } // end else "result cache not valid"
      }
      catch (RuntimeException exc)
      {
        // No FFDC code needed
        // FFDC driven by wrapper class.
          
  //TODO:      tc.exception(tc, exc);
          
        // We don't yet know if this exception is "real" or due to inconsistency
        oops = exc;
      }
      catch (Error exc)
      {
        // No FFDC code needed
        // FFDC driven by wrapper class.
  
  //TODO:      tc.exception(tc, exc);
         
       // We don't yet know if this exception is "real" or due to inconsistency
        oops = exc;
      }
  
      // Reevaluate the version numbers to make sure nothing changed under us.
      if (wildG != matchTreeGeneration || (e != null && exactG != e.exactGeneration))
      {
        result.reset(); // discard results.  They can't be trusted.
        if (e == null)
          synchronized(this) {
            matchTree.get(rootValue, msg, cache, evalContext, result);
          }
        else
          pessimisticGet(rootValue, msg, cache, evalContext, result);
  
        if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled())
          tc.exit(this,cclass, "search");
        return;
      }
  
      // The result of this optimistic get are "valid."  Now make sure there was no
      // exception.
      if (oops != null)
      { 
        // FFDC
        FFDC.processException(this,
            cclass,
            "com.ibm.ws.sib.matchspace.impl.MatchSpaceImpl.search",
            oops,
            "1:604:1.44");
        if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled())
          tc.exit(this,cclass, "search", oops);   
        throw new MatchingException(oops.toString());
      }
  
      optimisticGets++;
  
      if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled())
        tc.exit(this,cclass, "search");
    }