// 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 static com.google.collide.dto.DocOpComponent.Type.*;
import com.google.collide.dto.DocOp;
import com.google.collide.dto.DocOpComponent;
import com.google.collide.dto.shared.DocOpFactory;
import com.google.collide.json.shared.JsonArray;
/**
* Helper to create document operations via a method call for each component.
*
*/
public class DocOpCapturer implements DocOpCursor {
private final JsonArray<DocOpComponent> components;
private final DocOpFactory factory;
private final DocOp op;
private int curComponentType = -1;
/** Only valid if the current component is delete or insert */
private String curText = "";
/** Only valid if the current component is retain or retain line */
private int curCount;
/** Only valid if the current component is retain */
private boolean curHasTrailingNewline;
private boolean curLineHasNonRetainComponents = false;
private final boolean shouldCompact;
/**
* @param shouldCompact whether similar adjacent components should be
* compacted into a single component
*/
public DocOpCapturer(DocOpFactory factory, boolean shouldCompact) {
this.factory = factory;
this.shouldCompact = shouldCompact;
op = factory.createDocOp();
components = op.getComponents();
}
@Override
public void delete(String text) {
checkAndCommitIfRequired(DELETE);
curComponentType = DELETE;
curText += text;
// Reset the variable if starting a new line
curLineHasNonRetainComponents = !text.endsWith("\n");
}
public DocOp getDocOp() {
commitCurrentComponent();
return op;
}
@Override
public void insert(String text) {
checkAndCommitIfRequired(INSERT);
curComponentType = INSERT;
curText += text;
// Reset the variable if starting a new line
curLineHasNonRetainComponents = !text.endsWith("\n");
}
@Override
public void retain(int count, boolean hasTrailingNewline) {
if (shouldCompact && !curLineHasNonRetainComponents && hasTrailingNewline) {
/*
* Since this line only had retain(s), we can convert it to the more terse
* retain line
*/
/*
* curXxx refers to the state prior to the retain we're handling right
* now. If the ongoing component was a retain, throw it away.
*/
if (curComponentType == RETAIN && !curHasTrailingNewline) {
discardCurrentComponent();
}
retainLine(1);
return;
}
checkAndCommitIfRequired(RETAIN);
curComponentType = RETAIN;
curCount += count;
curHasTrailingNewline = hasTrailingNewline;
if (curHasTrailingNewline) {
curLineHasNonRetainComponents = false;
}
}
@Override
public void retainLine(int lineCount) {
checkAndCommitIfRequired(RETAIN_LINE);
curComponentType = RETAIN_LINE;
curCount += lineCount;
curLineHasNonRetainComponents = false;
}
private void checkAndCommitIfRequired(int newType) {
/*-
* Rules for committing:
* - This instance does NOT compact components of the same type
* into a single component, or
* - The component about to be captured is a different type from the
* currently compacting component type, or
* - The new and current component type is the same and not RETAIN_LINE,
* and it ends with a newline
*/
if (!shouldCompact || curComponentType != newType
|| (curComponentType == RETAIN && curHasTrailingNewline)
|| (curComponentType == DELETE && curText.endsWith("\n"))
|| (curComponentType == INSERT && curText.endsWith("\n"))) {
commitCurrentComponent();
}
}
private void commitCurrentComponent() {
if (curComponentType == -1) {
return;
}
switch (curComponentType) {
case INSERT:
components.add(factory.createInsert(curText));
break;
case DELETE:
components.add(factory.createDelete(curText));
break;
case RETAIN:
components.add(factory.createRetain(curCount, curHasTrailingNewline));
break;
case RETAIN_LINE:
components.add(factory.createRetainLine(curCount));
break;
default:
throw new IllegalStateException("Cannot handle component type with ordinal "
+ curComponentType);
}
discardCurrentComponent();
}
private void discardCurrentComponent() {
curComponentType = -1;
curHasTrailingNewline = false;
curText = "";
curCount = 0;
}
}