| /* |
| * Copyright 2000-2013 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 com.intellij.codeInsight.guess.impl; |
| |
| import com.intellij.codeInsight.guess.GuessManager; |
| import com.intellij.codeInspection.dataFlow.*; |
| import com.intellij.codeInspection.dataFlow.instructions.PushInstruction; |
| import com.intellij.codeInspection.dataFlow.instructions.TypeCastInstruction; |
| import com.intellij.codeInspection.dataFlow.instructions.InstanceofInstruction; |
| import com.intellij.codeInspection.dataFlow.instructions.MethodCallInstruction; |
| import com.intellij.codeInspection.dataFlow.value.DfaInstanceofValue; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.psi.*; |
| import com.intellij.psi.search.LocalSearchScope; |
| import com.intellij.psi.search.PsiElementProcessor; |
| import com.intellij.psi.search.PsiElementProcessorAdapter; |
| import com.intellij.psi.search.SearchScope; |
| import com.intellij.psi.search.searches.ClassInheritorsSearch; |
| import com.intellij.psi.search.searches.ReferencesSearch; |
| import com.intellij.psi.util.PsiUtil; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.util.containers.ContainerUtil; |
| import gnu.trove.THashMap; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| public class GuessManagerImpl extends GuessManager { |
| |
| private final MethodPatternMap myMethodPatternMap = new MethodPatternMap(); |
| |
| { |
| initMethodPatterns(); |
| } |
| |
| @SuppressWarnings({"HardCodedStringLiteral"}) |
| private void initMethodPatterns() { |
| // Collection |
| myMethodPatternMap.addPattern(new MethodPattern("add", 1, 0)); |
| myMethodPatternMap.addPattern(new MethodPattern("contains", 1, 0)); |
| myMethodPatternMap.addPattern(new MethodPattern("remove", 1, 0)); |
| |
| // Vector |
| myMethodPatternMap.addPattern(new MethodPattern("add", 2, 1)); |
| myMethodPatternMap.addPattern(new MethodPattern("addElement", 1, 0)); |
| myMethodPatternMap.addPattern(new MethodPattern("elementAt", 1, -1)); |
| myMethodPatternMap.addPattern(new MethodPattern("firstElement", 0, -1)); |
| myMethodPatternMap.addPattern(new MethodPattern("lastElement", 0, -1)); |
| myMethodPatternMap.addPattern(new MethodPattern("get", 1, -1)); |
| myMethodPatternMap.addPattern(new MethodPattern("indexOf", 1, 0)); |
| myMethodPatternMap.addPattern(new MethodPattern("indexOf", 2, 0)); |
| myMethodPatternMap.addPattern(new MethodPattern("lastIndexOf", 1, 0)); |
| myMethodPatternMap.addPattern(new MethodPattern("lastIndexOf", 2, 0)); |
| myMethodPatternMap.addPattern(new MethodPattern("insertElementAt", 2, 0)); |
| myMethodPatternMap.addPattern(new MethodPattern("removeElement", 1, 0)); |
| myMethodPatternMap.addPattern(new MethodPattern("set", 2, 1)); |
| myMethodPatternMap.addPattern(new MethodPattern("setElementAt", 2, 0)); |
| } |
| |
| private final Project myProject; |
| |
| public GuessManagerImpl(Project project) { |
| myProject = project; |
| } |
| |
| @Override |
| public PsiType[] guessContainerElementType(PsiExpression containerExpr, TextRange rangeToIgnore) { |
| HashSet<PsiType> typesSet = new HashSet<PsiType>(); |
| |
| PsiType type = containerExpr.getType(); |
| PsiType elemType; |
| if ((elemType = getGenericElementType(type)) != null) return new PsiType[]{elemType}; |
| |
| if (containerExpr instanceof PsiReferenceExpression){ |
| PsiElement refElement = ((PsiReferenceExpression)containerExpr).resolve(); |
| if (refElement instanceof PsiVariable){ |
| |
| PsiFile file = refElement.getContainingFile(); |
| if (file == null){ |
| file = containerExpr.getContainingFile(); // implicit variable in jsp |
| } |
| HashSet<PsiVariable> checkedVariables = new HashSet<PsiVariable>(); |
| addTypesByVariable(typesSet, (PsiVariable)refElement, file, checkedVariables, CHECK_USAGE | CHECK_DOWN, rangeToIgnore); |
| checkedVariables.clear(); |
| addTypesByVariable(typesSet, (PsiVariable)refElement, file, checkedVariables, CHECK_UP, rangeToIgnore); |
| } |
| } |
| |
| return typesSet.toArray(PsiType.createArray(typesSet.size())); |
| } |
| |
| @Nullable |
| private static PsiType getGenericElementType(PsiType collectionType) { |
| if (collectionType instanceof PsiClassType) { |
| PsiClassType classType = (PsiClassType) collectionType; |
| PsiType[] parameters = classType.getParameters(); |
| if (parameters.length == 1) { |
| return parameters[0]; |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public PsiType[] guessTypeToCast(PsiExpression expr) { //TODO : make better guess based on control flow |
| LinkedHashSet<PsiType> types = new LinkedHashSet<PsiType>(); |
| |
| ContainerUtil.addIfNotNull(getControlFlowExpressionType(expr), types); |
| addExprTypesWhenContainerElement(types, expr); |
| addExprTypesByDerivedClasses(types, expr); |
| |
| return types.toArray(PsiType.createArray(types.size())); |
| } |
| |
| @NotNull |
| @Override |
| public Map<PsiExpression, PsiType> getControlFlowExpressionTypes(@NotNull final PsiExpression forPlace) { |
| final Map<PsiExpression, PsiType> typeMap = buildDataflowTypeMap(forPlace); |
| if (typeMap != null) { |
| return typeMap; |
| } |
| |
| return Collections.emptyMap(); |
| } |
| |
| @Nullable |
| private static Map<PsiExpression, PsiType> buildDataflowTypeMap(PsiExpression forPlace) { |
| PsiElement scope = DfaPsiUtil.getTopmostBlockInSameClass(forPlace); |
| if (scope == null) { |
| PsiFile file = forPlace.getContainingFile(); |
| if (!(file instanceof PsiCodeFragment)) { |
| return Collections.emptyMap(); |
| } |
| |
| scope = file; |
| } |
| |
| DataFlowRunner runner = new DataFlowRunner(scope) { |
| @Override |
| protected DfaMemoryState createMemoryState() { |
| return new ExpressionTypeMemoryState(getFactory()); |
| } |
| }; |
| |
| final ExpressionTypeInstructionVisitor visitor = new ExpressionTypeInstructionVisitor(forPlace); |
| if (runner.analyzeMethod(scope, visitor) == RunnerResult.OK) { |
| return visitor.getResult(); |
| } |
| return null; |
| } |
| |
| private static Map<PsiExpression, PsiType> getAllTypeCasts(PsiExpression forPlace) { |
| assert forPlace.isValid(); |
| final int start = forPlace.getTextRange().getStartOffset(); |
| final Map<PsiExpression, PsiType> allCasts = new THashMap<PsiExpression, PsiType>(ExpressionTypeMemoryState.EXPRESSION_HASHING_STRATEGY); |
| getTopmostBlock(forPlace).accept(new JavaRecursiveElementWalkingVisitor() { |
| @Override |
| public void visitTypeCastExpression(PsiTypeCastExpression expression) { |
| final PsiType castType = expression.getType(); |
| final PsiExpression operand = expression.getOperand(); |
| if (operand != null && castType != null) { |
| allCasts.put(operand, castType); |
| } |
| super.visitTypeCastExpression(expression); |
| } |
| |
| @Override |
| public void visitInstanceOfExpression(PsiInstanceOfExpression expression) { |
| final PsiTypeElement castType = expression.getCheckType(); |
| final PsiExpression operand = expression.getOperand(); |
| if (castType != null) { |
| allCasts.put(operand, castType.getType()); |
| } |
| super.visitInstanceOfExpression(expression); |
| } |
| |
| @Override |
| public void visitElement(PsiElement element) { |
| if (element.getTextRange().getStartOffset() > start) { |
| return; |
| } |
| |
| super.visitElement(element); |
| } |
| }); |
| return allCasts; |
| } |
| |
| private static PsiElement getTopmostBlock(PsiElement scope) { |
| assert scope.isValid(); |
| PsiElement lastScope = scope; |
| while (true) { |
| final PsiCodeBlock lastCodeBlock = PsiTreeUtil.getParentOfType(lastScope, PsiCodeBlock.class, true); |
| if (lastCodeBlock == null) { |
| break; |
| } |
| lastScope = lastCodeBlock; |
| } |
| if (lastScope == scope) { |
| PsiFile file = scope.getContainingFile(); |
| if (file instanceof PsiCodeFragment) { |
| return file; |
| } |
| } |
| return lastScope; |
| } |
| |
| private void addExprTypesByDerivedClasses(LinkedHashSet<PsiType> set, PsiExpression expr) { |
| PsiType type = expr.getType(); |
| if (!(type instanceof PsiClassType)) return; |
| PsiClass refClass = PsiUtil.resolveClassInType(type); |
| if (refClass == null) return; |
| |
| PsiManager manager = PsiManager.getInstance(myProject); |
| PsiElementProcessor.CollectElementsWithLimit<PsiClass> processor = new PsiElementProcessor.CollectElementsWithLimit<PsiClass>(5); |
| ClassInheritorsSearch.search(refClass, true).forEach(new PsiElementProcessorAdapter<PsiClass>(processor)); |
| if (processor.isOverflow()) return; |
| |
| for (PsiClass derivedClass : processor.getCollection()) { |
| if (derivedClass instanceof PsiAnonymousClass) continue; |
| PsiType derivedType = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory().createType(derivedClass); |
| set.add(derivedType); |
| } |
| } |
| |
| private void addExprTypesWhenContainerElement(LinkedHashSet<PsiType> set, PsiExpression expr) { |
| if (expr instanceof PsiMethodCallExpression){ |
| PsiMethodCallExpression callExpr = (PsiMethodCallExpression)expr; |
| PsiReferenceExpression methodExpr = callExpr.getMethodExpression(); |
| String methodName = methodExpr.getReferenceName(); |
| MethodPattern pattern = myMethodPatternMap.findPattern(methodName, callExpr.getArgumentList().getExpressions().length); |
| if (pattern != null && pattern.parameterIndex < 0/* return value */){ |
| PsiExpression qualifier = methodExpr.getQualifierExpression(); |
| if (qualifier != null) { |
| PsiType[] types = guessContainerElementType(qualifier, null); |
| for (PsiType type : types) { |
| if (type instanceof PsiClassType) { |
| if (((PsiClassType)type).resolve() instanceof PsiAnonymousClass) continue; |
| } |
| set.add(type); |
| } |
| } |
| } |
| } |
| } |
| |
| private static final int CHECK_USAGE = 0x01; |
| private static final int CHECK_UP = 0x02; |
| private static final int CHECK_DOWN = 0x04; |
| |
| private void addTypesByVariable(HashSet<PsiType> typesSet, |
| PsiVariable var, |
| PsiFile scopeFile, |
| HashSet<PsiVariable> checkedVariables, |
| int flags, |
| TextRange rangeToIgnore) { |
| if (!checkedVariables.add(var)) return; |
| //System.out.println("analyzing usages of " + var + " in file " + scopeFile); |
| SearchScope searchScope = new LocalSearchScope(scopeFile); |
| |
| if ((flags & (CHECK_USAGE | CHECK_DOWN)) != 0){ |
| for (PsiReference varRef : ReferencesSearch.search(var, searchScope, false)) { |
| PsiElement ref = varRef.getElement(); |
| |
| if ((flags & CHECK_USAGE) != 0) { |
| PsiType type = guessElementTypeFromReference(myMethodPatternMap, ref, rangeToIgnore); |
| if (type != null && !(type instanceof PsiPrimitiveType)) { |
| typesSet.add(type); |
| } |
| } |
| |
| if ((flags & CHECK_DOWN) != 0) { |
| if (ref.getParent() instanceof PsiExpressionList && ref.getParent().getParent() instanceof PsiMethodCallExpression) { //TODO : new |
| PsiExpressionList list = (PsiExpressionList)ref.getParent(); |
| PsiExpression[] args = list.getExpressions(); |
| int argIndex = -1; |
| for (int j = 0; j < args.length; j++) { |
| PsiExpression arg = args[j]; |
| if (arg.equals(ref)) { |
| argIndex = j; |
| break; |
| } |
| } |
| |
| PsiMethodCallExpression methodCall = (PsiMethodCallExpression)list.getParent(); |
| PsiMethod method = (PsiMethod)methodCall.getMethodExpression().resolve(); |
| if (method != null) { |
| PsiParameter[] parameters = method.getParameterList().getParameters(); |
| if (argIndex < parameters.length) { |
| addTypesByVariable(typesSet, parameters[argIndex], method.getContainingFile(), checkedVariables, flags | CHECK_USAGE, |
| rangeToIgnore); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| if ((flags & CHECK_UP) != 0){ |
| if (var instanceof PsiParameter && var.getParent() instanceof PsiParameterList && var.getParent().getParent() instanceof PsiMethod){ |
| PsiParameterList list = (PsiParameterList)var.getParent(); |
| PsiParameter[] parameters = list.getParameters(); |
| int argIndex = -1; |
| for(int i = 0; i < parameters.length; i++){ |
| PsiParameter parameter = parameters[i]; |
| if (parameter.equals(var)){ |
| argIndex = i; |
| break; |
| } |
| } |
| |
| PsiMethod method = (PsiMethod)var.getParent().getParent(); |
| //System.out.println("analyzing usages of " + method + " in file " + scopeFile); |
| for (PsiReference methodRef : ReferencesSearch.search(method, searchScope, false)) { |
| PsiElement ref = methodRef.getElement(); |
| if (ref.getParent() instanceof PsiMethodCallExpression) { |
| PsiMethodCallExpression methodCall = (PsiMethodCallExpression)ref.getParent(); |
| PsiExpression[] args = methodCall.getArgumentList().getExpressions(); |
| if (args.length <= argIndex) continue; |
| PsiExpression arg = args[argIndex]; |
| if (arg instanceof PsiReferenceExpression) { |
| PsiElement refElement = ((PsiReferenceExpression)arg).resolve(); |
| if (refElement instanceof PsiVariable) { |
| addTypesByVariable(typesSet, (PsiVariable)refElement, scopeFile, checkedVariables, flags | CHECK_USAGE, rangeToIgnore); |
| } |
| } |
| //TODO : constructor |
| } |
| } |
| } |
| } |
| } |
| |
| @Nullable |
| private static PsiType guessElementTypeFromReference(MethodPatternMap methodPatternMap, |
| PsiElement ref, |
| TextRange rangeToIgnore) { |
| PsiElement refParent = ref.getParent(); |
| if (refParent instanceof PsiReferenceExpression){ |
| PsiReferenceExpression parentExpr = (PsiReferenceExpression)refParent; |
| if (ref.equals(parentExpr.getQualifierExpression()) && parentExpr.getParent() instanceof PsiMethodCallExpression){ |
| String methodName = parentExpr.getReferenceName(); |
| PsiMethodCallExpression methodCall = (PsiMethodCallExpression)parentExpr.getParent(); |
| PsiExpression[] args = methodCall.getArgumentList().getExpressions(); |
| MethodPattern pattern = methodPatternMap.findPattern(methodName, args.length); |
| if (pattern != null){ |
| if (pattern.parameterIndex < 0){ // return value |
| if (methodCall.getParent() instanceof PsiTypeCastExpression && |
| (rangeToIgnore == null || !rangeToIgnore.contains(methodCall.getTextRange()))) { |
| return ((PsiTypeCastExpression)methodCall.getParent()).getType(); |
| } |
| } |
| else{ |
| return args[pattern.parameterIndex].getType(); |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| @Nullable |
| public PsiType getControlFlowExpressionType(@NotNull PsiExpression expr) { |
| final Map<PsiExpression, PsiType> allCasts = getAllTypeCasts(expr); |
| if (!allCasts.containsKey(expr)) { |
| return null; //optimization |
| } |
| |
| final Map<PsiExpression, PsiType> fromDfa = buildDataflowTypeMap(expr); |
| if (fromDfa != null) { |
| return fromDfa.get(expr); |
| } |
| |
| return null; |
| } |
| |
| private static class ExpressionTypeInstructionVisitor extends InstructionVisitor { |
| private Map<PsiExpression, PsiType> myResult; |
| private final PsiElement myForPlace; |
| |
| private ExpressionTypeInstructionVisitor(PsiElement forPlace) { |
| myForPlace = forPlace; |
| } |
| |
| public Map<PsiExpression, PsiType> getResult() { |
| return myResult; |
| } |
| |
| @Override |
| public DfaInstructionState[] visitInstanceof(InstanceofInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { |
| memState.pop(); |
| memState.pop(); |
| memState.push(new DfaInstanceofValue(runner.getFactory(), instruction.getLeft(), instruction.getCastType())); |
| return new DfaInstructionState[]{new DfaInstructionState(runner.getInstruction(instruction.getIndex() + 1), memState)}; |
| } |
| |
| @Override |
| public DfaInstructionState[] visitTypeCast(TypeCastInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { |
| ((ExpressionTypeMemoryState) memState).setExpressionType(instruction.getCasted(), instruction.getCastTo()); |
| return super.visitTypeCast(instruction, runner, memState); |
| } |
| |
| @Override |
| public DfaInstructionState[] visitMethodCall(MethodCallInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { |
| if (myForPlace == instruction.getCallExpression()) { |
| addToResult(((ExpressionTypeMemoryState)memState).getStates()); |
| } |
| return super.visitMethodCall(instruction, runner, memState); |
| } |
| |
| @Override |
| public DfaInstructionState[] visitPush(PushInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { |
| if (myForPlace == instruction.getPlace()) { |
| addToResult(((ExpressionTypeMemoryState)memState).getStates()); |
| } |
| return super.visitPush(instruction, runner, memState); |
| } |
| |
| private void addToResult(Map<PsiExpression, PsiType> map) { |
| if (myResult == null) { |
| myResult = new THashMap<PsiExpression, PsiType>(map, ExpressionTypeMemoryState.EXPRESSION_HASHING_STRATEGY); |
| } else { |
| final Iterator<PsiExpression> iterator = myResult.keySet().iterator(); |
| while (iterator.hasNext()) { |
| PsiExpression psiExpression = iterator.next(); |
| if (!myResult.get(psiExpression).equals(map.get(psiExpression))) { |
| iterator.remove(); |
| } |
| } |
| } |
| } |
| } |
| } |