@Override
 public int transformIndex(int index, Operation op, Object param) {
   if (op instanceof SplitOperation) {
     SplitOperation s = (SplitOperation) op;
     index = transformIndex(index, s.getSecond(), param);
     index = transformIndex(index, s.getFirst(), param);
     return index;
   } else if (op instanceof NoOperation) {
     return index;
   } else if (op instanceof InsertOperation) {
     int pos = ((InsertOperation) op).getPosition();
     if (index < pos) {
       return index;
     } else {
       return index + ((InsertOperation) op).getTextLength();
     }
   } else if (op instanceof DeleteOperation) {
     int pos = ((DeleteOperation) op).getPosition();
     if (index <= pos) {
       return index;
     } else {
       return index - ((DeleteOperation) op).getTextLength();
     }
   } else {
     throw new IllegalArgumentException("Unsupported Operation type: " + op);
   }
 }
  /**
   * Transform operation <var>op1</var> in the context of another operation <var>op2</var>. The
   * transformed operation <var>op1'</var> is returned.
   *
   * @param op1 the operation to transform
   * @param op2 the operation which is the context for the other one
   * @param param a boolean flag to privilege the first operation <code>op1</code> (i.e. remains
   *     unchanged) when two insert operations are equivalent i.e. they have the same position and
   *     origin index.
   * @return the transformed operation <var>op1'</var>
   */
  @Override
  public Operation transform(Operation op1, Operation op2, Object param) {

    log.trace("Transform " + op1 + " in the context of " + op2 + " (privileged==" + param + ")");

    boolean privileged = (Boolean) param;

    /** A NoOperation is not affected. */
    if (op1 instanceof NoOperation) {
      return new NoOperation();
    }

    /** If the context is null, we return op1 unchanged. */
    if (op2 instanceof NoOperation) {
      return op1;
    }

    if (op1 instanceof SplitOperation) {
      /**
       * Given two operations s1 and s2 to be transformed in the context of op2, we need to
       * calculate s1' as t(s1, op2) and s2' as t(s2, op2') where op2' is t(op2, s1) <code>
       *          O
       *     s1 /   \ op2
       *      O       O
       * s2 /   \   / s1'
       *  O       O
       *    \   / s2'
       *      O
       * </code>
       */
      SplitOperation s = (SplitOperation) op1;
      return new SplitOperation(
          transform(s.getFirst(), op2, param),
          transform(s.getSecond(), transform(op2, s.getFirst(), !privileged), param));
    }
    if (op2 instanceof SplitOperation) {
      /**
       * Given an operation op1 to be transformed in the context of two operations s1 and s2, we
       * need to calculate op1' as t(op1', s2) where op1' is t(op1, s1) <code>
       *           O
       *      s1 /   \ op1
       *       O       O
       *  s2 /   \   /
       *   O       O
       * op1'\   /
       *       O
       *      </code>
       */
      SplitOperation s = (SplitOperation) op2;
      return transform(transform(op1, s.getFirst(), param), s.getSecond(), param);
    }

    if (op1 instanceof InsertOperation) {
      if (op2 instanceof InsertOperation) {
        return transform((InsertOperation) op1, (InsertOperation) op2, privileged);
      }
      if (op2 instanceof DeleteOperation) {
        return transform((InsertOperation) op1, (DeleteOperation) op2);
      }
    }
    if (op1 instanceof DeleteOperation) {
      if (op2 instanceof InsertOperation) {
        return transform((DeleteOperation) op1, (InsertOperation) op2);
      }
      if (op2 instanceof DeleteOperation) {
        return transform((DeleteOperation) op1, (DeleteOperation) op2);
      }
    }
    throw new InvalidParameterException("op1: " + op1 + ", op2: " + op2);
  }