Package org.eclipse.ecf.internal.sync.doc.cola

Source Code of org.eclipse.ecf.internal.sync.doc.cola.ColaSynchronizationStrategy

/****************************************************************************
* Copyright (c) 2008 Mustafa K. Isik and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*    Mustafa K. Isik - initial API and implementation
*****************************************************************************/

package org.eclipse.ecf.internal.sync.doc.cola;

import java.util.*;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IAdapterManager;
import org.eclipse.ecf.core.identity.ID;
import org.eclipse.ecf.core.util.Trace;
import org.eclipse.ecf.internal.sync.Activator;
import org.eclipse.ecf.internal.sync.SyncDebugOptions;
import org.eclipse.ecf.sync.*;
import org.eclipse.ecf.sync.doc.DocumentChangeMessage;
import org.eclipse.ecf.sync.doc.IDocumentChange;
import org.eclipse.osgi.util.NLS;

public class ColaSynchronizationStrategy implements
    IModelSynchronizationStrategy {

  // <ColaDocumentChangeMessage>
  private final List unacknowledgedLocalOperations;
  private final boolean isInitiator;
  private long localOperationsCount;
  private long remoteOperationsCount;

  // <ID, ColaSynchronizationStrategy>
  private static Map sessionStrategies = new HashMap();

  private ColaSynchronizationStrategy(boolean isInitiator) {
    this.isInitiator = isInitiator;
    unacknowledgedLocalOperations = new LinkedList();
    localOperationsCount = 0;
    remoteOperationsCount = 0;
  }

  public static ColaSynchronizationStrategy getInstanceFor(ID client,
      boolean isInitiator) {
    ColaSynchronizationStrategy existingStrategy = (ColaSynchronizationStrategy) sessionStrategies
        .get(client);
    if (existingStrategy != null
        && existingStrategy.isInitiator == isInitiator)
      return existingStrategy;
    existingStrategy = new ColaSynchronizationStrategy(isInitiator);
    sessionStrategies.put(client, existingStrategy);
    return existingStrategy;
  }

  public static void cleanUpFor(ID client) {
    sessionStrategies.remove(client);
  }

  public static void dispose() {
    sessionStrategies.clear();
  }

  /**
   * Handles proper transformation of incoming
   * <code>ColaDocumentChangeMessage</code>s. Returned
   * <code>DocumentChangeMessage</code>s can be applied directly to the shared
   * document. The method implements the concurrency algorithm described in
   * <code>http://wiki.eclipse.org/RT_Shared_Editing</code>
   *
   * @param remoteMsg
   * @return List contains <code>DocumentChangeMessage</code>s ready for
   *         sequential application to document
   */
  public List transformIncomingMessage(final DocumentChangeMessage remoteMsg) {
    if (!(remoteMsg instanceof ColaDocumentChangeMessage)) {
      throw new IllegalArgumentException(
          "DocumentChangeMessage is incompatible with Cola SynchronizationStrategy"); //$NON-NLS-1$
    }
    Trace.entering(Activator.PLUGIN_ID, SyncDebugOptions.METHODS_ENTERING,
        this.getClass(), "transformIncomingMessage", remoteMsg); //$NON-NLS-1$
    ColaDocumentChangeMessage transformedRemote = (ColaDocumentChangeMessage) remoteMsg;

    final List transformedRemotes = new LinkedList();
    transformedRemotes.add(transformedRemote);

    remoteOperationsCount++;
    Trace.trace(Activator.PLUGIN_ID, "unacknowledgedLocalOperations="
        + unacknowledgedLocalOperations);
    // this is where the concurrency algorithm is executed
    if (!unacknowledgedLocalOperations.isEmpty()) {// Do not remove this. It
                            // is necessary. The
                            // following iterator
                            // does not suffice.
      // remove operations from queue that have been implicitly
      // acknowledged as received on the remote site by the reception of
      // this message
      for (final Iterator it = unacknowledgedLocalOperations.iterator(); it
          .hasNext();) {
        final ColaDocumentChangeMessage unackedLocalOp = (ColaDocumentChangeMessage) it
            .next();
        if (transformedRemote.getRemoteOperationsCount() > unackedLocalOp
            .getLocalOperationsCount()) {
          Trace.trace(
              Activator.PLUGIN_ID,
              NLS.bind(
                  "transformIncomingMessage.removing {0}", unackedLocalOp)); //$NON-NLS-1$
          it.remove();
        } else {
          // the unackowledgedLocalOperations queue is ordered and
          // sorted
          // due to sequential insertion of local ops, thus once a
          // local op with a higher
          // or equal local op count (i.e. remote op count from the
          // remote operation's view)
          // is reached, we can abandon the check for the remaining
          // queue items
          Trace.trace(Activator.PLUGIN_ID,
              "breaking out of unackedLocalOperations loop"); //$NON-NLS-1$
          break;// exits for-loop
        }
      }

      // at this point the queue has been freed of operations that
      // don't require to be transformed against

      if (!unacknowledgedLocalOperations.isEmpty()) {
        ColaDocumentChangeMessage localOp = (ColaDocumentChangeMessage) unacknowledgedLocalOperations
            .get(0);
        Assert.isTrue(transformedRemote.getRemoteOperationsCount() == localOp
            .getLocalOperationsCount());

        for (final ListIterator unackOpsListIt = unacknowledgedLocalOperations
            .listIterator(); unackOpsListIt.hasNext();) {
          for (final ListIterator trafoRemotesIt = transformedRemotes
              .listIterator(); trafoRemotesIt.hasNext();) {
            // returns new instance
            // clarify operation preference, owner/docshare
            // initiator
            // consistently comes first
            localOp = (ColaDocumentChangeMessage) unackOpsListIt
                .next();
            transformedRemote = (ColaDocumentChangeMessage) trafoRemotesIt
                .next();
            transformedRemote = transformedRemote.transformAgainst(
                localOp, isInitiator);

            if (transformedRemote.isSplitUp()) {
              // currently this only happens for a remote deletion
              // that needs to be transformed against a locally
              // applied insertion
              // attention: before applying a list of deletions to
              // docshare, the indices need to be
              // updated/finalized one last time
              // since deletions can only be applied sequentially
              // and every deletion is going to change the
              // underlying document and the
              // respective indices!
              trafoRemotesIt.remove();
              for (final Iterator splitUpIterator = transformedRemote
                  .getSplitUpRepresentation().iterator(); splitUpIterator
                  .hasNext();) {
                trafoRemotesIt.add(splitUpIterator.next());
              }
              // according to the ListIterator documentation it
              // seems so as if the following line is unnecessary,
              // as a call to next() after the last removal and
              // additions will return what it would have returned
              // anyway
              // trafoRemotesIt.next();//TODO not sure about the
              // need for this - I want to jump over the two
              // inserted ops and reach the end of this iterator
            }

            // TODO check whether or not this collection shuffling
            // does what it is supposed to, i.e. remove current
            // localop in unack list and add split up representation
            // instead
            if (localOp.isSplitUp()) {
              // local operation has been split up during
              // operational transform --> remove current version
              // and add new versions plus jump over those
              unackOpsListIt.remove();
              for (final Iterator splitUpOpIterator = localOp
                  .getSplitUpRepresentation().iterator(); splitUpOpIterator
                  .hasNext();) {
                unackOpsListIt.add(splitUpOpIterator.next());
              }
              // according to the ListIterator documentation it
              // seems so as if the following line is unnecessary,
              // as a call to next() after the last removal and
              // additions will return what it would have returned
              // anyway
              // unackOpsListIt.next();//TODO check whether or not
              // this does jump over both inserted operations that
              // replaced the current unack op
            }// end split up localop handling
          }// transformedRemotes List iteration
        }
      }

    }
    Trace.exiting(Activator.PLUGIN_ID, SyncDebugOptions.METHODS_EXITING,
        this.getClass(), "transformIncomingMessage", transformedRemote); //$NON-NLS-1$

    // TODO find a cleaner and more OO way of cleaning up the list if it
    // contains multiple deletions:
    if (transformedRemotes.size() > 1) {
      final ColaDocumentChangeMessage firstOp = (ColaDocumentChangeMessage) transformedRemotes
          .get(0);
      if (firstOp.isDeletion()) {
        // this means all operations in the return list must also be
        // deletions, i.e. modify virtual/optimistic offset for
        // sequential application to document
        final ListIterator deletionFinalizerIt = transformedRemotes
            .listIterator();
        ColaDocumentChangeMessage previousDel = (ColaDocumentChangeMessage) deletionFinalizerIt
            .next();// jump over first del-op does not need
                // modification, we know this is OK because of
                // previous size check;
        ColaDocumentChangeMessage currentDel;

        for (; deletionFinalizerIt.hasNext();) {
          currentDel = (ColaDocumentChangeMessage) deletionFinalizerIt
              .next();
          currentDel.setOffset(currentDel.getOffset()
              - previousDel.getLengthOfReplacedText());
          previousDel = currentDel;
        }
      }
    }

    return transformedRemotes;
  }

  public String toString() {
    final StringBuffer buf = new StringBuffer("ColaSynchronizationStrategy"); //$NON-NLS-1$
    return buf.toString();
  }

  /*
   * (non-Javadoc)
   *
   * @see
   * org.eclipse.ecf.sync.doc.IDocumentSynchronizationStrategy#registerLocalChange
   * (org.eclipse.ecf.sync.doc.IModelChange)
   */
  public IModelChangeMessage[] registerLocalChange(IModelChange localChange) {
    List results = new ArrayList();
    Trace.entering(Activator.PLUGIN_ID, SyncDebugOptions.METHODS_ENTERING,
        this.getClass(), "registerLocalChange", localChange); //$NON-NLS-1$
    if (localChange instanceof IDocumentChange) {
      final IDocumentChange docChange = (IDocumentChange) localChange;
      final ColaDocumentChangeMessage colaMsg = new ColaDocumentChangeMessage(
          new DocumentChangeMessage(docChange.getOffset(),
              docChange.getLengthOfReplacedText(),
              docChange.getText()), localOperationsCount,
          remoteOperationsCount);
      // If not replacement, we simply add to
      // unacknowledgedLocalOperations and add message
      // to results
      if (!colaMsg.isReplacement()) {
        unacknowledgedLocalOperations.add(colaMsg);
        localOperationsCount++;
        results.add(colaMsg);
      } else {
        // It *is a replacement message, so we add both a delete and an
        // insert message
        // First create/add a delete message (text set to "")...
        ColaDocumentChangeMessage delMsg = new ColaDocumentChangeMessage(
            new DocumentChangeMessage(docChange.getOffset(),
                docChange.getLengthOfReplacedText(), ""),
            localOperationsCount, remoteOperationsCount);
        unacknowledgedLocalOperations.add(delMsg);
        localOperationsCount++;
        results.add(delMsg);
        // Then create/add the insert message (length set to 0)
        ColaDocumentChangeMessage insMsg = new ColaDocumentChangeMessage(
            new DocumentChangeMessage(docChange.getOffset(), 0,
                docChange.getText()), localOperationsCount,
            remoteOperationsCount);
        unacknowledgedLocalOperations.add(insMsg);
        localOperationsCount++;
        results.add(insMsg);
      }
      Trace.exiting(Activator.PLUGIN_ID,
          SyncDebugOptions.METHODS_EXITING, this.getClass(),
          "registerLocalChange", colaMsg); //$NON-NLS-1$
    }
    return (IModelChangeMessage[]) results
        .toArray(new IModelChangeMessage[] {});
  }

  /*
   * (non-Javadoc)
   *
   * @see org.eclipse.ecf.sync.doc.IDocumentSynchronizationStrategy#
   * toDocumentChangeMessage(byte[])
   */
  public IModelChange deserializeRemoteChange(byte[] bytes)
      throws SerializationException {
    return DocumentChangeMessage.deserialize(bytes);
  }

  /*
   * (non-Javadoc)
   *
   * @see org.eclipse.ecf.sync.doc.IDocumentSynchronizationStrategy#
   * transformRemoteChange(org.eclipse.ecf.sync.doc.IModelChangeMessage)
   */
  public IModelChange[] transformRemoteChange(IModelChange remoteChange) {
    if (!(remoteChange instanceof DocumentChangeMessage))
      return new IDocumentChange[0];
    final DocumentChangeMessage m = (DocumentChangeMessage) remoteChange;
    final List l = this.transformIncomingMessage(m);
    return (IDocumentChange[]) l.toArray(new IDocumentChange[] {});
  }

  /*
   * (non-Javadoc)
   *
   * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
   */
  public Object getAdapter(Class adapter) {
    if (adapter == null)
      return null;
    IAdapterManager manager = Activator.getDefault().getAdapterManager();
    if (manager == null)
      return null;
    return manager.loadAdapter(this, adapter.getName());
  }
}
TOP

Related Classes of org.eclipse.ecf.internal.sync.doc.cola.ColaSynchronizationStrategy

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.