// 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.client.documentparser;
import com.google.collide.codemirror2.Token;
import com.google.collide.json.shared.JsonArray;
import com.google.collide.shared.document.Line;
import com.google.collide.shared.util.JsonCollections;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import javax.annotation.Nonnull;
/**
* An adapter for {@link DocumentParser.Listener} that performs only async
* parsing while collecting some line-dependent data.
*/
public abstract class AsyncParser<T extends AsyncParser.LineAware>
implements DocumentParser.Listener {
/**
* Data item base interface. It is important that they are associated with
* lines because we clear data for lines that are invalidated and need to
* be parsed again.
*/
public static interface LineAware {
/**
* @return line number this data belongs to
*/
int getLineNumber();
}
/**
* Called before parsing has started.
*/
protected void onBeforeParse() {}
/**
* Called when the data parsed earlier is discarded. Implementations
* should clear everything associated with this data. This method may
* be called on any time and should not depend on parsing cycle state.
*
* @param cleanedData data items that will be deleted
*/
protected void onCleanup(JsonArray<T> cleanedData) {}
/**
* Called when a line is being parsed. This is guaranteed to be called after
* {@link #onBeforeParse} and before {@link #onAfterParse}.
*
* @param line line being parsed
* @param lineNumber line number being parsed
* @param tokens tokens collected on the line
*/
protected void onParseLine(Line line, int lineNumber, JsonArray<Token> tokens) {}
/**
* Called just after chunk parsing has finished. This does not mean that
* parsing for the whole file has finished. You have to use what you get
* here.
*
* @param nodes resulting array of all nodes collected so far for the whole
* file
*/
protected void onAfterParse(JsonArray<T> nodes) {}
/**
* Flag that prevents work after instance was cleaned.
*/
private boolean detached;
/**
* Flag indicating that block of lines is being parsed
*/
private boolean iterating;
/**
* Array of data items.
*
* When new block of lines is parsed, this list is truncated.
*/
private JsonArray<T> nodes = JsonCollections.createArray();
@Override
public final void onDocumentLineParsed(
Line line, int lineNumber, @Nonnull JsonArray<Token> tokens) {
if (detached) {
return;
}
if (!iterating) {
return;
}
onParseLine(line, lineNumber, tokens);
}
@Override
public final void onIterationFinish() {
Preconditions.checkState(iterating);
if (detached) {
return;
}
iterating = false;
onAfterParse(nodes);
}
@Override
public final void onIterationStart(int lineNumber) {
Preconditions.checkState(!iterating);
if (detached) {
return;
}
iterating = true;
cutTail(lineNumber);
onBeforeParse();
}
/**
* Adds given data to collected set while parsing.
*
* @param dataNode data to add
*/
protected final void addData(T dataNode) {
nodes.add(dataNode);
}
private void cutTail(int lineNumber) {
if (nodes.size() != 0) {
int cutTailIndex = findCutTailIndex(nodes, lineNumber);
if (cutTailIndex < nodes.size()) {
JsonArray<T> tail = nodes.splice(cutTailIndex, nodes.size() - cutTailIndex);
onCleanup(tail);
}
}
}
/**
* Find index for tail truncation in nodes array.
*
* <p>We support nodes array sorted by line number.
* Now we use that property in modified binary search.
*/
@VisibleForTesting
public static <T extends LineAware> int findCutTailIndex(JsonArray<T> nodes,
int lineNumber) {
int low = 0;
int high = nodes.size() - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
if (lineNumber > nodes.get(mid).getLineNumber()) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return low;
}
/**
* Cleanup object. Overriding implementations should call this method before
* any custom cleanup.
*
* After cleanup is invoked this instance should never be used.
*/
public void cleanup() {
cutTail(0);
detached = true;
}
}