/** * 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.util; import org.waveprotocol.wave.model.util.Pair; import org.waveprotocol.wave.model.util.Preconditions; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; public abstract class ImmutableUpdateMap<T extends ImmutableUpdateMap<T, U>, U extends UpdateMap> implements UpdateMap { public static class AttributeUpdate { public final String name; final String oldValue; final String newValue; public AttributeUpdate(String name, String oldValue, String newValue) { Preconditions.checkNotNull(name, "Null name in AttributeUpdate"); this.name = name; this.oldValue = oldValue; this.newValue = newValue; } @Override public String toString() { return "[" + name + ": " + oldValue + " -> " + newValue + "]"; } } protected final List<AttributeUpdate> updates; @Override public int changeSize() { return updates.size(); } @Override public String getChangeKey(int i) { return updates.get(i).name; } @Override public String getOldValue(int i) { return updates.get(i).oldValue; } @Override public String getNewValue(int i) { return updates.get(i).newValue; } public ImmutableUpdateMap() { updates = Collections.emptyList(); } public ImmutableUpdateMap(String ... triples) { Preconditions.checkArgument(triples.length % 3 == 0, "Triples must come in groups of three"); ArrayList<AttributeUpdate> accu = new ArrayList<AttributeUpdate>(triples.length / 3); for (int i = 0; i < triples.length; i += 3) { Preconditions.checkNotNull(triples[i], "Null key"); accu.add(new AttributeUpdate(triples[i], triples[i + 1], triples[i + 2])); } Collections.sort(accu, comparator); for (int i = 1; i < accu.size(); i++) { int x = comparator.compare(accu.get(i - 1), accu.get(i)); if (x == 0) { throw new IllegalArgumentException("Duplicate key: " + accu.get(i).name); } assert x < 0; } updates = accu; } public ImmutableUpdateMap(Map<String, Pair<String, String>> updates) { this(tripletsFromMap(updates)); } private static String[] tripletsFromMap(Map<String, Pair<String, String>> updates) { String[] triplets = new String[updates.size() * 3]; int i = 0; for (Map.Entry<String, Pair<String, String>> e : updates.entrySet()) { triplets[i++] = e.getKey(); triplets[i++] = e.getValue().getFirst(); triplets[i++] = e.getValue().getSecond(); } return triplets; } protected ImmutableUpdateMap(List<AttributeUpdate> updates) { this.updates = updates; } public T exclude(Collection<String> names) { List<AttributeUpdate> newAttributes = new ArrayList<AttributeUpdate>(); for (AttributeUpdate update : updates) { if (!names.contains(update.name)) { newAttributes.add(update); } } return createFromList(newAttributes); } protected static final Comparator<AttributeUpdate> comparator = new Comparator<AttributeUpdate>() { @Override public int compare(AttributeUpdate a, AttributeUpdate b) { return a.name.compareTo(b.name); } }; public T composeWith(U mutation) { List<AttributeUpdate> newAttributes = new ArrayList<AttributeUpdate>(); Iterator<AttributeUpdate> iterator = updates.iterator(); AttributeUpdate nextAttribute = iterator.hasNext() ? iterator.next() : null; // TODO: Have a slow path when the cast would fail. List<AttributeUpdate> mutationAttributes = ((ImmutableUpdateMap<?,?>) mutation).updates; loop: for (AttributeUpdate attribute : mutationAttributes) { while (nextAttribute != null) { int comparison = comparator.compare(attribute, nextAttribute); if (comparison < 0) { break; } else if (comparison > 0) { newAttributes.add(nextAttribute); nextAttribute = iterator.hasNext() ? iterator.next() : null; } else { if (!areEqual(nextAttribute.newValue, attribute.oldValue)) { Preconditions.illegalArgument( "Mismatched old value: attempt to update " + nextAttribute + " with " + attribute); } newAttributes.add(new AttributeUpdate(attribute.name, nextAttribute.oldValue, attribute.newValue)); nextAttribute = iterator.hasNext() ? iterator.next() : null; continue loop; } } newAttributes.add(attribute); } if (nextAttribute != null) { newAttributes.add(nextAttribute); while (iterator.hasNext()) { newAttributes.add(iterator.next()); } } return createFromList(newAttributes); } protected abstract T createFromList(List<AttributeUpdate> attributes); // TODO: Is there a utility method for this somewhere? private boolean areEqual(Object a, Object b) { return (a == null) ? b == null : a.equals(b); } @Override public String toString() { return "Updates: " + updates; } public static void checkUpdatesSorted(List<AttributeUpdate> updates) { AttributeUpdate previous = null; for (AttributeUpdate u : updates) { Preconditions.checkNotNull(u, "Null attribute update"); assert u.name != null; if (previous != null && previous.name.compareTo(u.name) >= 0) { Preconditions.illegalArgument( "Attribute keys not strictly monotonic: " + previous.name + ", " + u.name); } previous = u; } } }