/**
* 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 org.waveprotocol.wave.model.document.operation.AnnotationBoundaryMap;
import org.waveprotocol.wave.model.util.Preconditions;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* A simple, immutable implementation of {@link AnnotationBoundaryMap} that
* does almost all necessary validation checks.
*/
// TODO: Validate key characters better.
// Should probably only allow whatever is allowed in an xml attribute, + "/"
// TODO: Keep these in sorted order?
public class AnnotationBoundaryMapImpl implements AnnotationBoundaryMap {
private static final String[] EMPTY_ARRAY = new String[0];
public static final AnnotationBoundaryMapImpl EMPTY_MAP = builder().build();
private final String[] changeKeys;
private final String[] changeOldValues;
private final String[] changeNewValues;
private final String[] endKeys;
public static final class Builder {
private String[] changeKeys = EMPTY_ARRAY;
private String[] changeOldValues = EMPTY_ARRAY;
private String[] changeNewValues = EMPTY_ARRAY;
private String[] endKeys = EMPTY_ARRAY;
private Builder() {}
public AnnotationBoundaryMapImpl build() {
return new AnnotationBoundaryMapImpl(endKeys, changeKeys, changeOldValues, changeNewValues);
}
public Builder initializationEnd(String ... keys) {
endKeys = keys;
return this;
}
public Builder initializationValues(String ... pairs) {
if (pairs.length % 2 != 0) {
throw new IllegalArgumentException("pairs must be even in size");
}
String[] keys = new String[pairs.length / 2];
String[] values = new String[pairs.length / 2];
for (int i = 0; i < keys.length; i++) {
keys[i] = pairs[i * 2];
values[i] = pairs[i * 2 + 1];
}
changeKeys = keys;
changeOldValues = new String[keys.length];
changeNewValues = values;
return this;
}
public Builder updateValues(String[] keys, String[] oldValues, String[] newValues) {
Preconditions.checkArgument(keys.length == oldValues.length
&& keys.length == oldValues.length, "Parallel arrays must have same length");
changeKeys = keys;
changeOldValues = oldValues;
changeNewValues = newValues;
return this;
}
public Builder updateValues(String ... triplets) {
if (triplets.length % 3 != 0) {
throw new IllegalArgumentException("Triplets must be a multiple of 3 in size");
}
String[] keys = new String[triplets.length / 3];
String[] oldValues = new String[triplets.length / 3];
String[] newValues = new String[triplets.length / 3];
for (int i = 0; i < keys.length; i++) {
keys[i] = triplets[i * 3];
oldValues[i] = triplets[i * 3 + 1];
newValues[i] = triplets[i * 3 + 2];
}
return updateValues(keys, oldValues, newValues);
}
}
public static Builder builder() {
return new Builder();
}
public AnnotationBoundaryMapImpl(String[] endKeys, String[] changeKeys,
String[] changeOldValues, String[] changeNewValues) {
if (changeKeys.length != changeOldValues.length ||
changeKeys.length != changeNewValues.length) {
throw new IllegalArgumentException(
"Change keys, new values, and old values sizes don't match");
}
Set<String> changeKeySet = new HashSet<String>(Arrays.asList(changeKeys));
Set<String> endKeySet = new HashSet<String>(Arrays.asList(endKeys));
if (changeKeySet.size() != changeKeys.length || endKeySet.size() != endKeys.length) {
throw new IllegalArgumentException("Keys must all be unique");
}
if (changeKeySet.contains(null) || endKeySet.contains(null)) {
throw new NullPointerException("Null keys are not permitted");
}
if (changeKeySet.contains("") || endKeySet.contains("")) {
throw new IllegalArgumentException("Empty-string keys are not permitted");
}
for (String changeKey : changeKeys) {
validateAnnotationKey(changeKey);
}
for (String endKey : endKeys) {
validateAnnotationKey(endKey);
if (changeKeySet.contains(endKey)) {
throw new IllegalArgumentException("Change keys and end keys must be disjoint sets");
}
}
this.changeKeys = copy(changeKeys);
this.changeOldValues = copy(changeOldValues);
this.changeNewValues = copy(changeNewValues);
this.endKeys = copy(endKeys);
}
/**
* Copies an array. GWT does not seem to support Arrays.copyOf()
*/
private String[] copy(String[] input) {
String[] ret = new String[input.length];
for (int i = 0; i < input.length; i++) {
ret[i] = input[i];
}
return ret;
}
public static void validateAnnotationKey(String key) throws IllegalArgumentException {
if (key.contains("?") || key.contains("@")) {
throw new IllegalArgumentException(
"Annotation keys must not contain the '?' or '@' characters");
}
}
@Override
public int changeSize() {
return changeKeys.length;
}
@Override
public int endSize() {
return endKeys.length;
}
@Override
public String getChangeKey(int changeIndex) {
return changeKeys[changeIndex];
}
@Override
public String getNewValue(int changeIndex) {
return changeNewValues[changeIndex];
}
@Override
public String getOldValue(int changeIndex) {
return changeOldValues[changeIndex];
}
@Override
public String getEndKey(int endIndex) {
return endKeys[endIndex];
}
@Override
public String toString() {
return DocOpUtil.toConciseString(this);
}
}