checkConsistent();
ViolationCollector v = new ViolationCollector();
DocOpValidator.validate(v, schemaConstraints, this, m);
if (!v.isValid()) {
throw new OperationException("Validation failed: " + v);
}
inconsistent = true;
annotationUpdates = AnnotationsUpdateImpl.EMPTY_MAP;
final ListIterator<Item> iterator = items.listIterator();
try {
// In theory, the above call to the validator makes the error checking in
// this DocOpCursor redundant. We check for errors anyway in case the
// validator is incorrect.
m.apply(new DocOpCursor() {
Item current = null;
AnnotationMap inherited = AnnotationMapImpl.EMPTY_MAP;
private AnnotationMap insertionAnnotations() {
return inherited.updateWith(annotationUpdates);
}
@Override
public void annotationBoundary(AnnotationBoundaryMap map) {
annotationUpdates = annotationUpdates.composeWith(map);
for (int i = 0; i < map.changeSize(); i++) {
knownAnnotationKeys.add(map.getChangeKey(i));
}
}
@Override
public void characters(String s) {
for (int i = 0; i < s.length(); i++) {
iterator.add(new CharacterItem(s.charAt(i), insertionAnnotations()));
}
}
@Override
public void elementStart(String type, Attributes attrs) {
iterator.add(new ElementStartItem(type, attrs, insertionAnnotations()));
}
@Override
public void elementEnd() {
iterator.add(new ElementEndItem(insertionAnnotations()));
}
@Override
public void deleteCharacters(String s) {
for (int i = 0; i < s.length(); i++) {
CharacterItem item = nextCharacter();
if (s.charAt(i) != item.character) {
throw new OpCursorException("Mismatched deleted characters: " +
s.charAt(i) + " vs " + item.character);
}
inherited = item.getAnnotations();
iterator.remove();
}
}
@Override
public void deleteElementEnd() {
ElementEndItem item = nextElementEnd();
inherited = item.getAnnotations();
iterator.remove();
}
@Override
public void deleteElementStart(String tag, Attributes attrs) {
ElementStartItem item = nextElementStart();
inherited = item.getAnnotations();
iterator.remove();
}
@Override
public void retain(int distance) {
for (int i = 0; i < distance; i++) {
inheritAndAnnotate(next());
}
}
@Override
public void replaceAttributes(Attributes oldAttrs, Attributes newAttrs) {
ElementStartItem item = nextElementStart();
item.replaceAttributes(newAttrs);
inheritAndAnnotate(item);
}
@Override
public void updateAttributes(AttributesUpdate attrUpdate) {
ElementStartItem item = nextElementStart();
item.updateAttributes(attrUpdate);
inheritAndAnnotate(item);
}
private void inheritAndAnnotate(Item item) {
inherited = item.getAnnotations();
item.updateAnnotations(annotationUpdates);
}
Item next() {
if (!iterator.hasNext()) {
throw new OpCursorException("Action past end of document, of size: " + length());
}
current = iterator.next();
return current;
}
ElementStartItem nextElementStart() {
try {
return (ElementStartItem) next();
} catch (ClassCastException e) {
throw new OpCursorException("Not at an element start, at: " + current);
}
}
ElementEndItem nextElementEnd() {
try {
return (ElementEndItem) next();
} catch (ClassCastException e) {
throw new OpCursorException("Not at an element end, at: " + current);
}
}
CharacterItem nextCharacter() {
try {
return (CharacterItem) next();
} catch (ClassCastException e) {
throw new OpCursorException("Not at a character, at: " + current);
}
}
});
if (iterator.hasNext()) {
int remainingItems = 0;
while (iterator.hasNext()) {
remainingItems++;
iterator.next();
}
throw new OperationException("Missing retain to end of document (" +
remainingItems + " items)");
}
} catch (OpCursorException e) {
throw new OperationException(e);
}
if (annotationUpdates.changeSize() != 0) {
throw new OperationException("Unended annotations at end of operation: " + annotationUpdates);
}
resetReadState();
inconsistent = false;