// Copyright 2012 Google Inc. All Rights Reserved. // // 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 com.google.collide.shared.ot; import com.google.collide.dto.DocOp; import com.google.collide.dto.DocOpComponent; import com.google.collide.dto.DocOpComponent.Delete; import com.google.collide.dto.DocOpComponent.Insert; import com.google.collide.dto.DocOpComponent.Retain; import com.google.collide.dto.DocOpComponent.RetainLine; import com.google.collide.json.shared.JsonArray; import com.google.collide.shared.document.Document; import com.google.collide.shared.document.DocumentMutator; import com.google.collide.shared.document.LineInfo; import com.google.collide.shared.document.TextChange; import com.google.collide.shared.util.JsonCollections; import com.google.common.base.Preconditions; /** */ public class DocOpApplier { public static JsonArray<TextChange> apply(DocOp docOp, Document document) { return apply(docOp, document, document); } public static JsonArray<TextChange> apply(DocOp docOp, Document document, DocumentMutator documentMutator) { DocOpApplier docOpApplier = new DocOpApplier(docOp, document, documentMutator); docOpApplier.apply(); return docOpApplier.textChanges; } private int column; private final JsonArray<DocOpComponent> components; private int componentIndex; private final Document document; private DocumentMutator documentMutator; /** * If true, we are definitely finished and cannot accepts any more doc op * components. */ private boolean isFinished; private LineInfo lineInfo; private final JsonArray<TextChange> textChanges = JsonCollections.createArray(); private DocOpApplier(DocOp docOp, Document document, DocumentMutator documentMutator) { this.components = docOp.getComponents(); this.document = document; this.documentMutator = documentMutator; lineInfo = new LineInfo(document.getFirstLine(), 0); } public void apply() { for (; componentIndex < components.size(); componentIndex++) { DocOpComponent component = components.get(componentIndex); switch (component.getType()) { case DocOpComponent.Type.INSERT: handleInsert((Insert) component); break; case DocOpComponent.Type.DELETE: handleDelete((Delete) component); break; case DocOpComponent.Type.RETAIN: handleRetain((Retain) component); break; case DocOpComponent.Type.RETAIN_LINE: handleRetainLine((RetainLine) component); break; } } } private void handleDelete(Delete deleteOp) { Preconditions.checkArgument(!isFinished, "Unexpected finished while handling delete"); Preconditions.checkArgument(lineInfo.line().getText().substring(column).startsWith( deleteOp.getText()), "To-be-deleted text isn't actually at location"); StringBuilder text = new StringBuilder(deleteOp.getText()); while (componentIndex + 1 < components.size() && components.get(componentIndex + 1).getType() == DocOpComponent.Type.DELETE) { componentIndex++; text.append(((Delete) components.get(componentIndex)).getText()); } addTextChange(documentMutator.deleteText(lineInfo.line(), lineInfo.number(), column, text.length())); } private void handleInsert(Insert insertOp) { Preconditions.checkArgument(!isFinished, "Unexpected finished while handling insert"); StringBuilder text = new StringBuilder(); int newLineDelta = 0; int newColumn = column; // Offset the componentIndex for the first iteration componentIndex--; do { componentIndex++; String insertOpText = ((Insert) components.get(componentIndex)).getText(); text.append(insertOpText); if (insertOpText.endsWith("\n")) { newLineDelta++; newColumn = 0; } else { newColumn += insertOpText.length(); } } while (componentIndex + 1 < components.size() && components.get(componentIndex + 1).getType() == DocOpComponent.Type.INSERT); addTextChange(documentMutator.insertText(lineInfo.line(), lineInfo.number(), column, text.toString())); for (; newLineDelta > 0; newLineDelta--) { moveToNextLine(); } column = newColumn; } private void addTextChange(TextChange textChange) { if (textChange != null) { textChanges.add(textChange); } } private void handleRetain(Retain retainOp) { Preconditions.checkArgument(!isFinished, "Unexpected finished while handling retain"); if (retainOp.hasTrailingNewline()) { moveToNextLine(); } else { column += retainOp.getCount(); } } private void handleRetainLine(RetainLine retainLineOp) { Preconditions.checkArgument(!isFinished, "Unexpected finished while handling retain line"); int lineCount = retainLineOp.getLineCount(); int newLineNumber = lineInfo.number() + lineCount; if (newLineNumber < document.getLineCount()) { lineInfo = document.getLineFinder().findLine(lineInfo.number() + lineCount); column = 0; } else { // We have spanned the entire document isFinished = true; } } private void moveToNextLine() { boolean didMove = lineInfo.moveToNext(); Preconditions.checkArgument(didMove, "Did not actually move to next line"); column = 0; } }