/**
* 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.document.operation.Attributes;
import org.waveprotocol.wave.model.document.operation.AttributesUpdate;
import org.waveprotocol.wave.model.document.operation.DocInitialization;
import org.waveprotocol.wave.model.document.operation.DocOp;
import org.waveprotocol.wave.model.document.operation.DocOpCursor;
import org.waveprotocol.wave.model.document.operation.automaton.AutomatonDocument;
import org.waveprotocol.wave.model.document.operation.automaton.DocOpAutomaton;
import org.waveprotocol.wave.model.document.operation.automaton.DocumentSchema;
import org.waveprotocol.wave.model.document.operation.automaton.DocOpAutomaton.ValidationResult;
import org.waveprotocol.wave.model.document.operation.automaton.DocOpAutomaton.ViolationCollector;
import org.waveprotocol.wave.model.util.Preconditions;
/**
* Validates an operation against a document.
*
*
*/
public final class DocOpValidator {
private DocOpValidator() {}
/**
* Returns whether op is a well-formed document initialization and satisfies
* the given schema constraints.
*/
public static ValidationResult validate(ViolationCollector v,
DocumentSchema schema, DocInitialization op) {
Preconditions.checkNotNull(schema, "Schema constraints required, if not, " +
"use DocumentSchema.NO_SCHEMA_CONSTRAINTS");
return validate(v, schema, DocOpAutomaton.EMPTY_DOCUMENT, op);
}
/**
* Returns whether op is well-formed.
*
* Any violations recorded in the output v that are not well-formedness
* violations are meaningless.
*/
public static boolean isWellFormed(ViolationCollector v, DocOp op) {
if (op instanceof BufferedDocOpImpl) {
return isWellFormed(v, (BufferedDocOpImpl) op);
} else {
return isWellFormedRaw(v, op);
}
}
/**
* Same as {@link #isWellFormed(ViolationCollector, DocOp)}, but with
* a fast path for already-validated instances of BufferedDocOpImpl
*/
public static boolean isWellFormed(ViolationCollector v, BufferedDocOpImpl buffered) {
if (buffered.isKnownToBeWellFormed()) {
// fast path
return true;
} else {
if (isWellFormedRaw(v, buffered)) {
buffered.markWellFormed();
return true;
} else {
return false;
}
}
}
/**
* Same as {@link #isWellFormed(ViolationCollector, DocOp)}, but without
* the fast path for BufferedDocOpImpl
*/
public static boolean isWellFormedRaw(ViolationCollector v, DocOp op) {
// We validate the operation against the empty document. It will likely
// be invalid; however, we ignore the validity aspect anyway since we
// only care about well-formedness.
return !validate(v, DocumentSchema.NO_SCHEMA_CONSTRAINTS,
DocOpAutomaton.EMPTY_DOCUMENT, op)
.isIllFormed();
}
private static final class IllFormed extends RuntimeException {
IllFormed(String message) {
super(message);
}
@Override
public Throwable fillInStackTrace() {
return this; // don't fill in stack trace, for efficiency
}
}
private static final IllFormed ILL_FORMED = new IllFormed(
"Preallocated exception with a meaningless stack trace");
/**
* Returns whether op is well-formed, applies to doc, and preserves the given
* schema constraints. Will not modify doc.
*/
public static <N, E extends N, T extends N> ValidationResult validate(
final ViolationCollector v, DocumentSchema schema,
AutomatonDocument doc, DocOp op) {
if (schema == null) {
schema = DocumentSchema.NO_SCHEMA_CONSTRAINTS;
}
final DocOpAutomaton a = new DocOpAutomaton(doc, schema);
final ValidationResult[] accu = new ValidationResult[] { ValidationResult.VALID };
try {
op.apply(new DocOpCursor() {
void abortIfIllFormed() {
if (accu[0].isIllFormed()) {
throw ILL_FORMED;
}
}
@Override
public void characters(String s) {
accu[0] = accu[0].mergeWith(a.checkCharacters(s, v));
abortIfIllFormed();
a.doCharacters(s);
}
@Override
public void deleteCharacters(String chars) {
accu[0] = accu[0].mergeWith(a.checkDeleteCharacters(chars, v));
abortIfIllFormed();
a.doDeleteCharacters(chars);
}
@Override
public void deleteElementEnd() {
accu[0] = accu[0].mergeWith(a.checkDeleteElementEnd(v));
abortIfIllFormed();
a.doDeleteElementEnd();
}
@Override
public void deleteElementStart(String type, Attributes attrs) {
accu[0] = accu[0].mergeWith(a.checkDeleteElementStart(type, attrs, v));
abortIfIllFormed();
a.doDeleteElementStart(type, attrs);
}
@Override
public void replaceAttributes(Attributes oldAttrs, Attributes newAttrs) {
accu[0] = accu[0].mergeWith(a.checkReplaceAttributes(oldAttrs, newAttrs, v));
abortIfIllFormed();
a.doReplaceAttributes(oldAttrs, newAttrs);
}
@Override
public void retain(int itemCount) {
accu[0] = accu[0].mergeWith(a.checkRetain(itemCount, v));
abortIfIllFormed();
a.doRetain(itemCount);
}
@Override
public void updateAttributes(AttributesUpdate u) {
accu[0] = accu[0].mergeWith(a.checkUpdateAttributes(u, v));
abortIfIllFormed();
a.doUpdateAttributes(u);
}
@Override
public void annotationBoundary(AnnotationBoundaryMap map) {
accu[0] = accu[0].mergeWith(a.checkAnnotationBoundary(map, v));
abortIfIllFormed();
a.doAnnotationBoundary(map);
}
@Override
public void elementEnd() {
accu[0] = accu[0].mergeWith(a.checkElementEnd(v));
abortIfIllFormed();
a.doElementEnd();
}
@Override
public void elementStart(String type, Attributes attrs) {
accu[0] = accu[0].mergeWith(a.checkElementStart(type, attrs, v));
abortIfIllFormed();
a.doElementStart(type, attrs);
}
});
} catch (IllFormed e) {
return ValidationResult.ILL_FORMED;
}
accu[0] = accu[0].mergeWith(a.checkFinish(v));
return accu[0];
}
}