private boolean findMatch(Step leftStep, Step rightStep) {
    if (logger.isDebugEnabled()) {
      logger.debug("findMatch() for " + leftStep.getName() + " and " + rightStep.getName());
    }
    boolean foundMatch = match(leftStep, rightStep);
    if (!foundMatch && rightStep.getNextStep() != null) {
      // No match - go on with search
      foundMatch = this.findMatch(leftStep, rightStep.getNextStep());
    }

    if (logger.isDebugEnabled()) {
      logger.debug("findMatch() returns " + foundMatch);
    }
    return foundMatch;
  }
  private boolean match(Step leftStep, Step rightStep) {
    if (logger.isDebugEnabled()) {
      logger.debug("match() for " + leftStep.getName() + " and " + rightStep.getName());
    }
    boolean nameMatch = false;
    if (rightStep != null) {
      boolean typeMatch = leftStep.getType().equals(rightStep.getType());

      if (typeMatch) {
        logger.debug("match() both steps are of type: " + leftStep.getType());
        if (leftStep.getName().equals(rightStep.getName())) {
          nameMatch = true;
        }
      }
    }

    if (logger.isDebugEnabled()) {
      logger.debug("match() returns " + nameMatch);
    }
    return nameMatch;
  }
  private void bisimulate(Step leftStep, Step rightStep, FDifference parentDiff) {
    if (logger.isDebugEnabled()) {
      logger.debug("bisimulate() " + leftStep.getName() + " <-> " + rightStep.getName());
    }

    FDifference stepDiff = null;

    Step nextLeftStep = null;
    Step nextRightStep = null;

    if (leftStep != null && rightStep != null) {

      // Handle end steps
      if (leftStep instanceof EndStep) {
        if (!(rightStep instanceof EndStep)) {
          // left process ends but right process proceeds
          stepDiff = new FDifference(FDKind.CreatedElement, FDRating.Conflict);
          stepDiff.setSource(leftStep);
          stepDiff.setDescription(
              "The left process ends with "
                  + leftStep.getPrevStep().getType()
                  + " '"
                  + leftStep.getPrevStep().getName()
                  + "' but there are unmatched process steps in the right process left - starting from "
                  + rightStep.getType()
                  + " '"
                  + rightStep.getName()
                  + "'.");
        }
      } else if (rightStep instanceof EndStep) {
        // the right process ends but the left process proceeds
        stepDiff = new FDifference(FDKind.DeletedElement, FDRating.Conflict);
        stepDiff.setSource(leftStep);
        stepDiff.setDescription(
            "The right process ends with "
                + rightStep.getPrevStep().getType()
                + " '"
                + rightStep.getPrevStep().getName()
                + "' but there are unmatched process steps in the left process left - starting from "
                + leftStep.getType()
                + " '"
                + leftStep.getName()
                + "'.");
      } else if (match(leftStep, rightStep)) {
        // matching steps

        if (leftStep instanceof SubProcess) {
          // the right step is implicitly of type SubProcess because we had matching steps
          SubProcess leftSubProcess = (SubProcess) leftStep;
          SubProcess rightSubProcess = (SubProcess) rightStep;

          stepDiff =
              this.compareProcesses(
                  leftSubProcess.getSubProcess(), rightSubProcess.getSubProcess());
        } else if (leftStep instanceof HotSpotGroupCall) {
          HotSpotGroupCall leftHsgc = (HotSpotGroupCall) leftStep;
          HotSpotGroupCall rightHsgc = (HotSpotGroupCall) rightStep;
          stepDiff =
              basis.compareSets(
                  "hot spot group calls",
                  leftHsgc.getHotSpotGroups(),
                  rightHsgc.getHotSpotGroups(),
                  leftHsgc);
          if (stepDiff != null) {
            stepDiff.setDescription(
                "The hot spot group calls of step '"
                    + leftHsgc.getName()
                    + "' were changed in version "
                    + this.getRightVersion());
          }
        }

        // We had matching steps - go one step further on both sides
        nextLeftStep = leftStep.getNextStep();
        nextRightStep = rightStep.getNextStep();
      } else {
        // No match - see if there was something added or deleted on the right
        if (findMatch(leftStep, rightStep.getNextStep())) {
          if (logger.isDebugEnabled()) {
            logger.debug(
                "bisimulate() left step '"
                    + leftStep.getName()
                    + "' found later on the right. So the right step "
                    + rightStep.getName()
                    + " was added.");
          }
          // There are new elements on the right side until the left step comes
          stepDiff = new FDifference(FDKind.CreatedElement, FDRating.Conflict);
          stepDiff.setSource(rightStep);
          stepDiff.setDescription(
              "The "
                  + rightStep.getType()
                  + " '"
                  + rightStep.getName()
                  + "' was added in version "
                  + this.getRightVersion());

          // Go one step further on right
          nextLeftStep = leftStep;
          nextRightStep = rightStep.getNextStep();
        } else {
          if (logger.isDebugEnabled()) {
            logger.debug(
                "bisimulate() left step '"
                    + leftStep.getName()
                    + "' not found on right. So this step was removed.");
          }
          // The left step was removed because there is no match on the right side
          stepDiff = new FDifference(FDKind.DeletedElement, FDRating.Conflict);
          stepDiff.setSource(leftStep);
          stepDiff.setDescription(
              "The "
                  + leftStep.getType()
                  + " '"
                  + leftStep.getName()
                  + "' was removed in version "
                  + this.getRightVersion());

          // Go one step further on left
          nextLeftStep = leftStep.getNextStep();
          nextRightStep = rightStep;
        }
      }

    } else if (leftStep != null) {
      // No right step
      stepDiff = new FDifference(FDKind.DeletedElement, FDRating.Conflict);
      stepDiff.setSource(leftStep);
      stepDiff.setDescription(
          "The right process ends in the middle of the process but the left process proceeds with "
              + leftStep.getType()
              + " "
              + leftStep.getName());
    } else if (rightStep != null) {
      // No left step
      stepDiff = new FDifference(FDKind.CreatedElement, FDRating.Conflict);
      stepDiff.setSource(leftStep);
      stepDiff.setDescription(
          "The left process ends in the middle of the process but the right process proceeds with "
              + rightStep.getType()
              + " "
              + rightStep.getName());
    }

    parentDiff.addDifference(stepDiff);

    // Compare the next steps
    if (nextLeftStep != null && nextRightStep != null) {
      bisimulate(nextLeftStep, nextRightStep, parentDiff);
    }
  }