// 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.code.gotodefinition;
import com.google.collide.client.codeunderstanding.CubeClient;
import com.google.collide.client.codeunderstanding.CubeData;
import com.google.collide.client.codeunderstanding.CubeDataUpdates;
import com.google.collide.client.codeunderstanding.CubeUpdateListener;
import com.google.collide.client.util.logging.Log;
import com.google.collide.dto.CodeReference;
import com.google.collide.dto.CodeReferences;
import com.google.collide.dto.FilePosition;
import com.google.collide.dto.client.ClientDocOpFactory;
import com.google.collide.dto.client.DtoClientImpls;
import com.google.collide.json.client.Jso;
import com.google.collide.shared.document.Document;
import com.google.collide.shared.document.LineInfo;
import com.google.collide.shared.document.LineNumberAndColumn;
import com.google.collide.shared.ot.PositionMigrator;
import com.google.common.annotations.VisibleForTesting;
import javax.annotation.Nullable;
/**
* Storage and lookup system for cubeReferences from source ranges to
* another file or URLs. Collects cubeReferences from various sources like Cube
* or dynamic reference provider.
*
*/
class ReferenceStore {
// Tracks docops since last time we received information from Cube.
// These docops are used when we need to find reference at given position
// if the position has changed as the result of user edits.
private final PositionMigrator positionMigrator;
private final CubeClient cubeClient;
private final CubeUpdateListener cubeListener = new CubeUpdateListener() {
@Override
public void onCubeResponse(CubeData data, CubeDataUpdates updates) {
if (!updates.isFileReferences()) {
return;
}
updateReferences(data);
}
};
// References that come from Cube.
private CodeReferences cubeReferences;
// References that come from client parser.
private DynamicReferenceProvider dynamicReferenceProvider;
public ReferenceStore(CubeClient cubeClient) {
this.cubeClient = cubeClient;
this.positionMigrator = new PositionMigrator(ClientDocOpFactory.INSTANCE);
cubeClient.addListener(cubeListener);
}
/**
* Finds reference at given position.
*
* @param lineInfo position line info
* @param column position column
* @param blocking whether to block until given line is parsed for references
* @return reference found at given position, or {@code null} if nothing there
*/
@VisibleForTesting
NavigableReference findReference(LineInfo lineInfo, int column, boolean blocking) {
// TODO: Optimize this search.
// Seach for reference at position where the cursor would have been if there were
// no text changes.
int line = lineInfo.number();
NavigableReference result = null;
if (cubeReferences != null && cubeReferences.getReferences().size() > 0) {
// TODO: Optimize this search.
// Seach for reference at position where the cursor would have been if there were
// no text changes.
LineNumberAndColumn oldPosition = positionMigrator.migrateFromNow(line, column);
for (int i = 0; i < cubeReferences.getReferences().size(); i++) {
CodeReference reference = cubeReferences.getReferences().get(i);
if (reference.getReferenceStart().getLineNumber() > oldPosition.lineNumber) {
// We've gone too far, nothing to look further.
break;
}
if (isFilePositionBefore(reference.getReferenceStart(), oldPosition.lineNumber,
oldPosition.column)
&& isFilePositionAfter(reference.getReferenceEnd(), oldPosition.lineNumber,
oldPosition.column)) {
// Migrate old reference to new position after edits.
CodeReference newReference = migrateCubeReference(reference);
if (newReference != null) {
result = NavigableReference.createToFile(newReference);
}
break;
}
}
}
if (result == null && dynamicReferenceProvider != null) {
result = dynamicReferenceProvider.getReferenceAt(lineInfo, column, blocking);
}
if (result == null) {
Log.debug(getClass(), "Found no references at: (" + line + "," + column + ")");
} else {
Log.debug(getClass(),
"Found reference at (" + line + "," + column + "):" + result.toString());
}
return result;
}
/**
* Migrates old reference (its position as it was when we received cube data
* before any edits) to new reference (new position after edits) if possible.
* Reference cannot be migrated if some characters disappeared inside it or
* newline was inserted in the middle.
*
* @param reference reference to migrate
* @return migrated reference or {@code null} if reference cannot be migrated
*/
private CodeReference migrateCubeReference(CodeReference reference) {
FilePosition oldStartPosition = reference.getReferenceStart();
FilePosition oldEndPosition = reference.getReferenceEnd();
LineNumberAndColumn newStartPosition = positionMigrator.migrateToNow(
oldStartPosition.getLineNumber(), oldStartPosition.getColumn());
LineNumberAndColumn newEndPosition = positionMigrator.migrateToNow(
oldEndPosition.getLineNumber(), oldEndPosition.getColumn());
int newLength = newEndPosition.column - newStartPosition.column;
int oldLength = oldEndPosition.getColumn() - oldStartPosition.getColumn();
if (newStartPosition.lineNumber != newEndPosition.lineNumber
|| newLength != oldLength || newLength < 0) {
// TODO: Make the method return null if text has changed inside the reference.
return null;
}
DtoClientImpls.CodeReferenceImpl newReference = Jso.create().cast();
return newReference
.setReferenceType(reference.getReferenceType())
.setReferenceStart(toDtoPosition(newStartPosition))
.setReferenceEnd(toDtoPosition(newEndPosition))
// TODO: Target may be in this file, in this case update target too.
.setTargetStart(reference.getTargetStart())
.setTargetEnd(reference.getTargetEnd())
.setTargetFilePath(reference.getTargetFilePath())
.setTargetSnippet(reference.getTargetSnippet());
}
private static FilePosition toDtoPosition(LineNumberAndColumn position) {
return DtoClientImpls.FilePositionImpl.make()
.setLineNumber(position.lineNumber)
.setColumn(position.column);
}
private static boolean isFilePositionBefore(FilePosition position, int line, int column) {
return position.getLineNumber() < line
|| (position.getLineNumber() == line && position.getColumn() <= column);
}
private static boolean isFilePositionAfter(FilePosition position, int line, int column) {
return position.getLineNumber() > line
|| (position.getLineNumber() == line && position.getColumn() >= column);
}
private void logAllReferences() {
if (cubeReferences == null) {
Log.debug(getClass(), "No references info yet.");
return;
}
Log.debug(getClass(), "All references in current file:");
for (int i = 0; i < cubeReferences.getReferences().size(); i++) {
CodeReference reference = cubeReferences.getReferences().get(i);
Log.debug(getClass(), "reference at: " + referenceToString(reference));
}
}
private static String filePositionToString(FilePosition position) {
return "(" + position.getLineNumber() + "," + position.getColumn() + ")";
}
private static String referenceToString(CodeReference reference) {
return filePositionToString(reference.getReferenceStart()) + " to file \""
+ reference.getTargetFilePath() + "\", target start: "
+ filePositionToString(reference.getTargetStart()) + ", target end: "
+ filePositionToString(reference.getTargetEnd());
}
public void onDocumentChanged(Document document,
@Nullable DynamicReferenceProvider dynamicReferenceProvider) {
updateReferences(cubeClient.getData());
this.positionMigrator.start(document.getTextListenerRegistrar());
this.dynamicReferenceProvider = dynamicReferenceProvider;
}
@VisibleForTesting
void updateReferences(CubeData data) {
positionMigrator.reset();
this.cubeReferences = data.getFileReferences();
// referenceRenderer.resetReferences(editor.getRenderer(), lineFinder);
if (Log.isLoggingEnabled()) {
Log.debug(getClass(), "Received code references");
logAllReferences();
}
}
public void cleanup() {
cubeClient.removeListener(cubeListener);
positionMigrator.stop();
}
}