private static MatchEventSpec analyzeMatchEvent(EvalNode relativeNode) {
    LinkedHashMap<String, Pair<EventType, String>> taggedEventTypes =
        new LinkedHashMap<String, Pair<EventType, String>>();
    LinkedHashMap<String, Pair<EventType, String>> arrayEventTypes =
        new LinkedHashMap<String, Pair<EventType, String>>();

    // Determine all the filter nodes used in the pattern
    EvalNodeAnalysisResult evalNodeAnalysisResult =
        EvalNodeUtil.recursiveAnalyzeChildNodes(relativeNode);

    // collect all filters underneath
    for (EvalFilterNode filterNode : evalNodeAnalysisResult.getFilterNodes()) {
      String optionalTag = filterNode.getEventAsName();
      if (optionalTag != null) {
        taggedEventTypes.put(
            optionalTag,
            new Pair<EventType, String>(
                filterNode.getFilterSpec().getFilterForEventType(),
                filterNode.getFilterSpec().getFilterForEventTypeName()));
      }
    }

    // collect those filters under a repeat since they are arrays
    Set<String> arrayTags = new HashSet<String>();
    for (EvalMatchUntilNode matchUntilNode : evalNodeAnalysisResult.getRepeatNodes()) {
      EvalNodeAnalysisResult matchUntilAnalysisResult =
          EvalNodeUtil.recursiveAnalyzeChildNodes(matchUntilNode.getChildNodes().get(0));
      for (EvalFilterNode filterNode : matchUntilAnalysisResult.getFilterNodes()) {
        String optionalTag = filterNode.getEventAsName();
        if (optionalTag != null) {
          arrayTags.add(optionalTag);
        }
      }
    }

    // for each array tag change collection
    for (String arrayTag : arrayTags) {
      if (taggedEventTypes.get(arrayTag) != null) {
        arrayEventTypes.put(arrayTag, taggedEventTypes.get(arrayTag));
        taggedEventTypes.remove(arrayTag);
      }
    }

    return new MatchEventSpec(taggedEventTypes, arrayEventTypes);
  }
  private static void recursiveCompile(
      EvalNode evalNode,
      StatementContext context,
      Set<String> eventTypeReferences,
      boolean isInsertInto,
      MatchEventSpec tags,
      Deque<Integer> subexpressionIdStack)
      throws ExprValidationException {
    int counter = 0;
    for (EvalNode child : evalNode.getChildNodes()) {
      subexpressionIdStack.addLast(counter++);
      recursiveCompile(
          child, context, eventTypeReferences, isInsertInto, tags, subexpressionIdStack);
      subexpressionIdStack.removeLast();
    }

    LinkedHashMap<String, Pair<EventType, String>> newTaggedEventTypes = null;
    LinkedHashMap<String, Pair<EventType, String>> newArrayEventTypes = null;

    if (evalNode instanceof EvalFilterNode) {
      EvalFilterNode filterNode = (EvalFilterNode) evalNode;
      String eventName = filterNode.getRawFilterSpec().getEventTypeName();
      EventType resolvedEventType =
          FilterStreamSpecRaw.resolveType(
              context.getEngineURI(),
              eventName,
              context.getEventAdapterService(),
              context.getPlugInTypeResolutionURIs());
      EventType finalEventType = resolvedEventType;
      String optionalTag = filterNode.getEventAsName();
      boolean isPropertyEvaluation = false;

      // obtain property event type, if final event type is properties
      if (filterNode.getRawFilterSpec().getOptionalPropertyEvalSpec() != null) {
        PropertyEvaluator optionalPropertyEvaluator =
            PropertyEvaluatorFactory.makeEvaluator(
                filterNode.getRawFilterSpec().getOptionalPropertyEvalSpec(),
                resolvedEventType,
                filterNode.getEventAsName(),
                context.getEventAdapterService(),
                context.getMethodResolutionService(),
                context.getSchedulingService(),
                context.getVariableService(),
                context.getEngineURI(),
                context.getStatementId(),
                context.getStatementName(),
                context.getAnnotations(),
                subexpressionIdStack);
        finalEventType = optionalPropertyEvaluator.getFragmentEventType();
        isPropertyEvaluation = true;
      }

      if (finalEventType instanceof EventTypeSPI) {
        eventTypeReferences.add(((EventTypeSPI) finalEventType).getMetadata().getPrimaryName());
      }

      // If a tag was supplied for the type, the tags must stay with this type, i.e. a=BeanA ->
      // b=BeanA -> a=BeanB is a no
      if (optionalTag != null) {
        Pair<EventType, String> pair = tags.getTaggedEventTypes().get(optionalTag);
        EventType existingType = null;
        if (pair != null) {
          existingType = pair.getFirst();
        }
        if (existingType == null) {
          pair = tags.getArrayEventTypes().get(optionalTag);
          if (pair != null) {
            throw new ExprValidationException(
                "Tag '"
                    + optionalTag
                    + "' for event '"
                    + eventName
                    + "' used in the repeat-until operator cannot also appear in other filter expressions");
          }
        }
        if ((existingType != null) && (existingType != finalEventType)) {
          throw new ExprValidationException(
              "Tag '"
                  + optionalTag
                  + "' for event '"
                  + eventName
                  + "' has already been declared for events of type "
                  + existingType.getUnderlyingType().getName());
        }
        pair = new Pair<EventType, String>(finalEventType, eventName);

        // add tagged type
        if (isPropertyEvaluation) {
          newArrayEventTypes = new LinkedHashMap<String, Pair<EventType, String>>();
          newArrayEventTypes.put(optionalTag, pair);
        } else {
          newTaggedEventTypes = new LinkedHashMap<String, Pair<EventType, String>>();
          newTaggedEventTypes.put(optionalTag, pair);
        }
      }

      // For this filter, filter types are all known tags at this time,
      // and additionally stream 0 (self) is our event type.
      // Stream type service allows resolution by property name event if that name appears in other
      // tags.
      // by defaulting to stream zero.
      // Stream zero is always the current event type, all others follow the order of the map
      // (stream 1 to N).
      String selfStreamName = optionalTag;
      if (selfStreamName == null) {
        selfStreamName = "s_" + UuidGenerator.generate();
      }
      LinkedHashMap<String, Pair<EventType, String>> filterTypes =
          new LinkedHashMap<String, Pair<EventType, String>>();
      Pair<EventType, String> typePair = new Pair<EventType, String>(finalEventType, eventName);
      filterTypes.put(selfStreamName, typePair);
      filterTypes.putAll(tags.getTaggedEventTypes());

      // for the filter, specify all tags used
      LinkedHashMap<String, Pair<EventType, String>> filterTaggedEventTypes =
          new LinkedHashMap<String, Pair<EventType, String>>(tags.getTaggedEventTypes());
      filterTaggedEventTypes.remove(optionalTag);

      // handle array tags (match-until clause)
      LinkedHashMap<String, Pair<EventType, String>> arrayCompositeEventTypes = null;
      if (tags.getArrayEventTypes() != null) {
        arrayCompositeEventTypes = new LinkedHashMap<String, Pair<EventType, String>>();
        String patternSubexEventType =
            getPatternSubexEventType(context.getStatementId(), "pattern", subexpressionIdStack);
        EventType arrayTagCompositeEventType =
            context
                .getEventAdapterService()
                .createSemiAnonymousMapType(
                    patternSubexEventType, new HashMap(), tags.getArrayEventTypes(), isInsertInto);
        for (Map.Entry<String, Pair<EventType, String>> entry :
            tags.getArrayEventTypes().entrySet()) {
          String tag = entry.getKey();
          if (!filterTypes.containsKey(tag)) {
            Pair<EventType, String> pair =
                new Pair<EventType, String>(arrayTagCompositeEventType, tag);
            filterTypes.put(tag, pair);
            arrayCompositeEventTypes.put(tag, pair);
          }
        }
      }

      StreamTypeService streamTypeService =
          new StreamTypeServiceImpl(filterTypes, context.getEngineURI(), true, false);
      List<ExprNode> exprNodes = filterNode.getRawFilterSpec().getFilterExpressions();

      FilterSpecCompiled spec =
          FilterSpecCompiler.makeFilterSpec(
              resolvedEventType,
              eventName,
              exprNodes,
              filterNode.getRawFilterSpec().getOptionalPropertyEvalSpec(),
              filterTaggedEventTypes,
              arrayCompositeEventTypes,
              streamTypeService,
              context.getMethodResolutionService(),
              context.getSchedulingService(),
              context.getVariableService(),
              context.getEventAdapterService(),
              context.getEngineURI(),
              null,
              context,
              subexpressionIdStack);
      filterNode.setFilterSpec(spec);
    } else if (evalNode instanceof EvalObserverNode) {
      EvalObserverNode observerNode = (EvalObserverNode) evalNode;
      try {
        ObserverFactory observerFactory =
            context.getPatternResolutionService().create(observerNode.getPatternObserverSpec());

        StreamTypeService streamTypeService =
            getStreamTypeService(
                context.getEngineURI(),
                context.getStatementId(),
                context.getEventAdapterService(),
                tags.taggedEventTypes,
                tags.arrayEventTypes,
                subexpressionIdStack,
                "observer");
        ExprValidationContext validationContext =
            new ExprValidationContext(
                streamTypeService,
                context.getMethodResolutionService(),
                null,
                context.getSchedulingService(),
                context.getVariableService(),
                context,
                context.getEventAdapterService(),
                context.getStatementName(),
                context.getStatementId(),
                context.getAnnotations());
        List<ExprNode> validated =
            validateExpressions(
                observerNode.getPatternObserverSpec().getObjectParameters(), validationContext);

        MatchedEventConvertor convertor =
            new MatchedEventConvertorImpl(
                tags.taggedEventTypes, tags.arrayEventTypes, context.getEventAdapterService());

        observerNode.setObserverFactory(observerFactory);
        observerFactory.setObserverParameters(validated, convertor);
      } catch (ObserverParameterException e) {
        throw new ExprValidationException(
            "Invalid parameter for pattern observer: " + e.getMessage(), e);
      } catch (PatternObjectException e) {
        throw new ExprValidationException(
            "Failed to resolve pattern observer: " + e.getMessage(), e);
      }
    } else if (evalNode instanceof EvalGuardNode) {
      EvalGuardNode guardNode = (EvalGuardNode) evalNode;
      try {
        GuardFactory guardFactory =
            context.getPatternResolutionService().create(guardNode.getPatternGuardSpec());

        StreamTypeService streamTypeService =
            getStreamTypeService(
                context.getEngineURI(),
                context.getStatementId(),
                context.getEventAdapterService(),
                tags.taggedEventTypes,
                tags.arrayEventTypes,
                subexpressionIdStack,
                "guard");
        ExprValidationContext validationContext =
            new ExprValidationContext(
                streamTypeService,
                context.getMethodResolutionService(),
                null,
                context.getSchedulingService(),
                context.getVariableService(),
                context,
                context.getEventAdapterService(),
                context.getStatementName(),
                context.getStatementId(),
                context.getAnnotations());
        List<ExprNode> validated =
            validateExpressions(
                guardNode.getPatternGuardSpec().getObjectParameters(), validationContext);

        MatchedEventConvertor convertor =
            new MatchedEventConvertorImpl(
                tags.taggedEventTypes, tags.arrayEventTypes, context.getEventAdapterService());

        guardNode.setGuardFactory(guardFactory);
        guardFactory.setGuardParameters(validated, convertor);
      } catch (GuardParameterException e) {
        throw new ExprValidationException(
            "Invalid parameter for pattern guard: " + e.getMessage(), e);
      } catch (PatternObjectException e) {
        throw new ExprValidationException("Failed to resolve pattern guard: " + e.getMessage(), e);
      }
    } else if (evalNode instanceof EvalEveryDistinctNode) {
      EvalEveryDistinctNode distinctNode = (EvalEveryDistinctNode) evalNode;
      MatchEventSpec matchEventFromChildNodes = analyzeMatchEvent(distinctNode);
      StreamTypeService streamTypeService =
          getStreamTypeService(
              context.getEngineURI(),
              context.getStatementId(),
              context.getEventAdapterService(),
              matchEventFromChildNodes.getTaggedEventTypes(),
              matchEventFromChildNodes.getArrayEventTypes(),
              subexpressionIdStack,
              "every-distinct");
      ExprValidationContext validationContext =
          new ExprValidationContext(
              streamTypeService,
              context.getMethodResolutionService(),
              null,
              context.getSchedulingService(),
              context.getVariableService(),
              context,
              context.getEventAdapterService(),
              context.getStatementName(),
              context.getStatementId(),
              context.getAnnotations());
      List<ExprNode> validated;
      try {
        validated = validateExpressions(distinctNode.getExpressions(), validationContext);
      } catch (ExprValidationPropertyException ex) {
        throw new ExprValidationPropertyException(
            ex.getMessage()
                + ", every-distinct requires that all properties resolve from sub-expressions to the every-distinct",
            ex.getCause());
      }

      MatchedEventConvertor convertor =
          new MatchedEventConvertorImpl(
              matchEventFromChildNodes.getTaggedEventTypes(),
              matchEventFromChildNodes.getArrayEventTypes(),
              context.getEventAdapterService());

      distinctNode.setConvertor(convertor);

      // Determine whether some expressions are constants or time period
      List<ExprNode> distinctExpressions = new ArrayList<ExprNode>();
      Long msecToExpire = null;
      for (ExprNode expr : validated) {
        if (expr instanceof ExprTimePeriod) {
          Double secondsExpire = (Double) ((ExprTimePeriod) expr).evaluate(null, true, context);
          if ((secondsExpire != null) && (secondsExpire > 0)) {
            msecToExpire = Math.round(1000d * secondsExpire);
          }
          log.debug("Setting every-distinct msec-to-expire to " + msecToExpire);
        } else if (expr.isConstantResult()) {
          log.warn(
              "Every-distinct node utilizes an expression returning a constant value, please check expression '"
                  + expr.toExpressionString()
                  + "', not adding expression to distinct-value expression list");
        } else {
          distinctExpressions.add(expr);
        }
      }
      if (distinctExpressions.isEmpty()) {
        throw new ExprValidationException(
            "Every-distinct node requires one or more distinct-value expressions that each return non-constant result values");
      }
      distinctNode.setExpressions(distinctExpressions, msecToExpire);
    } else if (evalNode instanceof EvalMatchUntilNode) {
      EvalMatchUntilNode matchUntilNode = (EvalMatchUntilNode) evalNode;

      // compile bounds expressions, if any
      MatchEventSpec untilMatchEventSpec =
          new MatchEventSpec(tags.getTaggedEventTypes(), tags.getArrayEventTypes());
      StreamTypeService streamTypeService =
          getStreamTypeService(
              context.getEngineURI(),
              context.getStatementId(),
              context.getEventAdapterService(),
              untilMatchEventSpec.getTaggedEventTypes(),
              untilMatchEventSpec.getArrayEventTypes(),
              subexpressionIdStack,
              "until");
      ExprValidationContext validationContext =
          new ExprValidationContext(
              streamTypeService,
              context.getMethodResolutionService(),
              null,
              context.getSchedulingService(),
              context.getVariableService(),
              context,
              context.getEventAdapterService(),
              context.getStatementName(),
              context.getStatementId(),
              context.getAnnotations());

      String message = "Match-until bounds value expressions must return a numeric value";
      if (matchUntilNode.getLowerBounds() != null) {
        ExprNode validated =
            ExprNodeUtility.getValidatedSubtree(matchUntilNode.getLowerBounds(), validationContext);
        matchUntilNode.setLowerBounds(validated);
        if ((validated.getExprEvaluator().getType() == null)
            || (!JavaClassHelper.isNumeric(validated.getExprEvaluator().getType()))) {
          throw new ExprValidationException(message);
        }
      }

      if (matchUntilNode.getUpperBounds() != null) {
        ExprNode validated =
            ExprNodeUtility.getValidatedSubtree(matchUntilNode.getUpperBounds(), validationContext);
        matchUntilNode.setUpperBounds(validated);
        if ((validated.getExprEvaluator().getType() == null)
            || (!JavaClassHelper.isNumeric(validated.getExprEvaluator().getType()))) {
          throw new ExprValidationException(message);
        }
      }

      MatchedEventConvertor convertor =
          new MatchedEventConvertorImpl(
              untilMatchEventSpec.getTaggedEventTypes(),
              untilMatchEventSpec.getArrayEventTypes(),
              context.getEventAdapterService());
      matchUntilNode.setConvertor(convertor);

      // compile new tag lists
      Set<String> arrayTags = null;
      EvalNodeAnalysisResult matchUntilAnalysisResult =
          EvalNodeUtil.recursiveAnalyzeChildNodes(matchUntilNode.getChildNodes().get(0));
      for (EvalFilterNode filterNode : matchUntilAnalysisResult.getFilterNodes()) {
        String optionalTag = filterNode.getEventAsName();
        if (optionalTag != null) {
          if (arrayTags == null) {
            arrayTags = new HashSet<String>();
          }
          arrayTags.add(optionalTag);
        }
      }

      if (arrayTags != null) {
        for (String arrayTag : arrayTags) {
          if (!tags.arrayEventTypes.containsKey(arrayTag)) {
            tags.arrayEventTypes.put(arrayTag, tags.taggedEventTypes.get(arrayTag));
            tags.taggedEventTypes.remove(arrayTag);
          }
        }
      }
      matchUntilNode.setTagsArrayedSet(arrayTags);
    } else if (evalNode instanceof EvalFollowedByNode) {
      EvalFollowedByNode followedByNode = (EvalFollowedByNode) evalNode;
      StreamTypeService streamTypeService =
          new StreamTypeServiceImpl(context.getEngineURI(), false);
      ExprValidationContext validationContext =
          new ExprValidationContext(
              streamTypeService,
              context.getMethodResolutionService(),
              null,
              context.getSchedulingService(),
              context.getVariableService(),
              context,
              context.getEventAdapterService(),
              context.getStatementName(),
              context.getStatementId(),
              context.getAnnotations());

      if (followedByNode.getOptionalMaxExpressions() != null) {
        List<ExprNode> validated = new ArrayList<ExprNode>();
        for (ExprNode maxExpr : followedByNode.getOptionalMaxExpressions()) {
          if (maxExpr == null) {
            validated.add(null);
          } else {
            ExprNodeSummaryVisitor visitor = new ExprNodeSummaryVisitor();
            maxExpr.accept(visitor);
            if (!visitor.isPlain()) {
              String errorMessage =
                  "Invalid maximum expression in followed-by, "
                      + visitor.getMessage()
                      + " are not allowed within the expression";
              log.error(errorMessage);
              throw new ExprValidationException(errorMessage);
            }

            ExprNode validatedExpr =
                ExprNodeUtility.getValidatedSubtree(maxExpr, validationContext);
            validated.add(validatedExpr);
            if ((validatedExpr.getExprEvaluator().getType() == null)
                || (!JavaClassHelper.isNumeric(validatedExpr.getExprEvaluator().getType()))) {
              String message =
                  "Invalid maximum expression in followed-by, the expression must return an integer value";
              throw new ExprValidationException(message);
            }
          }
        }
        followedByNode.setOptionalMaxExpressions(validated);
      }
    }

    if (newTaggedEventTypes != null) {
      tags.getTaggedEventTypes().putAll(newTaggedEventTypes);
    }
    if (newArrayEventTypes != null) {
      tags.getArrayEventTypes().putAll(newArrayEventTypes);
    }
  }