/**
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.waveprotocol.wave.model.operation.testing.reference;
import org.waveprotocol.wave.model.document.operation.AnnotationBoundaryMap;
import org.waveprotocol.wave.model.document.operation.Attributes;
import org.waveprotocol.wave.model.document.operation.AttributesUpdate;
import org.waveprotocol.wave.model.document.operation.DocOp;
import org.waveprotocol.wave.model.document.operation.EvaluatingDocOpCursor;
import org.waveprotocol.wave.model.document.operation.algorithm.RangeNormalizer;
import org.waveprotocol.wave.model.document.operation.impl.DocOpBuffer;
import org.waveprotocol.wave.model.operation.OperationPair;
import org.waveprotocol.wave.model.operation.TransformException;
import org.waveprotocol.wave.model.operation.testing.reference.PositionTracker.RelativePosition;
/**
* A utility class for transforming insertion operations.
*
* @author Alexandre Mah
*/
final class InsertionInsertionTransformer {
/**
* A target of a document mutation which can be used to transform document
* mutations by making use primarily of information from one mutation with the
* help of auxiliary information from a second mutation. These targets should
* be used in pairs.
*/
private static final class Target implements EvaluatingDocOpCursor<DocOp> {
/**
* The target to which to write the transformed mutation.
*/
private final EvaluatingDocOpCursor<DocOp> targetDocument =
new RangeNormalizer<DocOp>(new DocOpBuffer());
/**
* The position of the processing cursor associated with this target
* relative to the position of the processing cursor associated to the
* opposing target. All positional calculations are based on cursor
* positions in the original document on which the two original operations
* apply.
*/
private final RelativePosition relativePosition;
/**
* The target that is used opposite this target in the transformation.
*/
private Target otherTarget;
Target(RelativePosition relativePosition) {
this.relativePosition = relativePosition;
}
// TODO: See if we can remove this explicit method and find a
// better way to do this using a constructor or factory.
public void setOtherTarget(Target otherTarget) {
this.otherTarget = otherTarget;
}
@Override
public DocOp finish() {
return targetDocument.finish();
}
@Override
public void retain(int itemCount) {
int oldPosition = relativePosition.get();
relativePosition.increase(itemCount);
if (relativePosition.get() < 0) {
targetDocument.retain(itemCount);
otherTarget.targetDocument.retain(itemCount);
} else if (oldPosition < 0) {
targetDocument.retain(-oldPosition);
otherTarget.targetDocument.retain(-oldPosition);
}
}
@Override
public void characters(String chars) {
targetDocument.characters(chars);
otherTarget.targetDocument.retain(chars.length());
}
@Override
public void elementStart(String tag, Attributes attrs) {
targetDocument.elementStart(tag, attrs);
otherTarget.targetDocument.retain(1);
}
@Override
public void elementEnd() {
targetDocument.elementEnd();
otherTarget.targetDocument.retain(1);
}
@Override
public void deleteCharacters(String chars) {
throw new UnsupportedOperationException("This method should never be called.");
}
@Override
public void deleteElementStart(String tag, Attributes attrs) {
throw new UnsupportedOperationException("This method should never be called.");
}
@Override
public void deleteElementEnd() {
throw new UnsupportedOperationException("This method should never be called.");
}
@Override
public void replaceAttributes(Attributes oldAttrs, Attributes newAttrs) {
throw new UnsupportedOperationException("This method should never be called.");
}
@Override
public void updateAttributes(AttributesUpdate attrUpdate) {
throw new UnsupportedOperationException("This method should never be called.");
}
@Override
public void annotationBoundary(AnnotationBoundaryMap map) {
throw new UnsupportedOperationException("This method should never be called.");
}
}
/**
* Transforms a pair of insertion operations.
*
* @param clientOp the operation from the client
* @param serverOp the operation from the server
* @return the transformed pair of operations
* @throws TransformException if a problem was encountered during the
* transformation process
*/
OperationPair<DocOp> transformOperations(DocOp clientOp,
DocOp serverOp) throws TransformException {
PositionTracker positionTracker = new PositionTracker();
RelativePosition clientPosition = positionTracker.getPosition1();
RelativePosition serverPosition = positionTracker.getPosition2();
// The target responsible for processing components of the client operation.
Target clientTarget = new Target(clientPosition);
// The target responsible for processing components of the server operation.
Target serverTarget = new Target(serverPosition);
clientTarget.setOtherTarget(serverTarget);
serverTarget.setOtherTarget(clientTarget);
// Incrementally apply the two operations in a linearly-ordered interleaving
// fashion.
int clientIndex = 0;
int serverIndex = 0;
while (clientIndex < clientOp.size()) {
clientOp.applyComponent(clientIndex++, clientTarget);
while (clientPosition.get() > 0) {
if (serverIndex >= serverOp.size()) {
throw new TransformException("Ran out of " + serverOp.size()
+ " server op components after " + clientIndex + " of " + clientOp.size()
+ " client op components, with " + clientPosition.get() + " spare positions");
}
serverOp.applyComponent(serverIndex++, serverTarget);
}
}
while (serverIndex < serverOp.size()) {
serverOp.applyComponent(serverIndex++, serverTarget);
}
clientOp = clientTarget.finish();
serverOp = serverTarget.finish();
return new OperationPair<DocOp>(clientOp, serverOp);
}
}