blob: 60c767533f23fe30464219e800d4558128560b64 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* 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 org.jetbrains.plugins.groovy.lang.psi.controlFlow;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.PsiShortNamesCache;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ArrayUtilRt;
import gnu.trove.TIntHashSet;
import gnu.trove.TObjectIntHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase;
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
import org.jetbrains.plugins.groovy.lang.psi.api.formatter.GrControlStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrBreakStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.clauses.GrCaseSection;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement;
import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
import org.jetbrains.plugins.groovy.lang.resolve.processors.PropertyResolverProcessor;
import org.jetbrains.plugins.groovy.lang.resolve.processors.ResolverProcessor;
import java.util.ArrayList;
import java.util.List;
/**
* @author ven
*/
public class ControlFlowBuilderUtil {
private static final Logger LOG = Logger.getInstance("org.jetbrains.plugins.groovy.lang.psi.controlFlow.ControlFlowBuilderUtil");
private ControlFlowBuilderUtil() {
}
public static int[] postorder(Instruction[] flow) {
int[] result = new int[flow.length];
boolean[] visited = new boolean[flow.length];
for (int i = 0; i < result.length; i++) visited[i] = false;
int N = flow.length;
for (int i = 0; i < flow.length; i++) { //graph might not be connected
if (!visited[i]) N = doVisitForPostorder(flow[i], N, result, visited);
}
LOG.assertTrue(N == 0);
return result;
}
private static int doVisitForPostorder(Instruction curr, int currN, int[] postorder, boolean[] visited) {
visited[curr.num()] = true;
for (Instruction succ : curr.allSuccessors()) {
if (!visited[succ.num()]) {
currN = doVisitForPostorder(succ, currN, postorder, visited);
}
}
postorder[curr.num()] = --currN;
return currN;
}
public static ReadWriteVariableInstruction[] getReadsWithoutPriorWrites(Instruction[] flow, boolean onlyFirstRead) {
List<ReadWriteVariableInstruction> result = new ArrayList<ReadWriteVariableInstruction>();
TObjectIntHashMap<String> namesIndex = buildNamesIndex(flow);
TIntHashSet[] definitelyAssigned = new TIntHashSet[flow.length];
int[] postorder = postorder(flow);
int[] invpostorder = invPostorder(postorder);
findReadsBeforeWrites(flow, definitelyAssigned, result, namesIndex, postorder, invpostorder, onlyFirstRead);
if (result.isEmpty()) return ReadWriteVariableInstruction.EMPTY_ARRAY;
return result.toArray(new ReadWriteVariableInstruction[result.size()]);
}
private static int[] invPostorder(int[] postorder) {
int[] result = new int[postorder.length];
for (int i = 0; i < postorder.length; i++) {
result[postorder[i]] = i;
}
return result;
}
private static TObjectIntHashMap<String> buildNamesIndex(Instruction[] flow) {
TObjectIntHashMap<String> namesIndex = new TObjectIntHashMap<String>();
int idx = 0;
for (Instruction instruction : flow) {
if (instruction instanceof ReadWriteVariableInstruction) {
String name = ((ReadWriteVariableInstruction)instruction).getVariableName();
if (!namesIndex.contains(name)) {
namesIndex.put(name, idx++);
}
}
}
return namesIndex;
}
private static void findReadsBeforeWrites(Instruction[] flow, TIntHashSet[] definitelyAssigned,
List<ReadWriteVariableInstruction> result,
TObjectIntHashMap<String> namesIndex,
int[] postorder,
int[] invpostorder,
boolean onlyFirstRead) {
//skip instructions that are not reachable from the start
int start = ArrayUtil.find(invpostorder, 0);
for (int i = start; i < flow.length; i++) {
int j = invpostorder[i];
Instruction curr = flow[j];
if (curr instanceof ReadWriteVariableInstruction) {
ReadWriteVariableInstruction rw = (ReadWriteVariableInstruction)curr;
int name = namesIndex.get(rw.getVariableName());
TIntHashSet vars = definitelyAssigned[j];
if (rw.isWrite()) {
if (vars == null) {
vars = new TIntHashSet();
definitelyAssigned[j] = vars;
}
vars.add(name);
}
else {
if (vars == null || !vars.contains(name)) {
result.add(rw);
if (onlyFirstRead) {
if (vars == null) {
vars = new TIntHashSet();
definitelyAssigned[j] = vars;
}
vars.add(name);
}
}
}
}
for (Instruction succ : curr.allSuccessors()) {
if (postorder[succ.num()] > postorder[curr.num()]) {
TIntHashSet currDefinitelyAssigned = definitelyAssigned[curr.num()];
TIntHashSet succDefinitelyAssigned = definitelyAssigned[succ.num()];
if (currDefinitelyAssigned != null) {
int[] currArray = currDefinitelyAssigned.toArray();
if (succDefinitelyAssigned == null) {
succDefinitelyAssigned = new TIntHashSet();
succDefinitelyAssigned.addAll(currArray);
definitelyAssigned[succ.num()] = succDefinitelyAssigned;
}
else {
succDefinitelyAssigned.retainAll(currArray);
}
}
else {
if (succDefinitelyAssigned != null) {
succDefinitelyAssigned.clear();
}
else {
succDefinitelyAssigned = new TIntHashSet();
definitelyAssigned[succ.num()] = succDefinitelyAssigned;
}
}
}
}
}
}
public static boolean isInstanceOfBinary(GrBinaryExpression binary) {
if (binary.getOperationTokenType() == GroovyTokenTypes.kIN) {
GrExpression left = binary.getLeftOperand();
GrExpression right = binary.getRightOperand();
if (left instanceof GrReferenceExpression && ((GrReferenceExpression)left).getQualifier() == null &&
right instanceof GrReferenceExpression && findClassByText((GrReferenceExpression)right)) {
return true;
}
}
return false;
}
private static boolean findClassByText(GrReferenceExpression ref) {
final String text = ref.getText();
final int i = text.indexOf('<');
String className = i == -1 ? text : text.substring(0, i);
PsiClass[] names = PsiShortNamesCache.getInstance(ref.getProject()).getClassesByName(className, ref.getResolveScope());
if (names.length > 0) return true;
PsiFile file = ref.getContainingFile();
if (file instanceof GroovyFile) {
GrImportStatement[] imports = ((GroovyFile)file).getImportStatements();
for (GrImportStatement anImport : imports) {
if (className.equals(anImport.getImportedName())) return true;
}
}
return false;
}
/**
* check whether statement is return (the statement which provides return value) statement of method or closure.
*
* @param st
* @return
*/
public static boolean isCertainlyReturnStatement(GrStatement st) {
final PsiElement parent = st.getParent();
if (parent instanceof GrOpenBlock) {
if (st != ArrayUtil.getLastElement(((GrOpenBlock)parent).getStatements())) return false;
PsiElement pparent = parent.getParent();
if (pparent instanceof GrMethod) {
return true;
}
if (pparent instanceof GrBlockStatement || pparent instanceof GrCatchClause || pparent instanceof GrLabeledStatement) {
pparent = pparent.getParent();
}
if (pparent instanceof GrIfStatement || pparent instanceof GrControlStatement || pparent instanceof GrTryCatchStatement) {
return isCertainlyReturnStatement((GrStatement)pparent);
}
}
else if (parent instanceof GrClosableBlock) {
return st == ArrayUtil.getLastElement(((GrClosableBlock)parent).getStatements());
}
else if (parent instanceof GroovyFileBase) {
return st == ArrayUtil.getLastElement(((GroovyFileBase)parent).getStatements());
}
else if (parent instanceof GrForStatement ||
parent instanceof GrIfStatement && st != ((GrIfStatement)parent).getCondition() ||
parent instanceof GrSynchronizedStatement && st != ((GrSynchronizedStatement)parent).getMonitor() ||
parent instanceof GrWhileStatement && st != ((GrWhileStatement)parent).getCondition() ||
parent instanceof GrConditionalExpression && st != ((GrConditionalExpression)parent).getCondition() ||
parent instanceof GrElvisExpression) {
return isCertainlyReturnStatement((GrStatement)parent);
}
else if (parent instanceof GrCaseSection) {
final GrStatement[] statements = ((GrCaseSection)parent).getStatements();
final GrStatement last = ArrayUtil.getLastElement(statements);
final GrSwitchStatement switchStatement = (GrSwitchStatement)parent.getParent();
if (last instanceof GrBreakStatement && statements.length > 1 && statements[statements.length - 2] == st) {
return isCertainlyReturnStatement(switchStatement);
}
else if (st == last) {
if (st instanceof GrBreakStatement || isLastStatementInCaseSection((GrCaseSection)parent, switchStatement)) {
return isCertainlyReturnStatement(switchStatement);
}
}
}
return false;
}
private static boolean isLastStatementInCaseSection(GrCaseSection caseSection, GrSwitchStatement switchStatement) {
final GrCaseSection[] sections = switchStatement.getCaseSections();
final int i = ArrayUtilRt.find(sections, caseSection);
if (i == sections.length - 1) {
return true;
}
for (int j = i + 1; j < sections.length; j++) {
GrCaseSection section = sections[j];
for (GrStatement statement : section.getStatements()) {
if (!(statement instanceof GrBreakStatement)) {
return false;
}
}
}
return true;
}
@NotNull
public static GroovyResolveResult[] resolveNonQualifiedRefWithoutFlow(@NotNull GrReferenceExpression ref) {
LOG.assertTrue(!ref.isQualified());
final String referenceName = ref.getReferenceName();
final ResolverProcessor processor = new PropertyResolverProcessor(referenceName, ref);
ResolveUtil.treeWalkUp(ref, processor, false);
final GroovyResolveResult[] candidates = processor.getCandidates();
if (candidates.length != 0) {
return candidates;
}
return GroovyResolveResult.EMPTY_ARRAY;
}
}