From 38a9a76d687e731347eb05acfcbd7fb2e2c45aad Mon Sep 17 00:00:00 2001 From: slewis Date: Thu, 2 Oct 2008 00:12:20 +0000 Subject: Initial checkin of o.e.e.sync plugin in runtime area --- framework/bundles/org.eclipse.ecf.sync/.classpath | 7 + framework/bundles/org.eclipse.ecf.sync/.options | 16 ++ framework/bundles/org.eclipse.ecf.sync/.project | 34 ++++ .../.settings/org.eclipse.jdt.core.prefs | 7 + .../.settings/org.eclipse.pde.core.prefs | 4 + .../org.eclipse.ecf.sync/META-INF/MANIFEST.MF | 20 ++ framework/bundles/org.eclipse.ecf.sync/about.html | 28 +++ .../bundles/org.eclipse.ecf.sync/build.properties | 10 + .../bundles/org.eclipse.ecf.sync/plugin.properties | 12 ++ .../org/eclipse/ecf/internal/sync/Activator.java | 63 ++++++ .../ecf/internal/sync/SyncDebugOptions.java | 23 +++ .../cola/ColaDeletionTransformationStrategy.java | 148 ++++++++++++++ .../sync/doc/cola/ColaDocumentChangeMessage.java | 113 +++++++++++ .../cola/ColaInsertionTransformationStategy.java | 105 ++++++++++ .../cola/ColaReplacementTransformationStategy.java | 44 +++++ .../sync/doc/cola/ColaSynchronizationStrategy.java | 220 +++++++++++++++++++++ .../cola/ColaSynchronizationStrategyFactory.java | 39 ++++ .../sync/doc/cola/ColaTransformationStrategy.java | 19 ++ .../identity/IdentitySynchronizationStrategy.java | 46 +++++ .../IdentitySynchronizationStrategyFactory.java | 38 ++++ .../org/eclipse/ecf/sync/IServiceConstants.java | 21 ++ .../org/eclipse/ecf/sync/doc/IDocumentChange.java | 37 ++++ .../ecf/sync/doc/IDocumentChangeMessage.java | 23 +++ .../sync/doc/IDocumentSynchronizationStrategy.java | 58 ++++++ .../IDocumentSynchronizationStrategyFactory.java | 39 ++++ .../ecf/sync/doc/SerializationException.java | 58 ++++++ .../sync/doc/messages/DocumentChangeMessage.java | 109 ++++++++++ 27 files changed, 1341 insertions(+) create mode 100644 framework/bundles/org.eclipse.ecf.sync/.classpath create mode 100644 framework/bundles/org.eclipse.ecf.sync/.options create mode 100644 framework/bundles/org.eclipse.ecf.sync/.project create mode 100644 framework/bundles/org.eclipse.ecf.sync/.settings/org.eclipse.jdt.core.prefs create mode 100644 framework/bundles/org.eclipse.ecf.sync/.settings/org.eclipse.pde.core.prefs create mode 100644 framework/bundles/org.eclipse.ecf.sync/META-INF/MANIFEST.MF create mode 100644 framework/bundles/org.eclipse.ecf.sync/about.html create mode 100644 framework/bundles/org.eclipse.ecf.sync/build.properties create mode 100644 framework/bundles/org.eclipse.ecf.sync/plugin.properties create mode 100644 framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/Activator.java create mode 100644 framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/SyncDebugOptions.java create mode 100644 framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaDeletionTransformationStrategy.java create mode 100644 framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaDocumentChangeMessage.java create mode 100644 framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaInsertionTransformationStategy.java create mode 100644 framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaReplacementTransformationStategy.java create mode 100644 framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaSynchronizationStrategy.java create mode 100644 framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaSynchronizationStrategyFactory.java create mode 100644 framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaTransformationStrategy.java create mode 100644 framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/identity/IdentitySynchronizationStrategy.java create mode 100644 framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/identity/IdentitySynchronizationStrategyFactory.java create mode 100644 framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/IServiceConstants.java create mode 100644 framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/IDocumentChange.java create mode 100644 framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/IDocumentChangeMessage.java create mode 100644 framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/IDocumentSynchronizationStrategy.java create mode 100644 framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/IDocumentSynchronizationStrategyFactory.java create mode 100644 framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/SerializationException.java create mode 100644 framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/messages/DocumentChangeMessage.java (limited to 'framework/bundles/org.eclipse.ecf.sync') diff --git a/framework/bundles/org.eclipse.ecf.sync/.classpath b/framework/bundles/org.eclipse.ecf.sync/.classpath new file mode 100644 index 000000000..2fbb7a23e --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/bundles/org.eclipse.ecf.sync/.options b/framework/bundles/org.eclipse.ecf.sync/.options new file mode 100644 index 000000000..2937ca723 --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/.options @@ -0,0 +1,16 @@ +# Debugging options for the org.eclipse.ecf.sync plug-in + +# Turn on general debugging for the org.eclipse.ecf.internal.provisional.docshare plug-in +org.eclipse.ecf.sync/debug=true +org.eclipse.ecf.sync/debug/filter = * +org.eclipse.ecf.sync/debug/flag = true + +# Trace when exceptions are caught +org.eclipse.ecf.sync/debug/exceptions/catching=false +# Trace when exceptions are thrown +org.eclipse.ecf.sync/debug/exceptions/throwing=false + +# Trace when methods are entered +org.eclipse.ecf.sync/debug/methods/entering=false +# Trace when methods are exited +org.eclipse.ecf.sync/debug/methods/exiting=false diff --git a/framework/bundles/org.eclipse.ecf.sync/.project b/framework/bundles/org.eclipse.ecf.sync/.project new file mode 100644 index 000000000..b250cebb6 --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/.project @@ -0,0 +1,34 @@ + + + org.eclipse.ecf.sync.doc + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.api.tools.apiAnalysisBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + org.eclipse.pde.api.tools.apiAnalysisNature + + diff --git a/framework/bundles/org.eclipse.ecf.sync/.settings/org.eclipse.jdt.core.prefs b/framework/bundles/org.eclipse.ecf.sync/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..4b7e2cfc4 --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +#Tue Sep 23 21:41:24 PDT 2008 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.2 +org.eclipse.jdt.core.compiler.compliance=1.4 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=warning +org.eclipse.jdt.core.compiler.problem.enumIdentifier=warning +org.eclipse.jdt.core.compiler.source=1.3 diff --git a/framework/bundles/org.eclipse.ecf.sync/.settings/org.eclipse.pde.core.prefs b/framework/bundles/org.eclipse.ecf.sync/.settings/org.eclipse.pde.core.prefs new file mode 100644 index 000000000..ae44e6131 --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,4 @@ +#Tue Sep 23 21:41:25 PDT 2008 +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/framework/bundles/org.eclipse.ecf.sync/META-INF/MANIFEST.MF b/framework/bundles/org.eclipse.ecf.sync/META-INF/MANIFEST.MF new file mode 100644 index 000000000..59f378c40 --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/META-INF/MANIFEST.MF @@ -0,0 +1,20 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %plugin.name +Bundle-SymbolicName: org.eclipse.ecf.sync +Bundle-Version: 1.0.0.qualifier +Bundle-Activator: org.eclipse.ecf.internal.sync.Activator +Bundle-Vendor: %plugin.provider +Bundle-RequiredExecutionEnvironment: J2SE-1.4 +Import-Package: org.eclipse.core.runtime;version="3.4.0", + org.eclipse.ecf.core.identity, + org.eclipse.ecf.core.util, + org.eclipse.osgi.util;version="1.1.0", + org.osgi.framework +Eclipse-LazyStart: true +Bundle-ActivationPolicy: lazy +Bundle-Localization: plugin +Export-Package: org.eclipse.ecf.internal.sync;x-internal:=true, + org.eclipse.ecf.internal.sync.doc.cola;x-internal:=true, + org.eclipse.ecf.sync.doc, + org.eclipse.ecf.sync.doc.messages diff --git a/framework/bundles/org.eclipse.ecf.sync/about.html b/framework/bundles/org.eclipse.ecf.sync/about.html new file mode 100644 index 000000000..6a60a262f --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/about.html @@ -0,0 +1,28 @@ + + + + +About + + +

About This Content

+ +

June 29, 2007

+

License

+ +

The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise +indicated below, the Content is provided to you under the terms and conditions of the +Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available +at http://www.eclipse.org/legal/epl-v10.html. +For purposes of the EPL, "Program" will mean the Content.

+ +

If you did not receive this Content directly from the Eclipse Foundation, the Content is +being redistributed by another party ("Redistributor") and different terms and conditions may +apply to your use of any object code in the Content. Check the Redistributor's license that was +provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise +indicated below, the terms and conditions of the EPL still apply to any source code in the Content +and such source code may be obtained at http://www.eclipse.org.

+ + + \ No newline at end of file diff --git a/framework/bundles/org.eclipse.ecf.sync/build.properties b/framework/bundles/org.eclipse.ecf.sync/build.properties new file mode 100644 index 000000000..fad931173 --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/build.properties @@ -0,0 +1,10 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + about.html,\ + plugin.properties +src.includes = META-INF/,\ + about.html,\ + plugin.properties,\ + src/ diff --git a/framework/bundles/org.eclipse.ecf.sync/plugin.properties b/framework/bundles/org.eclipse.ecf.sync/plugin.properties new file mode 100644 index 000000000..636946c9d --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/plugin.properties @@ -0,0 +1,12 @@ +################################################################################ +# Copyright (c) 2008 Composent, Inc. 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: +# Composent, Inc. - initial API and implementation +################################################################################ +plugin.provider = Eclipse.org +plugin.name = ECF Synchronization API \ No newline at end of file diff --git a/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/Activator.java b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/Activator.java new file mode 100644 index 000000000..3f74dee68 --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/Activator.java @@ -0,0 +1,63 @@ +package org.eclipse.ecf.internal.sync; + +import java.util.Dictionary; +import java.util.Properties; + +import org.eclipse.ecf.internal.sync.doc.cola.ColaSynchronizationStrategyFactory; +import org.eclipse.ecf.internal.sync.doc.identity.IdentitySynchronizationStrategyFactory; +import org.eclipse.ecf.sync.IServiceConstants; +import org.eclipse.ecf.sync.doc.IDocumentSynchronizationStrategyFactory; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; + +public class Activator implements BundleActivator { + + public static final String PLUGIN_ID = "org.eclipse.ecf.sync"; + + private static Activator bundle; + private ServiceRegistration colaServiceRegistration; + private ServiceRegistration identityServiceRegistration; + private BundleContext context; + + public static Activator getDefault() { + return bundle; + } + + /* + * (non-Javadoc) + * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext) + */ + public void start(BundleContext ctxt) throws Exception { + bundle = this; + this.context = ctxt; + // Register identity synchronizer service + final Dictionary identityServiceProps = new Properties(); + identityServiceProps.put(IServiceConstants.SYNCSTRATEGY_TYPE_PROPERTY, IdentitySynchronizationStrategyFactory.SYNCHSTRATEGY_TYPE); + identityServiceProps.put(IServiceConstants.SYNCSTRATEGY_PROVIDER_PROPETY, IdentitySynchronizationStrategyFactory.SYNCHSTRATEGY_PROVIDER); + identityServiceRegistration = this.context.registerService(IDocumentSynchronizationStrategyFactory.class.getName(), new IdentitySynchronizationStrategyFactory(), identityServiceProps); + // Register cola synchronizer service + final Dictionary colaServiceProps = new Properties(); + colaServiceProps.put(IServiceConstants.SYNCSTRATEGY_TYPE_PROPERTY, ColaSynchronizationStrategyFactory.SYNCHSTRATEGY_TYPE); + colaServiceProps.put(IServiceConstants.SYNCSTRATEGY_PROVIDER_PROPETY, ColaSynchronizationStrategyFactory.SYNCHSTRATEGY_PROVIDER); + colaServiceRegistration = this.context.registerService(IDocumentSynchronizationStrategyFactory.class.getName(), new ColaSynchronizationStrategyFactory(), colaServiceProps); + } + + /* + * (non-Javadoc) + * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) + */ + public void stop(BundleContext context) throws Exception { + if (colaServiceRegistration != null) { + colaServiceRegistration.unregister(); + colaServiceRegistration = null; + } + if (identityServiceRegistration != null) { + identityServiceRegistration.unregister(); + identityServiceRegistration = null; + } + this.context = null; + bundle = null; + } + +} diff --git a/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/SyncDebugOptions.java b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/SyncDebugOptions.java new file mode 100644 index 000000000..cb8dc9038 --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/SyncDebugOptions.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 2008 Composent, Inc. 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: Composent, Inc. - initial API and implementation + ******************************************************************************/ +package org.eclipse.ecf.internal.sync; + +public interface SyncDebugOptions { + + public static final String DEBUG = Activator.PLUGIN_ID + "/debug"; //$NON-NLS-1$ + + public static final String EXCEPTIONS_CATCHING = DEBUG + "/exceptions/catching"; //$NON-NLS-1$ + + public static final String EXCEPTIONS_THROWING = DEBUG + "/exceptions/throwing"; //$NON-NLS-1$ + + public static final String METHODS_ENTERING = DEBUG + "/methods/entering"; //$NON-NLS-1$ + + public static final String METHODS_EXITING = DEBUG + "/methods/exiting"; //$NON-NLS-1$ + +} diff --git a/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaDeletionTransformationStrategy.java b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaDeletionTransformationStrategy.java new file mode 100644 index 000000000..22eaa757f --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaDeletionTransformationStrategy.java @@ -0,0 +1,148 @@ +/**************************************************************************** + * 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 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.doc.messages.DocumentChangeMessage; + +public class ColaDeletionTransformationStrategy implements ColaTransformationStrategy { + + private static final long serialVersionUID = -7430435392915553959L; + private static ColaDeletionTransformationStrategy INSTANCE; + + private ColaDeletionTransformationStrategy() { + // default constructor is private to enforce singleton property via + // static factory method + } + + public static ColaTransformationStrategy getInstance() { + if (INSTANCE == null) { + INSTANCE = new ColaDeletionTransformationStrategy(); + } + return INSTANCE; + } + + public ColaDocumentChangeMessage getOperationalTransform(ColaDocumentChangeMessage remoteIncomingMsg, ColaDocumentChangeMessage localAppliedMsg, boolean localMsgHighPrio) { + + Trace.entering(Activator.PLUGIN_ID, SyncDebugOptions.METHODS_ENTERING, this.getClass(), "getOperationalTransform", new Object[] {remoteIncomingMsg, localAppliedMsg, new Boolean(localMsgHighPrio)}); //$NON-NLS-1$ + + final ColaDocumentChangeMessage remoteTransformedMsg = remoteIncomingMsg; + + if (localAppliedMsg.isDeletion()) { + final int noOpLength = 0; + + if (remoteTransformedMsg.getOffset() < localAppliedMsg.getOffset()) { + + if ((remoteTransformedMsg.getOffset() + remoteTransformedMsg.getLengthOfReplacedText()) < localAppliedMsg.getOffset()) { + + //no overlap - remote OK as is, local needs modification + localAppliedMsg.setOffset(localAppliedMsg.getOffset() - remoteTransformedMsg.getLengthOfReplacedText()); + + } else if ((remoteTransformedMsg.getOffset() + remoteTransformedMsg.getLengthOfReplacedText()) < (localAppliedMsg.getOffset() + localAppliedMsg.getLengthOfReplacedText())) { + + //remote del at lower index reaches into locally applied del, local del reaches further out + //--> shorten remote del appropriately, move and shorten local del left + remoteTransformedMsg.setLengthOfReplacedText((remoteTransformedMsg.getOffset() + remoteTransformedMsg.getLengthOfReplacedText()) - localAppliedMsg.getOffset()); + localAppliedMsg.setLengthOfReplacedText((remoteTransformedMsg.getOffset() + remoteTransformedMsg.getLengthOfReplacedText()) - localAppliedMsg.getOffset()); + localAppliedMsg.setOffset(remoteTransformedMsg.getOffset());//TODO verify! + + } else if ((remoteTransformedMsg.getOffset() + remoteTransformedMsg.getLengthOfReplacedText()) >= (localAppliedMsg.getOffset() + localAppliedMsg.getLengthOfReplacedText())) { + + //remote del at lower index, remote del fully extends over local del + //--> shorten remote by local.lengthOfReplacedText, make local no-op + remoteTransformedMsg.setLengthOfReplacedText(remoteTransformedMsg.getLengthOfReplacedText() - localAppliedMsg.getLengthOfReplacedText()); + //TODO verify whether this is equivalent to setting no-op, otherwise declare a noop boolean field for ColaDeletions + localAppliedMsg.setOffset(remoteTransformedMsg.getOffset()); + localAppliedMsg.setLengthOfReplacedText(noOpLength); + } + } else if (remoteTransformedMsg.getOffset() == localAppliedMsg.getOffset()) { + + if ((remoteTransformedMsg.getOffset() + remoteTransformedMsg.getLengthOfReplacedText()) < (localAppliedMsg.getOffset() + localAppliedMsg.getLengthOfReplacedText())) { + //start indices are equal, remote is shorter --> make remote no-op + remoteTransformedMsg.setLengthOfReplacedText(noOpLength); + //--> shorten localOp to only delete non-overlapping region + localAppliedMsg.setLengthOfReplacedText(localAppliedMsg.getLengthOfReplacedText() - remoteTransformedMsg.getLengthOfReplacedText()); + } else if ((remoteTransformedMsg.getOffset() + remoteTransformedMsg.getLengthOfReplacedText()) == (localAppliedMsg.getOffset() + localAppliedMsg.getLengthOfReplacedText())) { + //same endIndex, i.e. same deletion + //--> make remote op be no-op + remoteTransformedMsg.setLengthOfReplacedText(noOpLength); + //--> make local op appear as no-op + localAppliedMsg.setLengthOfReplacedText(noOpLength); + } else if ((remoteTransformedMsg.getOffset() + remoteTransformedMsg.getLengthOfReplacedText()) > (localAppliedMsg.getOffset() + localAppliedMsg.getLengthOfReplacedText())) { + //remote del extends over local del + //-->shorten remote del by length of local del, index/offset does not need to be updated + remoteTransformedMsg.setLengthOfReplacedText(remoteTransformedMsg.getLengthOfReplacedText() - localAppliedMsg.getLengthOfReplacedText()); + //-->make local del appear as no-op + localAppliedMsg.setLengthOfReplacedText(noOpLength); + } + } else if (remoteTransformedMsg.getOffset() > localAppliedMsg.getOffset()) { + + if (remoteTransformedMsg.getOffset() > (localAppliedMsg.getOffset() + localAppliedMsg.getLengthOfReplacedText())) { + + //move remote deletion left by length of local deletion + remoteTransformedMsg.setOffset(remoteTransformedMsg.getOffset() - localAppliedMsg.getLengthOfReplacedText()); + + } else if ((remoteTransformedMsg.getOffset() + remoteTransformedMsg.getLengthOfReplacedText()) < (localAppliedMsg.getOffset() + localAppliedMsg.getLengthOfReplacedText())) { + + //remote is fully contained in/overlapping with local del + //--> remote is no-op + remoteTransformedMsg.setLengthOfReplacedText(noOpLength); + //-->local needs to be shortened by length of remote + localAppliedMsg.setLengthOfReplacedText(localAppliedMsg.getLengthOfReplacedText() - remoteTransformedMsg.getLengthOfReplacedText()); + + } else if (remoteTransformedMsg.getOffset() < (localAppliedMsg.getOffset() + localAppliedMsg.getLengthOfReplacedText())) { + + //remote del starts within local del, but extends further + //-->shorten remote by overlap and move left to index of local del + remoteTransformedMsg.setLengthOfReplacedText(remoteTransformedMsg.getLengthOfReplacedText() - (localAppliedMsg.getOffset() + localAppliedMsg.getLengthOfReplacedText()) - remoteTransformedMsg.getOffset()); + remoteTransformedMsg.setOffset(localAppliedMsg.getOffset()); + //-->shorten local applied message + localAppliedMsg.setLengthOfReplacedText(localAppliedMsg.getLengthOfReplacedText() - (localAppliedMsg.getOffset() + localAppliedMsg.getLengthOfReplacedText()) - remoteTransformedMsg.getOffset()); + } + } + } else if (localAppliedMsg.isInsertion()) { + if (remoteTransformedMsg.getOffset() < localAppliedMsg.getOffset()) { + if ((remoteTransformedMsg.getOffset() + remoteTransformedMsg.getLengthOfReplacedText()) < localAppliedMsg.getOffset()) { + //remote remains unchanged, deletion happens fully before local insertion + //local insertion needs to be moved left by full length of deletion + localAppliedMsg.setOffset(localAppliedMsg.getOffset() - remoteTransformedMsg.getLengthOfReplacedText()); + } else if ((remoteTransformedMsg.getOffset() + remoteTransformedMsg.getLengthOfReplacedText()) >= localAppliedMsg.getOffset()) { //TODO optimize away, "if" just here for clarity, "else" would be enough + //remote deletion reaches into local insertion and potentially over it + //remote deletion needs to be split apart + final DocumentChangeMessage deletionFirstMsg = new DocumentChangeMessage(remoteTransformedMsg.getOffset(), localAppliedMsg.getOffset() - remoteTransformedMsg.getOffset(), remoteTransformedMsg.getText()); + final ColaDocumentChangeMessage deletionFirstPart = new ColaDocumentChangeMessage(deletionFirstMsg, remoteTransformedMsg.getLocalOperationsCount(), remoteTransformedMsg.getRemoteOperationsCount()); + remoteTransformedMsg.addToSplitUpRepresentation(deletionFirstPart); + + final DocumentChangeMessage deletionSecondMsg = new DocumentChangeMessage(localAppliedMsg.getOffset() + localAppliedMsg.getLengthOfInsertedText(), remoteTransformedMsg.getLengthOfReplacedText() - deletionFirstPart.getLengthOfReplacedText(), remoteTransformedMsg.getText()); + final ColaDocumentChangeMessage deletionSecondPart = new ColaDocumentChangeMessage(deletionSecondMsg, remoteTransformedMsg.getLocalOperationsCount(), remoteTransformedMsg.getRemoteOperationsCount()); + remoteTransformedMsg.addToSplitUpRepresentation(deletionSecondPart); + + remoteTransformedMsg.setSplitUp(true); + + //local insertion needs to be moved left by overlap + localAppliedMsg.setOffset(remoteTransformedMsg.getOffset()); + + } + } else if (remoteTransformedMsg.getOffset() >= localAppliedMsg.getOffset()) { + //remote del needs to be moved right by full length of insertion + remoteTransformedMsg.setOffset(remoteTransformedMsg.getOffset() + localAppliedMsg.getLengthOfInsertedText()); + } + } + + Trace.exiting(Activator.PLUGIN_ID, SyncDebugOptions.METHODS_EXITING, this.getClass(), "getOperationalTransform", null); //$NON-NLS-1$ + + return remoteTransformedMsg; + } + +} diff --git a/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaDocumentChangeMessage.java b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaDocumentChangeMessage.java new file mode 100644 index 000000000..4ab9c577f --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaDocumentChangeMessage.java @@ -0,0 +1,113 @@ +/**************************************************************************** + * 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.LinkedList; +import java.util.List; + +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.doc.messages.DocumentChangeMessage; + +public class ColaDocumentChangeMessage extends DocumentChangeMessage { + + private static final long serialVersionUID = 2038025022180647210L; + + // TODO encapsulate in a new ColaOpOriginationState and re-implement equals, + // hashCode, i.e. make comparable + private final long localOperationsCount; + private final long remoteOperationsCount; + private final ColaTransformationStrategy trafoStrat; + private boolean splitUp; + private List splitUpRepresentation; + + public ColaDocumentChangeMessage(DocumentChangeMessage msg, long localOperationsCount, long remoteOperationsCount) { + super(msg.getOffset(), msg.getLengthOfReplacedText(), msg.getText()); + this.localOperationsCount = localOperationsCount; + this.remoteOperationsCount = remoteOperationsCount; + this.splitUp = false; + this.splitUpRepresentation = new LinkedList(); + if (super.getLengthOfReplacedText() == 0) { + // this is neither a replacement, nor a deletion + trafoStrat = ColaInsertionTransformationStategy.getInstance(); + } else { + if (super.getText().length() == 0) { + // something has been replaced, nothing inserted, must be a + // deletion + trafoStrat = ColaDeletionTransformationStrategy.getInstance(); + } else { + // something has been replaced with some new input, has to be a + // replacement op + trafoStrat = ColaReplacementTransformationStategy.getInstance(); + //TODO this has not been implemented yet + //throw new IllegalArgumentException("Replacement Handling not implemented yet! Known Bug."); + } + } + } + + public boolean isInsertion() { + return (this.trafoStrat instanceof ColaInsertionTransformationStategy); + } + + public boolean isDeletion() { + return (this.trafoStrat instanceof ColaDeletionTransformationStrategy); + } + + public boolean isReplacement() { + return (this.trafoStrat instanceof ColaReplacementTransformationStategy); + } + + public long getLocalOperationsCount() { + return this.localOperationsCount; + } + + public long getRemoteOperationsCount() { + return this.remoteOperationsCount; + } + + public ColaDocumentChangeMessage transformAgainst(ColaDocumentChangeMessage localMsg, boolean localMsgHighPrio) { + Trace.entering(Activator.PLUGIN_ID, SyncDebugOptions.METHODS_ENTERING, this.getClass(), "transformAgainst", localMsg); //$NON-NLS-1$ + final ColaDocumentChangeMessage transformedMsg = trafoStrat.getOperationalTransform(this, localMsg, localMsgHighPrio); + Trace.entering(Activator.PLUGIN_ID, SyncDebugOptions.METHODS_EXITING, this.getClass(), "transformAgainst", transformedMsg); //$NON-NLS-1$ + return transformedMsg; + } + + public String toString() { + final StringBuffer buf = new StringBuffer("ColaDocumentChangeMessage["); //$NON-NLS-1$ + buf.append("text=").append(getText()).append(";offset=").append(getOffset()); //$NON-NLS-1$ //$NON-NLS-2$ + buf.append(";length=").append(getLengthOfReplacedText()).append("]"); //$NON-NLS-1$ //$NON-NLS-2$ + buf.append(";operationsCount[local=").append(getLocalOperationsCount()); //$NON-NLS-1$ + buf.append(";remote=").append(getRemoteOperationsCount()).append("]]"); //$NON-NLS-1$//$NON-NLS-2$ + return buf.toString(); + } + + public void setSplitUp(boolean toBeSplitUp) { + this.splitUp = toBeSplitUp; + } + + public boolean isSplitUp() { + return splitUp; + } + + public void setSplitUpRepresentation(List splitUpRepresentation) { + this.splitUpRepresentation = splitUpRepresentation; + } + + public List getSplitUpRepresentation() { + return splitUpRepresentation; + } + + public void addToSplitUpRepresentation(ColaDocumentChangeMessage splitUpRepresentationPart) { + this.splitUpRepresentation.add(splitUpRepresentationPart); + } +} diff --git a/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaInsertionTransformationStategy.java b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaInsertionTransformationStategy.java new file mode 100644 index 000000000..e91f728bd --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaInsertionTransformationStategy.java @@ -0,0 +1,105 @@ +/**************************************************************************** + * 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 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.doc.messages.DocumentChangeMessage; + +public class ColaInsertionTransformationStategy implements ColaTransformationStrategy { + + private static final long serialVersionUID = 5192625383622519749L; + private static ColaInsertionTransformationStategy INSTANCE; + + private ColaInsertionTransformationStategy() { + // default constructor is private to enforce singleton property via + // static factory method + } + + public static ColaTransformationStrategy getInstance() { + if (INSTANCE == null) { + INSTANCE = new ColaInsertionTransformationStategy(); + } + return INSTANCE; + } + + /** + * Resolves two conflicting ColaDocumentChangeMessages by applying an appropriate operational transform. + * + * If necessary, modifies localAppliedMsg as well to reflect knowledge of remoteIncomingMsg + * in case more conflicting/further diverging remote messages arrive. + * + * @param remoteIncomingMsg message originating from remote site, generated on same document state as localAppliedMsg + * @param localAppliedMsg message already applied to local document, generation state corresponds to that of remoteIncomingMsg + * @param localMsgHighPrio determines insertion preference for same offsets, if true localAppliedMsg comes first + * @return operational transform of remote message, not conflicting with applied local message + */ + public ColaDocumentChangeMessage getOperationalTransform(ColaDocumentChangeMessage remoteIncomingMsg, ColaDocumentChangeMessage localAppliedMsg, boolean localMsgHighPrio) { + + Trace.entering(Activator.PLUGIN_ID, SyncDebugOptions.METHODS_ENTERING, this.getClass(), "getOperationalTransform", new Object[] {remoteIncomingMsg, localAppliedMsg, new Boolean(localMsgHighPrio)}); //$NON-NLS-1$ + + final ColaDocumentChangeMessage remoteTransformedMsg = remoteIncomingMsg; + + if (localAppliedMsg.isInsertion()) { + + if (remoteTransformedMsg.getOffset() < localAppliedMsg.getOffset()) { + //coopt(remote(low),local(high)) --> (remote(low),local(low + high)) + localAppliedMsg.setOffset(localAppliedMsg.getOffset() + remoteTransformedMsg.getOffset()); + } else if (remoteTransformedMsg.getOffset() == localAppliedMsg.getOffset()) { + //coopt(remote(same),local(same)) + if (localMsgHighPrio) { + //at owner --> (remote(high),local(same)) + remoteTransformedMsg.setOffset(remoteTransformedMsg.getOffset() + localAppliedMsg.getText().length()); + } else { + //at participant --> (remote(same),local(high)) + localAppliedMsg.setOffset(localAppliedMsg.getOffset() + remoteTransformedMsg.getText().length()); + } + } else if (remoteTransformedMsg.getOffset() > localAppliedMsg.getOffset()) { + //coopt(remote(high),local(low)) --> (remote(low + high),local(low)) + remoteTransformedMsg.setOffset(remoteTransformedMsg.getOffset() + localAppliedMsg.getText().length()); + } + + } else if (localAppliedMsg.isDeletion()) { + + if (remoteTransformedMsg.getOffset() <= localAppliedMsg.getOffset()) { + + localAppliedMsg.setOffset(localAppliedMsg.getOffset() + remoteTransformedMsg.getLengthOfInsertedText()); + + } else if (remoteTransformedMsg.getOffset() > localAppliedMsg.getOffset()) { + + if (remoteTransformedMsg.getOffset() > (localAppliedMsg.getOffset() + localAppliedMsg.getLengthOfReplacedText())) { + + remoteTransformedMsg.setOffset(remoteTransformedMsg.getOffset() - localAppliedMsg.getLengthOfReplacedText()); + } else if (remoteTransformedMsg.getOffset() <= (localAppliedMsg.getOffset() + localAppliedMsg.getLengthOfReplacedText())) { + + //TODO test this ^#$%^#$ case + final DocumentChangeMessage deletionFirstMessage = new DocumentChangeMessage(localAppliedMsg.getOffset(), remoteTransformedMsg.getOffset() - localAppliedMsg.getOffset(), localAppliedMsg.getText()); + final ColaDocumentChangeMessage deletionFirstPart = new ColaDocumentChangeMessage(deletionFirstMessage, localAppliedMsg.getLocalOperationsCount(), localAppliedMsg.getRemoteOperationsCount()); + localAppliedMsg.addToSplitUpRepresentation(deletionFirstPart); + + final DocumentChangeMessage deletionSecondMessage = new DocumentChangeMessage(localAppliedMsg.getOffset() + remoteTransformedMsg.getLengthOfInsertedText(), localAppliedMsg.getLengthOfReplacedText() - deletionFirstPart.getLengthOfReplacedText(), localAppliedMsg.getText()); + final ColaDocumentChangeMessage deletionSecondPart = new ColaDocumentChangeMessage(deletionSecondMessage, localAppliedMsg.getLocalOperationsCount(), localAppliedMsg.getRemoteOperationsCount()); + localAppliedMsg.addToSplitUpRepresentation(deletionSecondPart); + + localAppliedMsg.setSplitUp(true); + + remoteTransformedMsg.setOffset(localAppliedMsg.getOffset()); + + } + } + } + + Trace.exiting(Activator.PLUGIN_ID, SyncDebugOptions.METHODS_EXITING, this.getClass(), "getOperationalTransform", remoteTransformedMsg); //$NON-NLS-1$ + return remoteTransformedMsg; + } +} diff --git a/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaReplacementTransformationStategy.java b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaReplacementTransformationStategy.java new file mode 100644 index 000000000..370c68f00 --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaReplacementTransformationStategy.java @@ -0,0 +1,44 @@ +/**************************************************************************** + * 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 org.eclipse.ecf.core.util.Trace; +import org.eclipse.ecf.internal.sync.Activator; +import org.eclipse.ecf.internal.sync.SyncDebugOptions; + +//TODO make this to be something like a marker interface, does not need to be a class +public class ColaReplacementTransformationStategy implements ColaTransformationStrategy { + + private static final long serialVersionUID = -7295023855308474804L; + private static ColaReplacementTransformationStategy INSTANCE; + + private ColaReplacementTransformationStategy() { + // default constructor is private to enforce singleton property via + // static factory method + } + + public static ColaTransformationStrategy getInstance() { + if (INSTANCE == null) { + INSTANCE = new ColaReplacementTransformationStategy(); + } + return INSTANCE; + } + + public ColaDocumentChangeMessage getOperationalTransform(ColaDocumentChangeMessage remoteMsg, ColaDocumentChangeMessage appliedLocalMsg, boolean localMsgHighPrio) { + Trace.entering(Activator.PLUGIN_ID, SyncDebugOptions.METHODS_ENTERING, this.getClass(), "getOperationalTransform", new Object[] {remoteMsg, appliedLocalMsg, new Boolean(localMsgHighPrio)}); //$NON-NLS-1$ + + Trace.exiting(Activator.PLUGIN_ID, SyncDebugOptions.METHODS_EXITING, this.getClass(), "getOperationalTransform", null); //$NON-NLS-1$ + + return null; + } + +} diff --git a/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaSynchronizationStrategy.java b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaSynchronizationStrategy.java new file mode 100644 index 000000000..96d3a44df --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaSynchronizationStrategy.java @@ -0,0 +1,220 @@ +/**************************************************************************** + * 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.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +import org.eclipse.core.runtime.Assert; +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.doc.IDocumentChange; +import org.eclipse.ecf.sync.doc.IDocumentChangeMessage; +import org.eclipse.ecf.sync.doc.IDocumentSynchronizationStrategy; +import org.eclipse.ecf.sync.doc.SerializationException; +import org.eclipse.ecf.sync.doc.messages.DocumentChangeMessage; +import org.eclipse.osgi.util.NLS; + +public class ColaSynchronizationStrategy implements IDocumentSynchronizationStrategy { + + // + private final LinkedList unacknowledgedLocalOperations; + private final boolean isInitiator; + private long localOperationsCount; + private long remoteOperationsCount; + + // + 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) { + if (sessionStrategies.get(client) == null) { + sessionStrategies.put(client, new ColaSynchronizationStrategy(isInitiator)); + } + return (ColaSynchronizationStrategy) sessionStrategies.get(client); + } + + public static void cleanUpFor(ID client) { + sessionStrategies.remove(client); + } + + public DocumentChangeMessage registerOutgoingMessage(DocumentChangeMessage localMsg) { + Trace.entering(Activator.PLUGIN_ID, SyncDebugOptions.METHODS_ENTERING, this.getClass(), "registerOutgoingMessage", localMsg); //$NON-NLS-1$ + final ColaDocumentChangeMessage colaMsg = new ColaDocumentChangeMessage(localMsg, localOperationsCount, remoteOperationsCount); + if (!colaMsg.isReplacement()) { + unacknowledgedLocalOperations.add(colaMsg); + localOperationsCount++; + } + Trace.exiting(Activator.PLUGIN_ID, SyncDebugOptions.METHODS_EXITING, this.getClass(), "registerOutgoingMessage", colaMsg); //$NON-NLS-1$ + return colaMsg; + } + + /** + * Handles proper transformation of incoming ColaDocumentChangeMessages. + * Returned DocumentChangeMessages can be applied directly to the + * shared document. The method implements the concurrency algorithm described + * in http://wiki.eclipse.org/RT_Shared_Editing + * @param remoteMsg + * @return List contains DocumentChangeMessages 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++; + //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.getFirst(); + 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.IDocumentChange) + */ + public IDocumentChangeMessage[] registerLocalChange(IDocumentChange localChange) { + Trace.entering(Activator.PLUGIN_ID, SyncDebugOptions.METHODS_ENTERING, this.getClass(), "registerLocalChange", localChange); //$NON-NLS-1$ + final ColaDocumentChangeMessage colaMsg = new ColaDocumentChangeMessage(new DocumentChangeMessage(localChange.getOffset(), localChange.getLengthOfReplacedText(), localChange.getText()), localOperationsCount, remoteOperationsCount); + if (!colaMsg.isReplacement()) { + unacknowledgedLocalOperations.add(colaMsg); + localOperationsCount++; + } + Trace.exiting(Activator.PLUGIN_ID, SyncDebugOptions.METHODS_EXITING, this.getClass(), "registerLocalChange", colaMsg); //$NON-NLS-1$ + return new IDocumentChangeMessage[] {colaMsg}; + } + + /* (non-Javadoc) + * @see org.eclipse.ecf.sync.doc.IDocumentSynchronizationStrategy#toDocumentChangeMessage(byte[]) + */ + public IDocumentChange deserializeToDocumentChange(byte[] bytes) throws SerializationException { + return DocumentChangeMessage.deserialize(bytes); + } + + /* (non-Javadoc) + * @see org.eclipse.ecf.sync.doc.IDocumentSynchronizationStrategy#transformRemoteChange(org.eclipse.ecf.sync.doc.IDocumentChangeMessage) + */ + public IDocumentChange[] transformRemoteChange(IDocumentChange remoteChange) { + if (!(remoteChange instanceof DocumentChangeMessage)) + return new IDocumentChange[] {}; + final DocumentChangeMessage m = (DocumentChangeMessage) remoteChange; + final List l = this.transformIncomingMessage(m); + return (IDocumentChange[]) l.toArray(new IDocumentChange[] {}); + } +} \ No newline at end of file diff --git a/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaSynchronizationStrategyFactory.java b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaSynchronizationStrategyFactory.java new file mode 100644 index 000000000..6ebbe0fab --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaSynchronizationStrategyFactory.java @@ -0,0 +1,39 @@ +/**************************************************************************** + * Copyright (c) 2008 Composent, Inc. 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: + * Composent, Inc. - initial API and implementation + *****************************************************************************/ + +package org.eclipse.ecf.internal.sync.doc.cola; + +import org.eclipse.ecf.core.identity.ID; +import org.eclipse.ecf.sync.doc.IDocumentSynchronizationStrategy; +import org.eclipse.ecf.sync.doc.IDocumentSynchronizationStrategyFactory; + +/** + * + */ +public class ColaSynchronizationStrategyFactory implements IDocumentSynchronizationStrategyFactory { + + public static final String SYNCHSTRATEGY_PROVIDER = "org.eclipse.ecf.internal.sync.doc.cola"; + + /* (non-Javadoc) + * @see org.eclipse.ecf.sync.doc.IDocumentSynchronizationStrategyFactory#disposeSynchronizationStragety(org.eclipse.ecf.core.identity.ID) + */ + public void disposeSynchronizationStragety(ID uniqueID) { + ColaSynchronizationStrategy.cleanUpFor(uniqueID); + } + + /* (non-Javadoc) + * @see org.eclipse.ecf.sync.doc.IDocumentSynchronizationStrategyFactory#getSyncronizationStrategy(org.eclipse.ecf.core.identity.ID, boolean) + */ + public IDocumentSynchronizationStrategy getSyncronizationStrategy(ID uniqueID, boolean isInitiator) { + return ColaSynchronizationStrategy.getInstanceFor(uniqueID, isInitiator); + } + +} diff --git a/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaTransformationStrategy.java b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaTransformationStrategy.java new file mode 100644 index 000000000..64542cfe7 --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/cola/ColaTransformationStrategy.java @@ -0,0 +1,19 @@ +/**************************************************************************** + * 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.io.Serializable; + +public interface ColaTransformationStrategy extends Serializable { + + ColaDocumentChangeMessage getOperationalTransform(ColaDocumentChangeMessage remoteIncomingMsg, ColaDocumentChangeMessage localAppliedMsg, boolean localMsgHighPrio); +} diff --git a/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/identity/IdentitySynchronizationStrategy.java b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/identity/IdentitySynchronizationStrategy.java new file mode 100644 index 000000000..796e0cd78 --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/identity/IdentitySynchronizationStrategy.java @@ -0,0 +1,46 @@ +/**************************************************************************** + * Copyright (c) 2008 Composent, Inc. 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: + * Composent, Inc. - initial API and implementation + *****************************************************************************/ + +package org.eclipse.ecf.internal.sync.doc.identity; + +import org.eclipse.ecf.sync.doc.IDocumentChange; +import org.eclipse.ecf.sync.doc.IDocumentChangeMessage; +import org.eclipse.ecf.sync.doc.IDocumentSynchronizationStrategy; +import org.eclipse.ecf.sync.doc.SerializationException; +import org.eclipse.ecf.sync.doc.messages.DocumentChangeMessage; + +/** + * + */ +public class IdentitySynchronizationStrategy implements IDocumentSynchronizationStrategy { + + /* (non-Javadoc) + * @see org.eclipse.ecf.sync.doc.IDocumentSynchronizationStrategy#deserializeToDocumentChange(byte[]) + */ + public IDocumentChange deserializeToDocumentChange(byte[] bytes) throws SerializationException { + return DocumentChangeMessage.deserialize(bytes); + } + + /* (non-Javadoc) + * @see org.eclipse.ecf.sync.doc.IDocumentSynchronizationStrategy#registerLocalChange(org.eclipse.ecf.sync.doc.IDocumentChange) + */ + public IDocumentChangeMessage[] registerLocalChange(IDocumentChange localChange) { + return new IDocumentChangeMessage[] {new DocumentChangeMessage(localChange.getOffset(), localChange.getLengthOfReplacedText(), localChange.getText())}; + } + + /* (non-Javadoc) + * @see org.eclipse.ecf.sync.doc.IDocumentSynchronizationStrategy#transformRemoteChange(org.eclipse.ecf.sync.doc.IDocumentChange) + */ + public IDocumentChange[] transformRemoteChange(IDocumentChange remoteChange) { + return new IDocumentChange[] {remoteChange}; + } + +} diff --git a/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/identity/IdentitySynchronizationStrategyFactory.java b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/identity/IdentitySynchronizationStrategyFactory.java new file mode 100644 index 000000000..534e797ba --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/internal/sync/doc/identity/IdentitySynchronizationStrategyFactory.java @@ -0,0 +1,38 @@ +/**************************************************************************** + * Copyright (c) 2008 Composent, Inc. 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: + * Composent, Inc. - initial API and implementation + *****************************************************************************/ + +package org.eclipse.ecf.internal.sync.doc.identity; + +import org.eclipse.ecf.core.identity.ID; +import org.eclipse.ecf.sync.doc.IDocumentSynchronizationStrategy; +import org.eclipse.ecf.sync.doc.IDocumentSynchronizationStrategyFactory; + +/** + * + */ +public class IdentitySynchronizationStrategyFactory implements IDocumentSynchronizationStrategyFactory { + + public static final String SYNCHSTRATEGY_PROVIDER = "org.eclipse.ecf.sync.doc.identity"; + + /* (non-Javadoc) + * @see org.eclipse.ecf.sync.doc.IDocumentSynchronizationStrategyFactory#disposeSynchronizationStragety(org.eclipse.ecf.core.identity.ID) + */ + public void disposeSynchronizationStragety(ID uniqueID) { + } + + /* (non-Javadoc) + * @see org.eclipse.ecf.sync.doc.IDocumentSynchronizationStrategyFactory#getSyncronizationStrategy(org.eclipse.ecf.core.identity.ID, boolean) + */ + public IDocumentSynchronizationStrategy getSyncronizationStrategy(ID uniqueID, boolean isInitiator) { + return new IdentitySynchronizationStrategy(); + } + +} diff --git a/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/IServiceConstants.java b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/IServiceConstants.java new file mode 100644 index 000000000..f71468a6c --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/IServiceConstants.java @@ -0,0 +1,21 @@ +/**************************************************************************** + * Copyright (c) 2008 Composent, Inc. 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: + * Composent, Inc. - initial API and implementation + *****************************************************************************/ + +package org.eclipse.ecf.sync; + +/** + * + */ +public interface IServiceConstants { + public static final String SYNCSTRATEGY_TYPE_PROPERTY = "org.eclipse.ecf.type"; + public static final String SYNCSTRATEGY_PROVIDER_PROPETY = "org.eclipse.ecf.sync.provider"; + +} diff --git a/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/IDocumentChange.java b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/IDocumentChange.java new file mode 100644 index 000000000..8743b7c59 --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/IDocumentChange.java @@ -0,0 +1,37 @@ +/**************************************************************************** + * Copyright (c) 2008 Composent, Inc. 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: + * Composent, Inc. - initial API and implementation + *****************************************************************************/ + +package org.eclipse.ecf.sync.doc; + +/** + * Local document change. Instances of this class represent + * local changes to a replicated document. + */ +public interface IDocumentChange { + /** + * Get offset in document where change has or will occur. + * @return int the offset + */ + public int getOffset(); + + /** + * Get length of text that was replaced. + * @return length of replaced text + */ + public int getLengthOfReplacedText(); + + /** + * Get the new text. + * @return String text. Will not return null, but + * may return empty string. + */ + public String getText(); +} diff --git a/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/IDocumentChangeMessage.java b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/IDocumentChangeMessage.java new file mode 100644 index 000000000..d3d2d8cd1 --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/IDocumentChangeMessage.java @@ -0,0 +1,23 @@ +/**************************************************************************** + * Copyright (c) 2008 Composent, Inc. 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: + * Composent, Inc. - initial API and implementation + *****************************************************************************/ + +package org.eclipse.ecf.sync.doc; + +/** + * Document change message. Instances of this interface + * may be serialized to a byte [] so that they can be + * communicated to remote processes. + */ +public interface IDocumentChangeMessage { + + public byte[] toByteArray() throws SerializationException; + +} diff --git a/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/IDocumentSynchronizationStrategy.java b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/IDocumentSynchronizationStrategy.java new file mode 100644 index 000000000..776e66861 --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/IDocumentSynchronizationStrategy.java @@ -0,0 +1,58 @@ +/**************************************************************************** + * Copyright (c) 2008 Composent, Inc. 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: + * Composent, Inc. - initial API and implementation + *****************************************************************************/ + +package org.eclipse.ecf.sync.doc; + +/** + * Document synchronization strategy. Instances implementing this interface + * expose the ability for clients to synchronize combinations of local + * and remote documents. + *

+ * Owners that wish to synchronize local and remote changes + * should call {@link #registerLocalChange(IDocumentChange)} when local + * changes occur, and then serialize returned {@link IDocumentChangeMessage}s and + * deliver the change message to remotes. When remote change messages are received, + * they should first be deserized via {@link #deserializeToDocumentChange(byte[])}, and then + * passed to {@link #transformRemoteChange(IDocumentChange)} to transform + * the change so that when the returned IDocumentChanges are applied to the local + * document its state will be consistent with other client(s). + */ +public interface IDocumentSynchronizationStrategy { + + /** + * Register local document change with document synchronization strategy. This method + * should be synchronously called when a local change has + * been made to the underlying document. + * @param localChange the IDocumentChange made to the local document + * @return IDocumentChangeMessage[] an array of document change message to be + * delivered to remote participants. + */ + public IDocumentChangeMessage[] registerLocalChange(IDocumentChange localChange); + + /** + * Transform remote document change into a set of local document changes to + * be synchronously applied to the local document. + * @param remoteChange the remote document change instance to + * be transformed by this synchronization strategy. + * @return IDocumentChange[] to apply to local document + */ + public IDocumentChange[] transformRemoteChange(IDocumentChange remoteChange); + + /** + * Deserialization of given byte array to concrete instance of + * IDocumentChange object to represent local change to be applied + * + * @param bytes the bytes to be deserialized + * @return IDocumentChange instance from bytes. Will not be null. + * @throws SerializationException thrown if some problem deserializing given bytes. + */ + public IDocumentChange deserializeToDocumentChange(byte[] bytes) throws SerializationException; +} diff --git a/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/IDocumentSynchronizationStrategyFactory.java b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/IDocumentSynchronizationStrategyFactory.java new file mode 100644 index 000000000..f0f1967f2 --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/IDocumentSynchronizationStrategyFactory.java @@ -0,0 +1,39 @@ +/**************************************************************************** + * Copyright (c) 2008 Composent, Inc. 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: + * Composent, Inc. - initial API and implementation + *****************************************************************************/ + +package org.eclipse.ecf.sync.doc; + +import org.eclipse.ecf.core.identity.ID; + +/** + * Factory for creating {@link IDocumentSynchronizationStrategy} instances for + * a uniquely identified entity. This interface is exposed as a service and + * provides an entry point for clients. + */ +public interface IDocumentSynchronizationStrategyFactory { + + public static final String SYNCHSTRATEGY_TYPE = "org.eclipse.ecf.sync.doc"; + + /** + * Get an IDocumentSynchronizationStrategy for a unique ID. Should not be null. + * @param uniqueID the uniqueID to identify the client of the {@link IDocumentSynchronizationStrategy}. + * @param isInitiator whether the client is the initiator of the + * shared editing, or the receiver. + * @return IDocumentSynchronizationStrategy for the given uniqueID. + */ + public IDocumentSynchronizationStrategy getSyncronizationStrategy(ID uniqueID, boolean isInitiator); + + /** + * Clean up the synchronization strategy caching for a given uniqueID. Should not be null. + * @param uniqueID the ID of the + */ + public void disposeSynchronizationStragety(ID uniqueID); +} diff --git a/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/SerializationException.java b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/SerializationException.java new file mode 100644 index 000000000..e477717b7 --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/SerializationException.java @@ -0,0 +1,58 @@ +/**************************************************************************** + * Copyright (c) 2008 Composent, Inc. 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: + * Composent, Inc. - initial API and implementation + *****************************************************************************/ + +package org.eclipse.ecf.sync.doc; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.ecf.core.util.ECFException; +import org.eclipse.ecf.internal.sync.Activator; + +/** + * + */ +public class SerializationException extends ECFException { + + private static final long serialVersionUID = -8702959540799683251L; + + /** + * @param message + * message associated with exception + */ + public SerializationException(String message) { + this(message, null); + } + + /** + * @param cause + * the cause of the new exception + */ + public SerializationException(Throwable cause) { + this(null, cause); + } + + /** + * @param message + * @param cause + */ + public SerializationException(String message, Throwable cause) { + this(new Status(IStatus.ERROR, Activator.PLUGIN_ID, 0, ((message == null) ? "" : message), cause)); //$NON-NLS-1$ + } + + /** + * @param status + * the status for th + */ + public SerializationException(IStatus status) { + super(status); + } + +} diff --git a/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/messages/DocumentChangeMessage.java b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/messages/DocumentChangeMessage.java new file mode 100644 index 000000000..ae75fa90c --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.sync/src/org/eclipse/ecf/sync/doc/messages/DocumentChangeMessage.java @@ -0,0 +1,109 @@ +/**************************************************************************** + * Copyright (c) 2007, 2008 Composent, Inc. 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: + * Composent, Inc. - initial API and implementation + * Mustafa K. Isik + *****************************************************************************/ + +package org.eclipse.ecf.sync.doc.messages; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import org.eclipse.ecf.sync.doc.IDocumentChange; +import org.eclipse.ecf.sync.doc.IDocumentChangeMessage; +import org.eclipse.ecf.sync.doc.SerializationException; + +/** + * + */ +public class DocumentChangeMessage implements IDocumentChange, IDocumentChangeMessage { + + private static final long serialVersionUID = -3195542805471664496L; + + public static DocumentChangeMessage deserialize(byte[] bytes) throws SerializationException { + try { + final ByteArrayInputStream bins = new ByteArrayInputStream(bytes); + final ObjectInputStream oins = new ObjectInputStream(bins); + return (DocumentChangeMessage) oins.readObject(); + } catch (final Exception e) { + throw new SerializationException("Exception deserializing DocumentChangeMessage", e); + } + } + + final String text; + int offset; + int length; + + public DocumentChangeMessage(int offset, int length, String text) { + this.offset = offset; + this.length = length; + this.text = text; + } + + /** + * Returns the modification index of the operation resembled by this + * message. + * + * @return modification index + */ + public int getOffset() { + return offset; + } + + public void setOffset(int updatedOffset) { + this.offset = updatedOffset; + } + + /** + * Returns the length of replaced text. + * + * @return length of replaced text + */ + public int getLengthOfReplacedText() { + return length; + } + + public void setLengthOfReplacedText(int length) { + this.length = length; + } + + public String getText() { + return text; + } + + public int getLengthOfInsertedText() { + return this.text.length(); + } + + public String toString() { + final StringBuffer buf = new StringBuffer("DocumentChangeMessage["); //$NON-NLS-1$ + buf.append("text=").append(text).append(";offset=").append(offset); //$NON-NLS-1$ //$NON-NLS-2$ + buf.append(";length=").append(length).append("]"); //$NON-NLS-1$ //$NON-NLS-2$ + return buf.toString(); + } + + private byte[] serialize() throws IOException { + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + final ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(this); + return bos.toByteArray(); + } + + public byte[] toByteArray() throws SerializationException { + try { + return serialize(); + } catch (final IOException e) { + throw new SerializationException("Exception serializing DocumentChangeMessage", e); + } + } + +} -- cgit v1.2.3