// 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.autocomplete.codegraph; import static com.google.collide.shared.document.util.LineUtils.comparePositions; import com.google.collide.client.util.PathUtil; import com.google.collide.dto.CodeBlock; import com.google.collide.json.shared.JsonArray; import com.google.collide.shared.document.util.LineUtils; import com.google.collide.shared.util.JsonCollections; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import java.util.HashMap; import java.util.Map; /** * Encapsulates code structure data of the file opened in the editor. * */ class CodeFile { static class CodeBlockReferences { private final Map<CodeBlock, CodeBlock> childParentRefs = new HashMap<CodeBlock, CodeBlock>(); void addChildParentRef(CodeBlock child, CodeBlock parent) { childParentRefs.put(child, parent); } CodeBlock getParent(CodeBlock child) { return childParentRefs.get(child); } void clear() { childParentRefs.clear(); } } private static Scope findScope(Scope scope, int lineNumber, int column, boolean endInclusive) { int relativeToBegin = comparePositions(lineNumber, column, scope.getBeginLineNumber(), scope.getBeginColumn()); // When we say that cursor column is X, we mean, that there are X chars // before cursor in this line. // But when we say that scope ends at column X, we mean that X-th char is // the last char that belongs to the scope. // That is why we do +1 to make this function work as designed. int scopeEndColumn = scope.getEndColumn() + 1; int relativeToEnd = comparePositions(lineNumber, column, scope.getEndLineNumber(), scopeEndColumn); if (relativeToBegin < 0 || relativeToEnd > 0) { return null; } if (!endInclusive && relativeToEnd == 0) { return null; } for (int i = 0; i < scope.getSubscopes().size(); ++i) { Scope subScope = findScope(scope.getSubscopes().get(i), lineNumber, column, endInclusive); if (subScope != null) { return subScope; } } return scope; } private final CodeBlockReferences refs = new CodeBlockReferences(); private final PathUtil filePath; private CodeBlock rootCodeBlock; private Scope rootScope; CodeFile(PathUtil filePath) { Preconditions.checkNotNull(filePath); this.filePath = filePath; } private void buildSubscopes(Scope rootScope, CodeBlock codeBlock, JsonArray<CodeBlock> queue) { JsonArray<Scope> subscopes = rootScope.getSubscopes(); for (int i = 0, size = codeBlock.getChildren().size(); i < size; i++) { CodeBlock child = codeBlock.getChildren().get(i); refs.addChildParentRef(child, codeBlock); if (isTextuallyNested(child, codeBlock)) { Scope childScope = new Scope(child); subscopes.add(childScope); } else { queue.add(child); } } for (int i = 0; i < subscopes.size(); i++) { Scope child = subscopes.get(i); buildSubscopes(child, child.getCodeBlock(), queue); } } private boolean isTextuallyNested(CodeBlock child, CodeBlock parent) { return LineUtils.comparePositions(child.getStartLineNumber(), child.getStartColumn(), parent.getStartLineNumber(), parent.getStartColumn()) >= 0 && LineUtils.comparePositions(child.getEndLineNumber(), child.getEndColumn(), parent.getEndLineNumber(), parent.getEndColumn()) <= 0; } /** * Finds the most suitable scope for a given position. * * @param lineNumber position line number * @param column position column * @param endInclusive when {@code true} then scopes are suitable if they * can be expanded by adding something at the given position * @return scope found */ Scope findScope(int lineNumber, int column, boolean endInclusive) { if (rootScope == null) { return null; } return findScope(rootScope, lineNumber, column, endInclusive); } PathUtil getFilePath() { return filePath; } CodeBlockReferences getReferences() { return refs; } CodeBlock getRootCodeBlock() { return rootCodeBlock; } @VisibleForTesting Scope getRootScope() { return this.rootScope; } void setRootCodeBlock(CodeBlock codeBlock) { this.rootCodeBlock = codeBlock; if (codeBlock == null) { return; } refs.clear(); rootScope = new Scope(codeBlock); JsonArray<CodeBlock> queue = JsonCollections.createArray(); buildSubscopes(rootScope, rootCodeBlock, queue); while (!queue.isEmpty()) { JsonArray<CodeBlock> newQueue = JsonCollections.createArray(); for (int i = 0; i < queue.size(); i++) { CodeBlock queued = queue.get(i); Scope lexicalContainer = findScope(queued.getStartLineNumber(), queued.getStartColumn(), false); if (lexicalContainer != null) { Scope lexicalScope = new Scope(queued); lexicalContainer.getSubscopes().add(lexicalScope); buildSubscopes(lexicalScope, queued, newQueue); } } queue = newQueue; } } }