/** * 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.document.operation.impl; import java.util.ArrayList; import java.util.List; import org.waveprotocol.wave.model.document.operation.AnnotationBoundaryMap; import org.waveprotocol.wave.model.document.operation.util.ImmutableUpdateMap; import org.waveprotocol.wave.model.util.Preconditions; public class AnnotationsUpdateImpl extends ImmutableUpdateMap<AnnotationsUpdateImpl, AnnotationsUpdate> implements AnnotationsUpdate { public static final AnnotationsUpdateImpl EMPTY_MAP = new AnnotationsUpdateImpl(); public AnnotationsUpdateImpl() {} private AnnotationsUpdateImpl(List<AttributeUpdate> updates) { super(updates); } @Override protected AnnotationsUpdateImpl createFromList(List<AttributeUpdate> updates) { return new AnnotationsUpdateImpl(updates); } /** * A string that is larger (according to compareTo) than any valid annotation key. */ private static final String MAX_STRING = "\uFFFF"; @Override public AnnotationsUpdateImpl composeWith(AnnotationBoundaryMap map) { List<AttributeUpdate> newUpdates = new ArrayList<AttributeUpdate>(); int existingIndex = 0; int changeIndex = 0; int endIndex = 0; while (existingIndex < updates.size() || changeIndex < map.changeSize() || endIndex < map.endSize()) { String existingKey = existingIndex < updates.size() ? updates.get(existingIndex).name : MAX_STRING; String changeKey = changeIndex < map.changeSize() ? map.getChangeKey(changeIndex) : MAX_STRING; String endKey = endIndex < map.endSize() ? map.getEndKey(endIndex) : MAX_STRING; // cases: // existingKey < endKey && existingKey < changeKey: keep, advance existing // existingKey < endKey && existingKey = changeKey: replace, advance existing & change // existingKey < endKey && existingKey > changeKey: add change, advance change // existingKey = endKey && existingKey < changeKey: remove, advance existing & end // existingKey = endKey && existingKey = changeKey: error (key in both change and end) // existingKey = endKey && existingKey > changeKey: remove, add change, advance all 3 // existingKey > endKey: error (attempt to end key that is not part of the update) int existingVsEnd = existingKey.compareTo(endKey); int existingVsChange = existingKey.compareTo(changeKey); if (existingVsEnd < 0) { if (existingVsChange < 0) { newUpdates.add(updates.get(existingIndex)); existingIndex++; } else if (existingVsChange == 0) { newUpdates.add(new AttributeUpdate(changeKey, map.getOldValue(changeIndex), map.getNewValue(changeIndex))); existingIndex++; changeIndex++; } else if (existingVsChange > 0) { newUpdates.add(new AttributeUpdate(changeKey, map.getOldValue(changeIndex), map.getNewValue(changeIndex))); changeIndex++; } else { assert false; } } else if (existingVsEnd == 0) { if (existingVsChange < 0) { existingIndex++; endIndex++; } else if (existingVsChange == 0) { Preconditions.illegalArgument("AnnotationBoundaryMap with key both in change and end: " + changeKey); } else if (existingVsChange > 0) { newUpdates.add(new AttributeUpdate(changeKey, map.getOldValue(changeIndex), map.getNewValue(changeIndex))); existingIndex++; endIndex++; changeIndex++; } else { assert false; } } else if (existingVsEnd > 0) { Preconditions.illegalArgument("Attempt to end key that is not part of the update: " + endKey); } else { assert false; } } return createFromList(newUpdates); } public boolean containsKey(String key) { Preconditions.checkNotNull(key, "Null key"); // TODO: Use Arrays.binarySearch(a, key, c). for (AttributeUpdate u : updates) { if (key.equals(u.name)) { return true; } } return false; } }