/**
  * Returns the execution plan name
  *
  * @param executionPlanAsString executionPlan (taken from code mirror) as a string
  * @return execution plan name as given in @Plan:name('MyPlanName'). Returns null in the absence
  *     of @Plan:name('MyPlanName')
  */
 public static String getExecutionPlanName(String executionPlanAsString) {
   String executionPlanName = null;
   ExecutionPlan executionPlan = SiddhiCompiler.parse(executionPlanAsString);
   executionPlanName =
       AnnotationHelper.getAnnotationElement(
               EventProcessorConstants.ANNOTATION_NAME_NAME, null, executionPlan.getAnnotations())
           .getValue();
   return executionPlanName;
 }
  public static void validateExecutionPlan(String executionPlan)
      throws ExecutionPlanConfigurationException, ExecutionPlanDependencyValidationException {

    String planName;
    int i = 0; // this is maintained for giving more context info in error messages, when throwing
    // exceptions.
    ArrayList<String> importedStreams = new ArrayList<String>();
    ArrayList<String> exportedStreams = new ArrayList<String>();
    Pattern databridgeStreamNamePattern =
        Pattern.compile(EventProcessorConstants.DATABRIDGE_STREAM_REGEX);
    Pattern streamVersionPattern = Pattern.compile(EventProcessorConstants.STREAM_VER_REGEX);

    ExecutionPlan parsedExecPlan = SiddhiCompiler.parse(executionPlan);
    Element element =
        AnnotationHelper.getAnnotationElement(
            EventProcessorConstants.ANNOTATION_NAME_NAME, null, parsedExecPlan.getAnnotations());
    if (element == null) { // check if plan name is given
      throw new ExecutionPlanConfigurationException(
          "Execution plan name is not given. Please specify execution plan name using the annotation "
              + "'"
              + EventProcessorConstants.ANNOTATION_TOKEN_AT
              + EventProcessorConstants.ANNOTATION_PLAN
              + EventProcessorConstants.ANNOTATION_TOKEN_COLON
              + EventProcessorConstants.ANNOTATION_NAME_NAME
              + EventProcessorConstants.ANNOTATION_TOKEN_OPENING_BRACKET
              + EventProcessorConstants.SIDDHI_SINGLE_QUOTE
              + "executionPlanNameHere"
              + EventProcessorConstants.SIDDHI_SINGLE_QUOTE
              + EventProcessorConstants.ANNOTATION_TOKEN_CLOSING_BRACKET
              + "'");
    }
    planName = element.getValue();
    if (planName.equals("")) {
      throw new ExecutionPlanConfigurationException(
          "Execution plan name is empty. Hence the plan is invalid");
    }
    if (planName.trim().contains(" ")) {
      throw new ExecutionPlanConfigurationException(
          "Execution plan name '"
              + planName
              + "' contains whitespaces. Please remove whitespaces.");
    }

    Map<String, org.wso2.siddhi.query.api.definition.StreamDefinition> streamDefMap =
        parsedExecPlan.getStreamDefinitionMap();
    for (Map.Entry<String, org.wso2.siddhi.query.api.definition.StreamDefinition> entry :
        streamDefMap.entrySet()) {
      Element importElement =
          AnnotationHelper.getAnnotationElement(
              EventProcessorConstants.ANNOTATION_IMPORT, null, entry.getValue().getAnnotations());
      Element exportElement =
          AnnotationHelper.getAnnotationElement(
              EventProcessorConstants.ANNOTATION_EXPORT, null, entry.getValue().getAnnotations());
      if (importElement != null && exportElement != null) {
        throw new ExecutionPlanConfigurationException(
            "Same stream definition has being imported and exported. Please correct "
                + i
                + "th of the "
                + parsedExecPlan.getStreamDefinitionMap().size()
                + "stream definition, with stream id '"
                + entry.getKey()
                + "'");
      }
      if (importElement
          != null) { // Treating import & export cases separately to give more specific error
        // messages.
        String atImportLiteral =
            EventProcessorConstants.ANNOTATION_TOKEN_AT + EventProcessorConstants.ANNOTATION_IMPORT;
        String importElementValue = importElement.getValue();
        if (importElementValue == null || importElementValue.trim().isEmpty()) {
          throw new ExecutionPlanConfigurationException(
              "Imported stream cannot be empty as in '"
                  + atImportLiteral
                  + EventProcessorConstants.ANNOTATION_TOKEN_OPENING_BRACKET
                  + EventProcessorConstants.SIDDHI_SINGLE_QUOTE
                  + EventProcessorConstants.SIDDHI_SINGLE_QUOTE
                  + EventProcessorConstants.ANNOTATION_TOKEN_CLOSING_BRACKET
                  + "'. Please correct "
                  + i
                  + "th of the "
                  + parsedExecPlan.getStreamDefinitionMap().size()
                  + "stream definition, with stream id '"
                  + entry.getKey()
                  + "'");
        }
        String[] streamIdComponents =
            importElementValue.split(EventProcessorConstants.STREAM_SEPARATOR);
        if (streamIdComponents.length != 2) {
          throw new ExecutionPlanConfigurationException(
              "Found malformed "
                  + atImportLiteral
                  + " element '"
                  + importElementValue
                  + "'. "
                  + atImportLiteral
                  + " annotation should take the form '"
                  + atImportLiteral
                  + EventProcessorConstants.ANNOTATION_TOKEN_OPENING_BRACKET
                  + EventProcessorConstants.SIDDHI_SINGLE_QUOTE
                  + "streamName"
                  + EventProcessorConstants.STREAM_SEPARATOR
                  + "StreamVersion"
                  + EventProcessorConstants.SIDDHI_SINGLE_QUOTE
                  + EventProcessorConstants.ANNOTATION_TOKEN_CLOSING_BRACKET
                  + "'. There should be a '"
                  + EventProcessorConstants.STREAM_SEPARATOR
                  + "' character, separating the streamName and its version");
        }
        if ((!databridgeStreamNamePattern.matcher(streamIdComponents[0].trim()).matches())) {
          throw new ExecutionPlanConfigurationException(
              "Invalid imported stream name["
                  + streamIdComponents[0]
                  + "] in execution plan:"
                  + planName
                  + ". Stream name should match the regex '"
                  + EventProcessorConstants.DATABRIDGE_STREAM_REGEX
                  + "'");
        }
        Matcher m = streamVersionPattern.matcher(streamIdComponents[1].trim());
        if (!m.matches()) {
          throw new ExecutionPlanConfigurationException(
              "Invalid stream version ["
                  + streamIdComponents[1]
                  + "] for stream name "
                  + streamIdComponents[0]
                  + " in execution plan: "
                  + planName
                  + ". Stream version should match the regex '"
                  + EventProcessorConstants.STREAM_VER_REGEX
                  + "'");
        }
        validateSiddhiStreamWithDatabridgeStream(
            streamIdComponents[0], streamIdComponents[1], entry.getValue());
        if (exportedStreams.contains(
            importElementValue)) { // check if same stream has been imported and exported.
          throw new ExecutionPlanConfigurationException(
              "Imported stream '"
                  + importElementValue
                  + "' is also among the exported streams. Hence the execution plan is invalid");
        }
        importedStreams.add(importElementValue);
      }
      if (exportElement != null) {
        String atExportLiteral =
            EventProcessorConstants.ANNOTATION_TOKEN_AT + EventProcessorConstants.ANNOTATION_EXPORT;
        String exportElementValue = exportElement.getValue();
        if (exportElementValue == null || exportElementValue.trim().isEmpty()) {
          throw new ExecutionPlanConfigurationException(
              "Exported stream cannot be empty as in '"
                  + atExportLiteral
                  + EventProcessorConstants.ANNOTATION_TOKEN_OPENING_BRACKET
                  + EventProcessorConstants.SIDDHI_SINGLE_QUOTE
                  + EventProcessorConstants.SIDDHI_SINGLE_QUOTE
                  + EventProcessorConstants.ANNOTATION_TOKEN_CLOSING_BRACKET
                  + "'. Please correct "
                  + i
                  + "th of the "
                  + parsedExecPlan.getStreamDefinitionMap().size()
                  + "stream definition, with stream id '"
                  + entry.getKey());
        }
        String[] streamIdComponents =
            exportElementValue.split(EventProcessorConstants.STREAM_SEPARATOR);
        if (streamIdComponents.length != 2) {
          throw new ExecutionPlanConfigurationException(
              "Found malformed "
                  + atExportLiteral
                  + " element '"
                  + exportElementValue
                  + "'. "
                  + atExportLiteral
                  + " annotation should take the form '"
                  + atExportLiteral
                  + EventProcessorConstants.ANNOTATION_TOKEN_OPENING_BRACKET
                  + EventProcessorConstants.SIDDHI_SINGLE_QUOTE
                  + "streamName"
                  + EventProcessorConstants.STREAM_SEPARATOR
                  + "StreamVersion"
                  + EventProcessorConstants.SIDDHI_SINGLE_QUOTE
                  + EventProcessorConstants.ANNOTATION_TOKEN_CLOSING_BRACKET
                  + "'. There should be a '"
                  + EventProcessorConstants.STREAM_SEPARATOR
                  + "' character, separating the streamName and its version");
        }
        if ((!databridgeStreamNamePattern.matcher(streamIdComponents[0].trim()).matches())) {
          throw new ExecutionPlanConfigurationException(
              "Invalid exported stream name["
                  + streamIdComponents[0]
                  + "] in execution plan:"
                  + planName
                  + ". Stream name should match the regex '"
                  + EventProcessorConstants.DATABRIDGE_STREAM_REGEX
                  + "'");
        }
        Matcher m = streamVersionPattern.matcher(streamIdComponents[1].trim());
        if (!m.matches()) {
          throw new ExecutionPlanConfigurationException(
              "Invalid stream version ["
                  + streamIdComponents[1]
                  + "] for stream name "
                  + streamIdComponents[0]
                  + " in execution plan: "
                  + planName
                  + ". Stream version should match the regex '"
                  + EventProcessorConstants.STREAM_VER_REGEX
                  + "'");
        }
        validateSiddhiStreamWithDatabridgeStream(
            streamIdComponents[0], streamIdComponents[1], entry.getValue());
        if (importedStreams.contains(exportElementValue)) {
          throw new ExecutionPlanConfigurationException(
              "Exported stream '"
                  + exportElementValue
                  + "' is also among the imported streams. Hence the execution plan is invalid");
        }
        exportedStreams.add(exportElementValue);
      }
      i++;
    }

    SiddhiManager siddhiManager = EventProcessorValueHolder.getSiddhiManager();
    loadDataSourceConfiguration(siddhiManager);
    try {
      siddhiManager.validateExecutionPlan(executionPlan);
    } catch (Throwable t) {
      throw new ExecutionPlanConfigurationException(t.getMessage(), t);
    }
  }