/** * 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.operation.wave; import com.google.common.annotations.VisibleForTesting; 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.DocOp; import org.waveprotocol.wave.model.document.operation.DocOpCursor; import org.waveprotocol.wave.model.util.ValueUtils; /** * Checker for if a document mutation is worthy of updating a blip * timestamp and authorship. * * This class shouldn't be there. Is worthy or not should be a property of the ops, * there shouldn't be an O(N) algorithm to work this out. * * @author zdwang@google.com (David Wang) */ public class WorthyChangeChecker { private WorthyChangeChecker() {} // NOTE(anorth): These constants are duplicated from internal models. // Keep them in sync. private static final String SELECTION_ANNOTATION_PREFIX = "user/"; private static final String LINKY_ANNOTATION = "link/auto"; private static final String SPELLY_ANNOTATION = "spell"; private static final String LANGUAGE_ANNOTATION = "lang"; private static final String ROSY_ANNOTATION = "tr/1"; // Copied from Blips. @VisibleForTesting public static final String THREAD_INLINE_ANCHOR_TAGNAME = "reply"; public interface ReusableWorthyChangeChecker { boolean isWorthy(DocOp op); } private static final ReusableWorthyChangeChecker INSTANCE = new ReusableWorthyChangeChecker() { @Override public boolean isWorthy(DocOp op) { try { op.apply(CHECKER); } catch (True t) { return true; } return false; } }; // Exception thrown in order to terminate the visitor pattern early when an // op is known to be worthy. private static class True extends RuntimeException { True(String message) { super(message); } @Override public Throwable fillInStackTrace() { return this; // don't fill in stack trace, for efficiency } } private static final True TRUE = new True("Preallocated exception with a meaningless stack trace"); private static final DocOpCursor CHECKER = new DocOpCursor() { @Override public void retain(int itemCount) { } @Override public void characters(String characters) { throw TRUE; } @Override public void elementStart(String type, Attributes attributes) { if (!isAnchor(type)) { throw TRUE; } } @Override public void elementEnd() { } @Override public void deleteCharacters(String chars) { throw TRUE; } @Override public void deleteElementStart(String type, Attributes attrs) { if (!isAnchor(type)) { throw TRUE; } } @Override public void deleteElementEnd() { } @Override public void replaceAttributes(Attributes oldAttrs, Attributes newAttrs) { throw TRUE; } @Override public void updateAttributes(AttributesUpdate attrUpdate) { throw TRUE; } @Override public void annotationBoundary(AnnotationBoundaryMap map) { for (int i = 0; i < map.changeSize(); i++) { String key = map.getChangeKey(i); String oldValue = map.getOldValue(i); String newValue = map.getNewValue(i); if (!ValueUtils.equal(oldValue, newValue) && !key.startsWith(SELECTION_ANNOTATION_PREFIX) && !key.equals(SPELLY_ANNOTATION) && !key.equals(LINKY_ANNOTATION) && !key.equals(ROSY_ANNOTATION) && !key.equals(LANGUAGE_ANNOTATION)) { throw TRUE; } } } }; private static boolean isAnchor(String tag) { return tag.equals(THREAD_INLINE_ANCHOR_TAGNAME); } public static boolean isWorthy(DocOp op) { return create().isWorthy(op); } public static ReusableWorthyChangeChecker create() { return INSTANCE; } /** * Tests whether a document id identifies a document for which modifications * are possibly worthy. Operations applied to <em>un</em>worthy documents are * always unworthy. (e.g., any edit on an unworthy document will not be * treated as an indexing event). * * @param docId document identifier * @return false if operations on the document identified by {@code docId} * should always be unworthy */ public static boolean isBlipIdWorthy(String docId) { // NOTE(anorth): These constants are duplicated from internal // models. Keep them in sync. return !(docId.startsWith("attach") || docId.equals("mini") || docId.startsWith("tr+")); } }