/** * Copyright 2009 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; import org.waveprotocol.wave.model.document.operation.AnnotationBoundaryMap; import org.waveprotocol.wave.model.document.operation.AttributesUpdate; import org.waveprotocol.wave.model.document.operation.BufferedDocOp; import org.waveprotocol.wave.model.document.operation.DocInitialization; import org.waveprotocol.wave.model.document.operation.DocOpComponentType; import org.waveprotocol.wave.model.document.operation.impl.DocOpUtil; import org.waveprotocol.wave.model.operation.wave.AddParticipant; import org.waveprotocol.wave.model.operation.wave.NoOp; import org.waveprotocol.wave.model.operation.wave.RemoveParticipant; import org.waveprotocol.wave.model.operation.wave.WaveletDocumentOperation; import org.waveprotocol.wave.model.operation.wave.WaveletOperation; import org.waveprotocol.wave.model.util.Preconditions; import java.util.Map; /** * Utilities for comparing operations. */ public class OpComparators { private OpComparators() {} // Proper equators would also provide hash codes. public interface OpEquator { boolean equalNullable(BufferedDocOp a, BufferedDocOp b); boolean equal(BufferedDocOp a, BufferedDocOp b); boolean equalNullable(WaveletOperation a, WaveletOperation b); boolean equal(WaveletOperation a, WaveletOperation b); } public abstract static class AbstractOpEquator implements OpEquator { @Override public boolean equal(WaveletOperation a, WaveletOperation b) { Preconditions.checkNotNull(a, "First argument is null"); Preconditions.checkNotNull(b, "Second argument is null"); return equalNullable(a, b); } @Override public boolean equalNullable(WaveletOperation a, WaveletOperation b) { if (a == null) { return b == null; } else if (a instanceof AddParticipant) { return (b instanceof AddParticipant) && ((AddParticipant) a).getParticipantId().equals( ((AddParticipant) b).getParticipantId()); } else if (a instanceof RemoveParticipant) { return (b instanceof RemoveParticipant) && ((RemoveParticipant) a).getParticipantId().equals( ((RemoveParticipant) b).getParticipantId()); } else if (a instanceof NoOp) { return b instanceof NoOp; } else if (a instanceof WaveletDocumentOperation) { if (!(b instanceof WaveletDocumentOperation)) { return false; } WaveletDocumentOperation a1 = (WaveletDocumentOperation) a; WaveletDocumentOperation b1 = (WaveletDocumentOperation) b; return (a1.getDocumentId().equals(b1.getDocumentId()) && equal(a1.getOperation(), b1.getOperation())); } else { throw new AssertionError("Unexpected WaveletOperation subtype: " + a); } } } public static final OpEquator SYNTACTIC_IDENTITY = new AbstractOpEquator() { @Override public boolean equal(BufferedDocOp a, BufferedDocOp b) { Preconditions.checkNotNull(a, "First argument is null"); Preconditions.checkNotNull(b, "Second argument is null"); return equalNullable(a, b); } @Override public boolean equalNullable(BufferedDocOp a, BufferedDocOp b) { if (a == null) { return b == null; } if (b == null) { return false; } if (a.size() != b.size()) { return false; } for (int i = 0; i < a.size(); ++i) { if (!equalComponent(a, b, i)) { return false; } } return true; } private boolean equalComponent(BufferedDocOp a, BufferedDocOp b, int i) { DocOpComponentType type = a.getType(i); if (type != b.getType(i)) { return false; } if (type == DocOpComponentType.ANNOTATION_BOUNDARY) { return equal(a.getAnnotationBoundary(i), b.getAnnotationBoundary(i)); } else if (type == DocOpComponentType.CHARACTERS) { return equal(a.getCharactersString(i), b.getCharactersString(i)); } else if (type == DocOpComponentType.ELEMENT_START) { return equal(a.getElementStartTag(i), b.getElementStartTag(i)) && equal(a.getElementStartAttributes(i), b.getElementStartAttributes(i)); } else if (type == DocOpComponentType.ELEMENT_END) { return true; // ignored } else if (type == DocOpComponentType.RETAIN) { return a.getRetainItemCount(i) == b.getRetainItemCount(i); } else if (type == DocOpComponentType.DELETE_CHARACTERS) { return equal(a.getDeleteCharactersString(i), b.getDeleteCharactersString(i)); } else if (type == DocOpComponentType.DELETE_ELEMENT_START) { return equal(a.getDeleteElementStartTag(i), b.getDeleteElementStartTag(i)) && equal(a.getDeleteElementStartAttributes(i), b.getDeleteElementStartAttributes(i)); } else if (type == DocOpComponentType.DELETE_ELEMENT_END) { return true; // ignored } else if (type == DocOpComponentType.REPLACE_ATTRIBUTES) { return equal(a.getReplaceAttributesOldAttributes(i), b.getReplaceAttributesOldAttributes(i)) && equal(a.getReplaceAttributesNewAttributes(i), b.getReplaceAttributesNewAttributes(i)); } else if (type == DocOpComponentType.UPDATE_ATTRIBUTES) { return equal(a.getUpdateAttributesUpdate(i), b.getUpdateAttributesUpdate(i)); } else { throw new IllegalArgumentException("unsupported DocOpComponentType: " + type); } } private boolean equal(AnnotationBoundaryMap a, AnnotationBoundaryMap b) { int changeSize = a.changeSize(); if (changeSize != b.changeSize()) { return false; } for (int i = 0; i < changeSize; ++i) { if (!equal(a.getChangeKey(i), b.getChangeKey(i))) { return false; } if (!equalNullable(a.getOldValue(i), b.getOldValue(i))) { return false; } if (!equalNullable(a.getNewValue(i), b.getNewValue(i))) { return false; } } int endSize = a.endSize(); if (endSize != b.endSize()) { return false; } for (int i = 0; i < endSize; ++i) { if (!equal(a.getEndKey(i), b.getEndKey(i))) { return false; } } return true; } private boolean equalNullable(String a, String b) { if (a == null) { return b == null; } if (b == null) { return false; } return equal(a, b); } private boolean equal(String a, String b) { return a.equals(Preconditions.checkNotNull(b, "b")); } private boolean equal(Map<String, String> a, Map<String, String> b) { return a.equals(Preconditions.checkNotNull(b, "b")); } private boolean equal(AttributesUpdate a, AttributesUpdate b) { int changeSize = a.changeSize(); if (changeSize != b.changeSize()) { return false; } for (int i = 0; i < changeSize; ++i) { if (!equal(a.getChangeKey(i), b.getChangeKey(i))) { return false; } if (!equalNullable(a.getOldValue(i), b.getOldValue(i))) { return false; } if (!equalNullable(a.getNewValue(i), b.getNewValue(i))) { return false; } } return true; } }; public static boolean equalDocuments(DocInitialization a, DocInitialization b) { return DocOpUtil.toXmlString(a).equals(DocOpUtil.toXmlString(b)); } }