/** * Copyright 2010 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; import org.waveprotocol.wave.model.util.Preconditions; import java.util.Map; /** * Computes the cost of documents. * * The cost is a metric that, roughly speaking, counts the number of components * and how big each comopnent is. We could call it "size" but IndexedDocument * has a method size() already (it returns the length). * * @author ohler@google.com (Christian Ohler) */ public class DocumentCostFunction { private final int objectOverhead; /** * The objectOverhead is a parameter that affects how expensive elements and * annotation boundaries are relative to characters. */ public static DocumentCostFunction withObjectOverhead(int objectOverhead) { return new DocumentCostFunction(objectOverhead); } private DocumentCostFunction(int objectOverhead) { Preconditions.checkArgument(objectOverhead >= 0, "Negative objectOverhead: %s", objectOverhead); this.objectOverhead = objectOverhead; } // In every loop, we add objectOverhead for every item, in addition to the // cost of the item itself. This makes sure that even empty strings and other // empty stuff have a nonzero cost, and it is uniform and easy to predict. public int computeCost(String s) { return s.codePointCount(0, s.length()); } public int computeCostNullable(String s) { return s == null ? 0 : computeCost(s); } public int computeCost(Attributes attrs) { int accu = 0; for (Map.Entry<String, String> attr : attrs.entrySet()) { accu += objectOverhead + computeCost(attr.getKey()) + computeCost(attr.getValue()); } return accu; } public int computeCost(AnnotationBoundaryMap map) { int accu = 0; for (int i = 0; i < map.changeSize(); i++) { accu += objectOverhead + computeCost(map.getChangeKey(i)) + computeCostNullable(map.getOldValue(i)) + computeCostNullable(map.getNewValue(i)); } for (int i = 0; i < map.endSize(); i++) { accu += objectOverhead + computeCost(map.getEndKey(i)); } return accu; } /** * Computes the cost of the given document. */ public int computeCost(DocInitialization doc) { final int[] accu = { 0 }; doc.apply(new DocInitializationCursor() { @Override public void annotationBoundary(AnnotationBoundaryMap map) { accu[0] += objectOverhead + computeCost(map); } @Override public void characters(String chars) { accu[0] += objectOverhead + computeCost(chars); } @Override public void elementStart(String type, Attributes attrs) { accu[0] += objectOverhead + computeCost(type) + computeCost(attrs); } @Override public void elementEnd() { accu[0] += objectOverhead; } }); return accu[0]; } }