diff --git a/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceReader.java b/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceReader.java index 9fb285c41ac8..79650074d237 100644 --- a/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceReader.java +++ b/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceReader.java @@ -30,6 +30,7 @@ import com.intellij.util.indexing.StorageException; import com.intellij.util.indexing.ValueContainer; import gnu.trove.THashSet; import gnu.trove.TIntHashSet; +import gnu.trove.TObjectIntHashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.backwardRefs.CompilerBackwardReferenceIndex; @@ -178,6 +179,24 @@ class CompilerReferenceReader { return myIndex; } + TObjectIntHashMap getTypeCasts(@NotNull LightRef.LightClassHierarchyElementDef castType, @NotNull TIntHashSet fileIds) throws StorageException { + TObjectIntHashMap typeCastStats = new TObjectIntHashMap<>(); + myIndex.get(CompilerIndices.BACK_CAST).getData(castType).forEach(new ValueContainer.ContainerAction>() { + @Override + public boolean perform(int id, Collection values) { + if (!fileIds.contains(id)) return true; + for (LightRef ref : values) { + if (!typeCastStats.adjustValue(ref, 1)) { + typeCastStats.put(ref, 1); + } + } + return true; + } + }); + + return typeCastStats; + } + static boolean exists(Project project) { File buildDir = BuildManager.getInstance().getProjectSystemDirectory(project); if (buildDir == null || CompilerBackwardReferenceIndex.versionDiffers(buildDir)) { diff --git a/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceServiceEx.java b/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceServiceEx.java index bf47948911c6..b69e08d824e1 100644 --- a/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceServiceEx.java +++ b/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceServiceEx.java @@ -16,9 +16,11 @@ package com.intellij.compiler.backwardRefs; import com.intellij.compiler.CompilerReferenceService; -import com.intellij.compiler.chainsSearch.SignatureAndOccurrences; +import com.intellij.compiler.chainsSearch.MethodRefAndOccurrences; +import com.intellij.compiler.chainsSearch.context.ChainCompletionContext; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.backwardRefs.LightRef; import org.jetbrains.jps.backwardRefs.SignatureData; @@ -33,8 +35,13 @@ public abstract class CompilerReferenceServiceEx extends CompilerReferenceServic } @NotNull - public abstract SortedSet findMethodReferenceOccurrences(@NotNull String rawReturnType, - @SignatureData.IteratorKind byte iteratorKind) + public abstract SortedSet findMethodReferenceOccurrences(@NotNull String rawReturnType, + @SignatureData.IteratorKind byte iteratorKind, + @NotNull ChainCompletionContext context) + throws ReferenceIndexUnavailableException; + + @Nullable + public abstract LightRef.LightClassHierarchyElementDef mayCallOfTypeCast(@NotNull LightRef.JavaLightMethodRef method, int probabilityThreshold) throws ReferenceIndexUnavailableException; public abstract boolean mayHappen(@NotNull LightRef qualifier, @NotNull LightRef base, int probabilityThreshold) diff --git a/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceServiceImpl.java b/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceServiceImpl.java index edce380b146c..16a6e7961459 100644 --- a/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceServiceImpl.java +++ b/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceServiceImpl.java @@ -20,8 +20,9 @@ import com.intellij.compiler.backwardRefs.view.CompilerReferenceFindUsagesTestIn import com.intellij.compiler.backwardRefs.view.CompilerReferenceHierarchyTestInfo; import com.intellij.compiler.backwardRefs.view.DirtyScopeTestInfo; import com.intellij.compiler.chainsSearch.ChainSearchMagicConstants; -import com.intellij.compiler.chainsSearch.MethodIncompleteSignature; -import com.intellij.compiler.chainsSearch.SignatureAndOccurrences; +import com.intellij.compiler.chainsSearch.MethodCall; +import com.intellij.compiler.chainsSearch.MethodRefAndOccurrences; +import com.intellij.compiler.chainsSearch.context.ChainCompletionContext; import com.intellij.compiler.server.BuildManager; import com.intellij.compiler.server.BuildManagerListener; import com.intellij.lang.injection.InjectedLanguageManager; @@ -59,6 +60,8 @@ import com.intellij.util.io.PersistentEnumeratorBase; import com.intellij.util.messages.MessageBusConnection; import gnu.trove.THashSet; import gnu.trove.TIntHashSet; +import gnu.trove.TObjectIntHashMap; +import gnu.trove.TObjectIntProcedure; import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -220,8 +223,9 @@ public class CompilerReferenceServiceImpl extends CompilerReferenceServiceEx imp @NotNull @Override - public SortedSet findMethodReferenceOccurrences(@NotNull String rawReturnType, - @SignatureData.IteratorKind byte iteratorKind) { + public SortedSet findMethodReferenceOccurrences(@NotNull String rawReturnType, + @SignatureData.IteratorKind byte iteratorKind, + @NotNull ChainCompletionContext context) { try { myReadDataLock.lock(); try { @@ -240,15 +244,15 @@ public class CompilerReferenceServiceImpl extends CompilerReferenceServiceEx imp .distinct() .map(r -> { int count = myReader.getOccurrenceCount(r); - return count <= 1 ? null : new SignatureAndOccurrences( - new MethodIncompleteSignature((LightRef.JavaLightMethodRef)r, sd, this), + return count <= 1 ? null : new MethodRefAndOccurrences( + new MethodCall((LightRef.JavaLightMethodRef)r, sd, context), count); })) .filter(Objects::nonNull) .collect(Collectors.groupingBy(x -> x.getSignature(), Collectors.summarizingInt(x -> x.getOccurrenceCount()))) .entrySet() .stream() - .map(e -> new SignatureAndOccurrences(e.getKey(), (int)e.getValue().getSum())) + .map(e -> new MethodRefAndOccurrences(e.getKey(), (int)e.getValue().getSum())) .collect(Collectors.toCollection(TreeSet::new)); } finally { @@ -261,6 +265,52 @@ public class CompilerReferenceServiceImpl extends CompilerReferenceServiceEx imp } } + /** + * finds one best candidate to do a cast type before given method call (eg.: ((B) a).someMethod()). Follows given formula: + * + * #(files where method & type cast is occurred) / #(files where method is occurred) > 1 - 1 / probabilityThreshold + */ + @Nullable + @Override + public LightRef.LightClassHierarchyElementDef mayCallOfTypeCast(@NotNull LightRef.JavaLightMethodRef method, int probabilityThreshold) + throws ReferenceIndexUnavailableException { + try { + myReadDataLock.lock(); + try { + if (myReader == null) throw new ReferenceIndexUnavailableException(); + final TIntHashSet ids = myReader.getAllContainingFileIds(method); + + LightRef.LightClassHierarchyElementDef owner = method.getOwner(); + + TObjectIntHashMap typeCasts = myReader.getTypeCasts(owner, ids); + + LightRef[] best = {null}; + int[] bestFileCount = {0}; + + typeCasts.forEachEntry(new TObjectIntProcedure() { + @Override + public boolean execute(LightRef operandType, int matchedFileCount) { + if (ids.size() > probabilityThreshold * (ids.size() - matchedFileCount)) { + if (best[0] == null || bestFileCount[0] < matchedFileCount) { + best[0] = operandType; + bestFileCount[0] = matchedFileCount; + } + } + return true; + } + }); + + return (LightRef.LightClassHierarchyElementDef)best[0]; + } + finally { + myReadDataLock.unlock(); + } + } catch (Exception e) { + onException(e, "conditional probability"); + return null; + } + } + /** * conditional probability P(ref1 | ref2) = P(ref1 * ref2) / P(ref2) > 1 - 1 / threshold * diff --git a/java/compiler/impl/src/com/intellij/compiler/chainsSearch/ChainOperation.java b/java/compiler/impl/src/com/intellij/compiler/chainsSearch/ChainOperation.java new file mode 100644 index 000000000000..97cc33468e36 --- /dev/null +++ b/java/compiler/impl/src/com/intellij/compiler/chainsSearch/ChainOperation.java @@ -0,0 +1,64 @@ +/* + * Copyright 2000-2017 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.compiler.chainsSearch; + +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiMethod; +import org.jetbrains.annotations.NotNull; + +public interface ChainOperation { + class TypeCast implements ChainOperation { + // we cast only to a class + private final PsiClass myOperandClass; + @NotNull private final PsiClass myCastClass; + + public TypeCast(@NotNull PsiClass operandClass, @NotNull PsiClass castClass) { + myOperandClass= operandClass; + myCastClass = castClass; + } + + public PsiClass getCastClass() { + return myCastClass; + } + + @Override + public String toString() { + return "cast of " + myOperandClass.getName(); + } + } + + class MethodCall implements ChainOperation { + @NotNull + private final PsiMethod[] myCandidates; + + public MethodCall(@NotNull PsiMethod[] candidates) { + if (candidates.length == 0) { + throw new IllegalStateException(); + } + myCandidates = candidates; + } + + @NotNull + public PsiMethod[] getCandidates() { + return myCandidates; + } + + @Override + public String toString() { + return myCandidates[0].getName() + "()"; + } + } +} diff --git a/java/compiler/impl/src/com/intellij/compiler/chainsSearch/ChainSearcher.java b/java/compiler/impl/src/com/intellij/compiler/chainsSearch/ChainSearcher.java index d2afd2168294..9163bb2716d6 100644 --- a/java/compiler/impl/src/com/intellij/compiler/chainsSearch/ChainSearcher.java +++ b/java/compiler/impl/src/com/intellij/compiler/chainsSearch/ChainSearcher.java @@ -28,11 +28,11 @@ import java.util.*; public class ChainSearcher { @NotNull - public static List search(int pathMaximalLength, - ChainSearchTarget searchTarget, - int maxResultSize, - ChainCompletionContext context, - CompilerReferenceServiceEx compilerReferenceServiceEx) { + public static List search(int pathMaximalLength, + ChainSearchTarget searchTarget, + int maxResultSize, + ChainCompletionContext context, + CompilerReferenceServiceEx compilerReferenceServiceEx) { SearchInitializer initializer = createInitializer(searchTarget, compilerReferenceServiceEx, context); return search(compilerReferenceServiceEx, initializer, pathMaximalLength, maxResultSize, context); } @@ -41,49 +41,49 @@ public class ChainSearcher { private static SearchInitializer createInitializer(ChainSearchTarget target, CompilerReferenceServiceEx referenceServiceEx, ChainCompletionContext context) { - SortedSet methods = null; + SortedSet methods = Collections.emptySortedSet(); for (byte kind : target.getArrayKind()) { - SortedSet currentMethods = - referenceServiceEx.findMethodReferenceOccurrences(target.getClassQName(), kind); - if (methods == null) { - methods = currentMethods; - } else { - methods = unionSortedSet(currentMethods, methods); - } + SortedSet currentMethods = referenceServiceEx.findMethodReferenceOccurrences(target.getClassQName(), kind, context); + methods = methods == null ? currentMethods : unionSortedSet(currentMethods, methods); } return new SearchInitializer(methods, context); } @NotNull - private static List search(CompilerReferenceServiceEx referenceServiceEx, - SearchInitializer initializer, - int chainMaxLength, - int maxResultSize, - ChainCompletionContext context) { - LinkedList q = initializer.getChainQueue(); + private static List search(CompilerReferenceServiceEx referenceServiceEx, + SearchInitializer initializer, + int chainMaxLength, + int maxResultSize, + ChainCompletionContext context) { + LinkedList q = initializer.getChainQueue(); - List result = new ArrayList<>(); + List result = new ArrayList<>(); while (!q.isEmpty()) { ProgressManager.checkCanceled(); - MethodChain currentChain = q.poll(); - MethodIncompleteSignature headSignature = currentChain.getHeadSignature(); + OperationChain currentChain = q.poll(); + RefChainOperation head = currentChain.getHead(); if (addChainIfTerminal(currentChain, result, chainMaxLength, context)) continue; // otherwise try to find chain continuation boolean updated = false; - SortedSet candidates = referenceServiceEx.findMethodReferenceOccurrences(headSignature.getOwner(), SignatureData.ZERO_DIM); - for (SignatureAndOccurrences candidate : candidates) { + SortedSet candidates = referenceServiceEx.findMethodReferenceOccurrences(head.getQualifierRawName(), SignatureData.ZERO_DIM, context); + LightRef ref = head.getLightRef(); + for (MethodRefAndOccurrences candidate : candidates) { if (candidate.getOccurrenceCount() * ChainSearchMagicConstants.FILTER_RATIO < currentChain.getChainWeight()) { break; } - MethodIncompleteSignature sign = candidate.getSignature(); - if ((sign.isStatic() || !sign.getOwner().equals(context.getTarget().getClassQName())) && - referenceServiceEx.mayHappen(candidate.getSignature().getRef(), headSignature.getRef(), ChainSearchMagicConstants.METHOD_PROBABILITY_THRESHOLD)) { - MethodChain continuation = currentChain.continuation(candidate.getSignature(), candidate.getOccurrenceCount(), context); + MethodCall sign = candidate.getSignature(); + if ((sign.isStatic() || !sign.getQualifierRawName().equals(context.getTarget().getClassQName())) && + (!(ref instanceof LightRef.JavaLightMethodRef) || + referenceServiceEx.mayHappen(candidate.getSignature().getLightRef(), ref, ChainSearchMagicConstants.METHOD_PROBABILITY_THRESHOLD))) { + + OperationChain + continuation = currentChain.continuationWithMethod(candidate.getSignature(), candidate.getOccurrenceCount(), context); if (continuation != null) { - boolean stopChain = candidate.getSignature().isStatic() || context.hasQualifier(context.resolveQualifierClass(candidate.getSignature())); + boolean stopChain = + candidate.getSignature().isStatic() || context.hasQualifier(context.resolvePsiClass(candidate.getSignature().getQualifierDef())); if (stopChain) { addChainIfNotPresent(continuation, result); } @@ -95,6 +95,19 @@ public class ChainSearcher { } } + if (ref instanceof LightRef.JavaLightMethodRef) { + LightRef.LightClassHierarchyElementDef def = + referenceServiceEx.mayCallOfTypeCast((LightRef.JavaLightMethodRef)ref, ChainSearchMagicConstants.METHOD_PROBABILITY_THRESHOLD); + if (def != null) { + OperationChain + continuation = currentChain.continuationWithCast(new TypeCast(def, head.getQualifierDef(), referenceServiceEx), context); + if (continuation != null) { + q.addFirst(continuation); + updated = true; + } + } + } + if (!updated) { addChainIfQualifierCanBeOccurredInContext(currentChain, result, context, referenceServiceEx); } @@ -109,15 +122,18 @@ public class ChainSearcher { /** * To reduce false-positives we add a method to result only if its qualifier can be occurred together with context variables. */ - private static void addChainIfQualifierCanBeOccurredInContext(MethodChain currentChain, - List result, + private static void addChainIfQualifierCanBeOccurredInContext(OperationChain currentChain, + List result, ChainCompletionContext context, CompilerReferenceServiceEx referenceServiceEx) { - if (!context.getTarget().getClassQName().equals(currentChain.getHeadSignature().getOwner())) { + RefChainOperation signature = currentChain.getHeadMethodCall(); + // type cast + introduced qualifier: it's too complex chain + if (currentChain.hasCast()) return; + if (!context.getTarget().getClassQName().equals(signature.getQualifierRawName())) { Set references = context.getContextClassReferences(); boolean isRelevantQualifier = false; for (LightRef ref: references) { - if (referenceServiceEx.mayHappen(currentChain.getHeadSignature().getOwnerRef(), ref, ChainSearchMagicConstants.VAR_PROBABILITY_THRESHOLD)) { + if (referenceServiceEx.mayHappen(signature.getQualifierDef(), ref, ChainSearchMagicConstants.VAR_PROBABILITY_THRESHOLD)) { isRelevantQualifier = true; break; } @@ -129,10 +145,12 @@ public class ChainSearcher { } } - private static boolean addChainIfTerminal(MethodChain currentChain, List result, int pathMaximalLength, + private static boolean addChainIfTerminal(OperationChain currentChain, List result, int pathMaximalLength, ChainCompletionContext context) { - if (currentChain.getHeadSignature().isStatic() || - context.hasQualifier(context.resolveQualifierClass(currentChain.getHeadSignature())) || + RefChainOperation signature = currentChain.getHeadMethodCall(); + RefChainOperation head = currentChain.getHead(); + if (((MethodCall)signature).isStatic() || + context.hasQualifier(context.resolvePsiClass(head.getQualifierDef())) || currentChain.length() >= pathMaximalLength) { addChainIfNotPresent(currentChain, result); return true; @@ -140,7 +158,7 @@ public class ChainSearcher { return false; } - private static void addChainIfNotPresent(MethodChain newChain, List result) { + private static void addChainIfNotPresent(OperationChain newChain, List result) { if (result.isEmpty()) { result.add(newChain); return; @@ -148,8 +166,8 @@ public class ChainSearcher { boolean doAdd = true; IntStack indicesToRemove = new IntStack(); for (int i = 0; i < result.size(); i++) { - MethodChain chain = result.get(i); - MethodChain.CompareResult r = MethodChain.compare(chain, newChain); + OperationChain chain = result.get(i); + OperationChain.CompareResult r = OperationChain.compare(chain, newChain); switch (r) { case LEFT_CONTAINS_RIGHT: indicesToRemove.push(i); diff --git a/java/compiler/impl/src/com/intellij/compiler/chainsSearch/MethodChain.java b/java/compiler/impl/src/com/intellij/compiler/chainsSearch/MethodChain.java deleted file mode 100644 index fbeeb34154a7..000000000000 --- a/java/compiler/impl/src/com/intellij/compiler/chainsSearch/MethodChain.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2000-2017 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.compiler.chainsSearch; - -import com.intellij.compiler.chainsSearch.context.ChainCompletionContext; -import com.intellij.psi.*; -import com.intellij.psi.util.InheritanceUtil; -import com.intellij.psi.util.PsiUtil; -import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.*; -import java.util.stream.Collectors; - -import static com.intellij.util.containers.ContainerUtil.newArrayList; -import static com.intellij.util.containers.ContainerUtil.reverse; - -public class MethodChain { - private final List myRevertedPath; - private final MethodIncompleteSignature mySignature; - private final int myWeight; - private final PsiClass myQualifierClass; - - @Nullable - public static MethodChain create(@NotNull MethodIncompleteSignature signature, - int weight, - @NotNull ChainCompletionContext context) { - PsiClass qualifier = context.resolveQualifierClass(signature); - if (qualifier == null || (!signature.isStatic() && InheritanceUtil.isInheritorOrSelf(context.getTarget().getTargetClass(), qualifier, true))) { - return null; - } - PsiMethod[] methods = context.resolve(signature); - if (methods.length == 0) return null; - Set classes = Arrays.stream(methods) - .flatMap(m -> Arrays.stream(m.getParameterList().getParameters())) - .map(p -> PsiUtil.resolveClassInType(p.getType())) - .collect(Collectors.toSet()); - PsiClass contextClass = context.getTarget().getTargetClass(); - if (classes.contains(contextClass)) { - return null; - } - classes.add(contextClass); - return new MethodChain(qualifier, Collections.singletonList(methods), signature, weight); - } - - public MethodChain(@NotNull PsiClass qualifierClass, - @NotNull List revertedPath, - MethodIncompleteSignature signature, - int weight) { - myQualifierClass = qualifierClass; - myRevertedPath = revertedPath; - mySignature = signature; - myWeight = weight; - } - - @NotNull - public MethodIncompleteSignature getHeadSignature() { - return mySignature; - } - - public int length() { - return myRevertedPath.size(); - } - - public PsiClass getQualifierClass() { - return myQualifierClass; - } - - @NotNull - public PsiMethod[] getFirst() { - return myRevertedPath.get(0); - } - - public List getPath() { - return reverse(myRevertedPath); - } - - public int getChainWeight() { - return myWeight; - } - - - public MethodChain continuation(@NotNull MethodIncompleteSignature signature, - int weight, - @NotNull ChainCompletionContext context) { - MethodChain head = create(signature, weight, context); - if (head == null) return null; - - ArrayList newRevertedPath = newArrayList(); - newRevertedPath.addAll(myRevertedPath); - newRevertedPath.add(head.getPath().get(0)); - return new MethodChain(head.getQualifierClass(), newRevertedPath, head.getHeadSignature(), Math.min(weight, getChainWeight())); - } - - @Override - public String toString() { - return myQualifierClass.getName() + "." + reverse(myRevertedPath).stream().map(methods -> methods[0].getName() + "()").collect(Collectors.joining(".")); - } - - @SuppressWarnings("ConstantConditions") - public static CompareResult compare(@NotNull MethodChain left, @NotNull MethodChain right) { - if (left.length() == 0 || right.length() == 0) { - throw new IllegalStateException("chains can't be empty"); - } - Iterator leftIterator = left.myRevertedPath.iterator(); - Iterator rightIterator = right.myRevertedPath.iterator(); - - while (leftIterator.hasNext() && rightIterator.hasNext()) { - PsiMethod[] thisNext = leftIterator.next(); - PsiMethod[] thatNext = rightIterator.next(); - if (!lookSimilar(thisNext, thatNext)) { - return CompareResult.NOT_EQUAL; - } - } - if (leftIterator.hasNext() && !rightIterator.hasNext()) { - return CompareResult.LEFT_CONTAINS_RIGHT; - } - if (!leftIterator.hasNext() && rightIterator.hasNext()) { - return CompareResult.RIGHT_CONTAINS_LEFT; - } - - return CompareResult.EQUAL; - } - - public enum CompareResult { - LEFT_CONTAINS_RIGHT, - RIGHT_CONTAINS_LEFT, - EQUAL, - NOT_EQUAL - } - - static boolean lookSimilar(PsiMethod[] methods1, PsiMethod[] methods2) { - PsiMethod repr1 = methods1[0]; - PsiMethod repr2 = methods2[0]; - if (repr1.hasModifierProperty(PsiModifier.STATIC) || repr2.hasModifierProperty(PsiModifier.STATIC)) return false; - if (!repr1.getName().equals(repr2.getName()) || - repr1.getParameterList().getParametersCount() != repr2.getParameterList().getParametersCount()) { - return false; - } - Set methodSet1 = ContainerUtil.newHashSet(methods1); - Set methodSet2 = ContainerUtil.newHashSet(methods2); - if (ContainerUtil.intersects(methodSet1, methodSet2)) return true; - - Set deepestSupers1 = methodSet1.stream().flatMap(m -> Arrays.stream(m.findDeepestSuperMethods())).collect(Collectors.toSet()); - return methodSet2.stream().flatMap(m -> Arrays.stream(m.findDeepestSuperMethods())).anyMatch(deepestSupers1::contains); - } -} diff --git a/java/compiler/impl/src/com/intellij/compiler/chainsSearch/MethodChainLookupRangingHelper.java b/java/compiler/impl/src/com/intellij/compiler/chainsSearch/MethodChainLookupRangingHelper.java index fde155f88049..256e8c16ca40 100644 --- a/java/compiler/impl/src/com/intellij/compiler/chainsSearch/MethodChainLookupRangingHelper.java +++ b/java/compiler/impl/src/com/intellij/compiler/chainsSearch/MethodChainLookupRangingHelper.java @@ -16,6 +16,7 @@ package com.intellij.compiler.chainsSearch; import com.intellij.codeInsight.NullableNotNullManager; +import com.intellij.codeInsight.completion.CastingLookupElementDecorator; import com.intellij.codeInsight.completion.InsertionContext; import com.intellij.codeInsight.completion.JavaChainLookupElement; import com.intellij.codeInsight.completion.JavaMethodCallElement; @@ -34,31 +35,40 @@ import com.intellij.psi.util.PsiUtil; import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.stream.Collectors; public class MethodChainLookupRangingHelper { @NotNull - public static LookupElement toLookupElement(MethodChain chain, + public static LookupElement toLookupElement(OperationChain chain, ChainCompletionContext context) { int unreachableParametersCount = 0; int matchedParametersInContext = 0; LookupElement chainLookupElement = null; - for (PsiMethod[] psiMethods : chain.getPath()) { - PsiMethod method = ObjectUtils.notNull(MethodChainsSearchUtil.getMethodWithMinNotPrimitiveParameters(psiMethods, context.getTarget().getTargetClass())); - Couple info = calculateParameterInfo(method, context); - unreachableParametersCount += info.getFirst(); - matchedParametersInContext += info.getSecond(); + for (ChainOperation op : chain.getPath()) { + if (op instanceof ChainOperation.MethodCall) { + PsiMethod method = ObjectUtils.notNull(MethodChainsSearchUtil.getMethodWithMinNotPrimitiveParameters(((ChainOperation.MethodCall)op).getCandidates(), + context.getTarget().getTargetClass())); + Couple info = calculateParameterInfo(method, context); + unreachableParametersCount += info.getFirst(); + matchedParametersInContext += info.getSecond(); - if (chainLookupElement == null) { - LookupElement qualifierLookupElement = createQualifierLookupElement(method, chain.getQualifierClass(), context); - LookupElement headLookupElement = createMethodLookupElement(method); - chainLookupElement = qualifierLookupElement == null ? headLookupElement : new JavaChainLookupElement(qualifierLookupElement, headLookupElement); + if (chainLookupElement == null) { + LookupElement qualifierLookupElement = method.hasModifierProperty(PsiModifier.STATIC) ? null : createQualifierLookupElement(chain.getQualifierClass(), context); + LookupElement headLookupElement = createMethodLookupElement(method); + chainLookupElement = qualifierLookupElement == null ? headLookupElement : new JavaChainLookupElement(qualifierLookupElement, headLookupElement); + } else { + chainLookupElement = new JavaChainLookupElement(chainLookupElement, new JavaMethodCallElement(method)); + } } else { - chainLookupElement = new JavaChainLookupElement(chainLookupElement, new JavaMethodCallElement(method)); + if (chainLookupElement == null) { + chainLookupElement = createQualifierLookupElement(chain.getQualifierClass(), context); + } + PsiClass castClass = ((ChainOperation.TypeCast)op).getCastClass(); + PsiClassType type = JavaPsiFacade.getElementFactory(castClass.getProject()).createType(castClass); + chainLookupElement = CastingLookupElementDecorator.createCastingElement(chainLookupElement, type); } } @@ -100,9 +110,9 @@ public class MethodChainLookupRangingHelper { }; } - @Nullable - private static LookupElement createQualifierLookupElement(@NotNull PsiMethod method, @NotNull PsiClass qualifierClass, @NotNull ChainCompletionContext context) { - if (method.hasModifierProperty(PsiModifier.STATIC)) return null; + @NotNull + private static LookupElement createQualifierLookupElement(@NotNull PsiClass qualifierClass, + @NotNull ChainCompletionContext context) { PsiNamedElement element = context.getQualifiers(qualifierClass).findFirst().orElse(null); if (element == null) { return new ChainCompletionNewVariableLookupElement(qualifierClass, context); diff --git a/java/compiler/impl/src/com/intellij/compiler/chainsSearch/MethodChainsSearchUtil.java b/java/compiler/impl/src/com/intellij/compiler/chainsSearch/MethodChainsSearchUtil.java index ae029a1702cf..42b54b329366 100644 --- a/java/compiler/impl/src/com/intellij/compiler/chainsSearch/MethodChainsSearchUtil.java +++ b/java/compiler/impl/src/com/intellij/compiler/chainsSearch/MethodChainsSearchUtil.java @@ -21,37 +21,16 @@ import com.intellij.psi.PsiParameter; import com.intellij.psi.PsiType; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.TypeConversionUtil; -import com.intellij.util.text.EditDistance; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Comparator; -import java.util.Set; import java.util.stream.Stream; public final class MethodChainsSearchUtil { - private final static int COMMON_PART_MIN_LENGTH = 3; - private MethodChainsSearchUtil() { } - public static boolean isSimilar(@NotNull String target, - @NotNull String candidate) { - return EditDistance.levenshtein(target, sanitizedToLowerCase(candidate), true) >= COMMON_PART_MIN_LENGTH; - } - - @NotNull - public static String sanitizedToLowerCase(@NotNull String name) { - StringBuilder result = new StringBuilder(); - for (int i = 0; i < name.length(); i++) { - char ch = name.charAt(i); - if (Character.isLetter(ch)) { - result.append(Character.toLowerCase(ch)); - } - } - return result.toString(); - } - @Nullable public static PsiMethod getMethodWithMinNotPrimitiveParameters(@NotNull PsiMethod[] methods, @NotNull PsiClass target) { diff --git a/java/compiler/impl/src/com/intellij/compiler/chainsSearch/SignatureAndOccurrences.java b/java/compiler/impl/src/com/intellij/compiler/chainsSearch/MethodRefAndOccurrences.java similarity index 76% rename from java/compiler/impl/src/com/intellij/compiler/chainsSearch/SignatureAndOccurrences.java rename to java/compiler/impl/src/com/intellij/compiler/chainsSearch/MethodRefAndOccurrences.java index 69e15ef02ee1..239503978f39 100644 --- a/java/compiler/impl/src/com/intellij/compiler/chainsSearch/SignatureAndOccurrences.java +++ b/java/compiler/impl/src/com/intellij/compiler/chainsSearch/MethodRefAndOccurrences.java @@ -17,16 +17,16 @@ package com.intellij.compiler.chainsSearch; import org.jetbrains.annotations.NotNull; -public class SignatureAndOccurrences implements Comparable { - private final MethodIncompleteSignature mySignature; +public class MethodRefAndOccurrences implements Comparable { + private final MethodCall mySignature; private final int myOccurrences; - public SignatureAndOccurrences(final MethodIncompleteSignature signature, final int occurrences) { + public MethodRefAndOccurrences(final MethodCall signature, final int occurrences) { mySignature = signature; myOccurrences = occurrences; } - public MethodIncompleteSignature getSignature() { + public MethodCall getSignature() { return mySignature; } @@ -35,7 +35,7 @@ public class SignatureAndOccurrences implements Comparable classes = Arrays.stream(methods) + .flatMap(m -> Arrays.stream(m.getParameterList().getParameters())) + .map(p -> PsiUtil.resolveClassInType(p.getType())) + .collect(Collectors.toSet()); + PsiClass contextClass = context.getTarget().getTargetClass(); + if (classes.contains(contextClass)) { + return null; + } + classes.add(contextClass); + return new OperationChain(qualifier, new ChainOperation[] {new ChainOperation.MethodCall(methods)}, signature, signature, weight); + } + + private OperationChain(@NotNull PsiClass qualifierClass, + @NotNull ChainOperation[] reverseOperations, + RefChainOperation signature, + MethodCall headMethodSign, + int weight) { + myQualifierClass = qualifierClass; + myReverseOperations = reverseOperations; + myHeadOperation = signature; + myHeadMethodCall = headMethodSign; + myWeight = weight; + } + + public boolean hasCast() { + return Arrays.stream(myReverseOperations).anyMatch(op -> op instanceof ChainOperation.TypeCast); + } + + @NotNull + public MethodCall getHeadMethodCall() { + return myHeadMethodCall; + } + + @NotNull + public RefChainOperation getHead() { + return myHeadOperation; + } + + public int length() { + return myReverseOperations.length; + } + + public PsiClass getQualifierClass() { + return myQualifierClass; + } + + @NotNull + public PsiMethod[] getFirst() { + return ((ChainOperation.MethodCall) myReverseOperations[0]).getCandidates(); + } + + public ChainOperation[] getPath() { + return ArrayUtil.reverseArray(myReverseOperations); + } + + public int getChainWeight() { + return myWeight; + } + + @Nullable + OperationChain continuationWithMethod(@NotNull MethodCall signature, + int weight, + @NotNull ChainCompletionContext context) { + OperationChain head = create(signature, weight, context); + if (head == null) return null; + + ChainOperation[] newReverseOperations = new ChainOperation[length() + 1]; + System.arraycopy(myReverseOperations, 0, newReverseOperations, 0, myReverseOperations.length); + newReverseOperations[length()] = head.getPath()[0]; + return new OperationChain(head.getQualifierClass(), newReverseOperations, head.getHead(), signature, Math.min(weight, getChainWeight())); + } + + @Nullable + OperationChain continuationWithCast(@NotNull TypeCast cast, + @NotNull ChainCompletionContext context) { + PsiClass operand = context.resolvePsiClass(cast.getLightRef()); + PsiClass castType = context.resolvePsiClass(cast.getCastTypeRef()); + if (operand == null || castType == null) return null; + + ChainOperation[] newReverseOperations = new ChainOperation[length() + 1]; + System.arraycopy(myReverseOperations, 0, newReverseOperations, 0, myReverseOperations.length); + newReverseOperations[length()] = new ChainOperation.TypeCast(operand, castType); + return new OperationChain(operand, newReverseOperations, cast, myHeadMethodCall, getChainWeight()); + } + + @Override + public String toString() { + ChainOperation[] path = getPath(); + return Arrays.toString(path) + " on " + myQualifierClass.getName(); + } + + @SuppressWarnings("ConstantConditions") + public static CompareResult compare(@NotNull OperationChain left, @NotNull OperationChain right) { + if (left.length() == 0 || right.length() == 0) { + throw new IllegalStateException("chains can't be empty"); + } + + int leftCurrentIdx = 0; + int rightCurrentIdx = 0; + + while (leftCurrentIdx < left.length() && rightCurrentIdx < right.length()) { + ChainOperation thisNext = left.myReverseOperations[leftCurrentIdx]; + ChainOperation thatNext = right.myReverseOperations[leftCurrentIdx]; + if (!lookSimilar(thisNext, thatNext)) { + return CompareResult.NOT_EQUAL; + } + leftCurrentIdx++; + rightCurrentIdx++; + } + if (leftCurrentIdx < left.length() && rightCurrentIdx == right.length()) { + return CompareResult.LEFT_CONTAINS_RIGHT; + } + if (leftCurrentIdx == left.length() && rightCurrentIdx < right.length()) { + return CompareResult.RIGHT_CONTAINS_LEFT; + } + + return CompareResult.EQUAL; + } + + public enum CompareResult { + LEFT_CONTAINS_RIGHT, + RIGHT_CONTAINS_LEFT, + EQUAL, + NOT_EQUAL + } + + static boolean lookSimilar(ChainOperation op1, ChainOperation op2) { + if (op1 instanceof ChainOperation.TypeCast || op2 instanceof ChainOperation.TypeCast) return false; + + PsiMethod[] methods1 = ((ChainOperation.MethodCall)op1).getCandidates(); + PsiMethod[] methods2 = ((ChainOperation.MethodCall)op2).getCandidates(); + + PsiMethod repr1 = methods1[0]; + PsiMethod repr2 = methods2[0]; + if (repr1.hasModifierProperty(PsiModifier.STATIC) || repr2.hasModifierProperty(PsiModifier.STATIC)) return false; + if (!repr1.getName().equals(repr2.getName()) || + repr1.getParameterList().getParametersCount() != repr2.getParameterList().getParametersCount()) { + return false; + } + Set methodSet1 = ContainerUtil.newHashSet(methods1); + Set methodSet2 = ContainerUtil.newHashSet(methods2); + if (ContainerUtil.intersects(methodSet1, methodSet2)) return true; + + Set deepestSupers1 = methodSet1.stream().flatMap(m -> Arrays.stream(m.findDeepestSuperMethods())).collect(Collectors.toSet()); + return methodSet2.stream().flatMap(m -> Arrays.stream(m.findDeepestSuperMethods())).anyMatch(deepestSupers1::contains); + } +} diff --git a/java/compiler/impl/src/com/intellij/compiler/chainsSearch/MethodIncompleteSignature.kt b/java/compiler/impl/src/com/intellij/compiler/chainsSearch/RefChainOperation.kt similarity index 52% rename from java/compiler/impl/src/com/intellij/compiler/chainsSearch/MethodIncompleteSignature.kt rename to java/compiler/impl/src/com/intellij/compiler/chainsSearch/RefChainOperation.kt index d976f7a1aae2..b12ace2190a6 100644 --- a/java/compiler/impl/src/com/intellij/compiler/chainsSearch/MethodIncompleteSignature.kt +++ b/java/compiler/impl/src/com/intellij/compiler/chainsSearch/RefChainOperation.kt @@ -16,60 +16,74 @@ package com.intellij.compiler.chainsSearch import com.intellij.compiler.backwardRefs.CompilerReferenceServiceEx +import com.intellij.compiler.chainsSearch.context.ChainCompletionContext import com.intellij.compiler.chainsSearch.context.ChainSearchTarget -import com.intellij.openapi.project.Project -import com.intellij.psi.* -import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.PsiArrayType +import com.intellij.psi.PsiClassType +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiModifier import com.intellij.psi.util.PsiUtil import org.jetbrains.jps.backwardRefs.LightRef import org.jetbrains.jps.backwardRefs.SignatureData -import java.util.function.Predicate -class MethodIncompleteSignature(val ref: LightRef.JavaLightMethodRef, - private val signatureData: SignatureData, - private val refService: CompilerReferenceServiceEx) { - companion object { +sealed class RefChainOperation { + abstract val qualifierRawName: String + + abstract val qualifierDef: LightRef.LightClassHierarchyElementDef + + abstract val lightRef: LightRef +} + +class TypeCast(override val lightRef: LightRef.LightClassHierarchyElementDef, + val castTypeRef: LightRef.LightClassHierarchyElementDef, + refService: CompilerReferenceServiceEx): RefChainOperation() { + override val qualifierRawName + get() = operandName.value + override val qualifierDef + get() = lightRef + + private val operandName = lazy(LazyThreadSafetyMode.NONE) { + refService.getName(lightRef.name) + } +} + +class MethodCall(override val lightRef: LightRef.JavaLightMethodRef, + private val signatureData: SignatureData, + private val context: ChainCompletionContext): RefChainOperation() { + + private companion object { val CONSTRUCTOR_METHOD_NAME = "" } - val name: String by lazy(LazyThreadSafetyMode.NONE) { - refService.getName(ref.name) + override val qualifierRawName: String + get() = owner.value + override val qualifierDef: LightRef.LightClassHierarchyElementDef + get() = lightRef.owner + + private val name = lazy(LazyThreadSafetyMode.NONE) { + context.refService.getName(lightRef.name) } - val ownerRef = ref.owner - - val owner: String by lazy(LazyThreadSafetyMode.NONE) { - refService.getName(ref.owner.name) + private val owner = lazy(LazyThreadSafetyMode.NONE) { + context.refService.getName(lightRef.owner.name) } - val rawReturnType: String by lazy(LazyThreadSafetyMode.NONE) { - refService.getName(signatureData.rawReturnType) + private val rawReturnType = lazy(LazyThreadSafetyMode.NONE) { + context.refService.getName(signatureData.rawReturnType) } - val parameterCount: Int - get() = ref.parameterCount - val isStatic: Boolean get() = signatureData.isStatic - fun resolveQualifier(project: Project, - resolveScope: GlobalSearchScope, - accessValidator: Predicate): PsiClass? { - val clazz = JavaPsiFacade.getInstance(project).findClass(owner, resolveScope) - return if (clazz != null && accessValidator.test(clazz)) clazz else null - } - - fun resolve(project: Project, - resolveScope: GlobalSearchScope, - accessValidator: Predicate): Array { - if (CONSTRUCTOR_METHOD_NAME == name) { + fun resolve(): Array { + if (CONSTRUCTOR_METHOD_NAME == name.value) { return PsiMethod.EMPTY_ARRAY } - val aClass = resolveQualifier(project, resolveScope, accessValidator) ?: return PsiMethod.EMPTY_ARRAY - return aClass.findMethodsByName(name, true) + val aClass = context.resolvePsiClass(qualifierDef) ?: return PsiMethod.EMPTY_ARRAY + return aClass.findMethodsByName(name.value, true) .filter { it.hasModifierProperty(PsiModifier.STATIC) == isStatic } .filter { !it.isDeprecated } - .filter { accessValidator.test(it) } + .filter { context.accessValidator().test(it) } .filter { val returnType = it.returnType when (signatureData.iteratorKind) { @@ -77,7 +91,7 @@ class MethodIncompleteSignature(val ref: LightRef.JavaLightMethodRef, when (returnType) { is PsiArrayType -> { val componentType = returnType.componentType - componentType is PsiClassType && componentType.resolve()?.qualifiedName == rawReturnType + componentType is PsiClassType && componentType.resolve()?.qualifiedName == rawReturnType.value } else -> false } @@ -85,11 +99,11 @@ class MethodIncompleteSignature(val ref: LightRef.JavaLightMethodRef, SignatureData.ITERATOR_ONE_DIM -> { val iteratorKind = ChainSearchTarget.getIteratorKind(PsiUtil.resolveClassInClassTypeOnly(returnType)) when { - iteratorKind != null -> PsiUtil.resolveClassInClassTypeOnly(PsiUtil.substituteTypeParameter(returnType, iteratorKind, 0, false))?.qualifiedName == rawReturnType + iteratorKind != null -> PsiUtil.resolveClassInClassTypeOnly(PsiUtil.substituteTypeParameter(returnType, iteratorKind, 0, false))?.qualifiedName == rawReturnType.value else -> false } } - SignatureData.ZERO_DIM -> returnType is PsiClassType && returnType.resolve()?.qualifiedName == rawReturnType + SignatureData.ZERO_DIM -> returnType is PsiClassType && returnType.resolve()?.qualifiedName == rawReturnType.value else -> throw IllegalStateException("kind is unsupported ${signatureData.iteratorKind}") } } @@ -101,23 +115,23 @@ class MethodIncompleteSignature(val ref: LightRef.JavaLightMethodRef, if (this === other) return true if (other?.javaClass != javaClass) return false - other as MethodIncompleteSignature + other as MethodCall - if (ref.owner != other.ref.owner) return false - if (ref.name != other.ref.name) return false + if (lightRef.owner != other.lightRef.owner) return false + if (lightRef.name != other.lightRef.name) return false if (signatureData != other.signatureData) return false return true } override fun hashCode(): Int { - var result = ref.owner.hashCode() - result = 31 * result + ref.name.hashCode() + var result = lightRef.owner.hashCode() + result = 31 * result + lightRef.name.hashCode() result = 31 * result + signatureData.hashCode() return result } override fun toString(): String { - return owner + (if (isStatic) "" else "#") + name + "(" + parameterCount + ")" + return qualifierRawName + (if (isStatic) "." else "#") + name + "(" + lightRef.parameterCount + ")" } -} \ No newline at end of file +} diff --git a/java/compiler/impl/src/com/intellij/compiler/chainsSearch/SearchInitializer.java b/java/compiler/impl/src/com/intellij/compiler/chainsSearch/SearchInitializer.java index 02bb2e51a938..0dd1efe69859 100644 --- a/java/compiler/impl/src/com/intellij/compiler/chainsSearch/SearchInitializer.java +++ b/java/compiler/impl/src/com/intellij/compiler/chainsSearch/SearchInitializer.java @@ -21,39 +21,39 @@ import java.util.*; public class SearchInitializer { private final ChainCompletionContext myContext; - private final LinkedList myQueue; - private final LinkedHashMap myChains; + private final LinkedList myQueue; + private final LinkedHashMap myChains; - public SearchInitializer(SortedSet indexValues, + public SearchInitializer(SortedSet indexValues, ChainCompletionContext context) { myContext = context; int size = indexValues.size(); - List chains = new ArrayList<>(size); + List chains = new ArrayList<>(size); populateFrequentlyUsedMethod(indexValues, chains); myQueue = new LinkedList<>(); myChains = new LinkedHashMap<>(chains.size()); - for (MethodChain chain : chains) { - MethodIncompleteSignature signature = chain.getHeadSignature(); + for (OperationChain chain : chains) { + MethodCall signature = (MethodCall)chain.getHead(); myQueue.add(chain); myChains.put(signature, chain); } } - public LinkedList getChainQueue() { + public LinkedList getChainQueue() { return myQueue; } - public LinkedHashMap getChains() { + public LinkedHashMap getChains() { return myChains; } - private void populateFrequentlyUsedMethod(SortedSet signatures, - List chains) { + private void populateFrequentlyUsedMethod(SortedSet signatures, + List chains) { int bestOccurrences = -1; - for (SignatureAndOccurrences indexValue : signatures) { - MethodChain methodChain = MethodChain.create(indexValue.getSignature(), indexValue.getOccurrenceCount(), myContext); - if (methodChain != null) { - chains.add(methodChain); + for (MethodRefAndOccurrences indexValue : signatures) { + OperationChain operationChain = OperationChain.create(indexValue.getSignature(), indexValue.getOccurrenceCount(), myContext); + if (operationChain != null) { + chains.add(operationChain); int occurrences = indexValue.getOccurrenceCount(); if (bestOccurrences == -1) { bestOccurrences = occurrences; diff --git a/java/compiler/impl/src/com/intellij/compiler/chainsSearch/completion/MethodChainCompletionContributor.java b/java/compiler/impl/src/com/intellij/compiler/chainsSearch/completion/MethodChainCompletionContributor.java index f97dfb21e8b9..6dcc2f11fb54 100644 --- a/java/compiler/impl/src/com/intellij/compiler/chainsSearch/completion/MethodChainCompletionContributor.java +++ b/java/compiler/impl/src/com/intellij/compiler/chainsSearch/completion/MethodChainCompletionContributor.java @@ -22,7 +22,7 @@ import com.intellij.compiler.backwardRefs.CompilerReferenceServiceEx; import com.intellij.compiler.backwardRefs.ReferenceIndexUnavailableException; import com.intellij.compiler.chainsSearch.ChainSearchMagicConstants; import com.intellij.compiler.chainsSearch.ChainSearcher; -import com.intellij.compiler.chainsSearch.MethodChain; +import com.intellij.compiler.chainsSearch.OperationChain; import com.intellij.compiler.chainsSearch.MethodChainLookupRangingHelper; import com.intellij.compiler.chainsSearch.context.ChainCompletionContext; import com.intellij.compiler.chainsSearch.context.ChainSearchTarget; @@ -92,13 +92,13 @@ public class MethodChainCompletionContributor extends CompletionContributor { private static List searchForLookups(ChainCompletionContext context) { CompilerReferenceServiceEx methodsUsageIndexReader = (CompilerReferenceServiceEx)CompilerReferenceService.getInstance(context.getProject()); ChainSearchTarget target = context.getTarget(); - List searchResult = + List searchResult = ChainSearcher.search(ChainSearchMagicConstants.MAX_CHAIN_SIZE, target, ChainSearchMagicConstants.MAX_SEARCH_RESULT_SIZE, context, methodsUsageIndexReader); - int maxWeight = searchResult.stream().mapToInt(MethodChain::getChainWeight).max().orElse(0); + int maxWeight = searchResult.stream().mapToInt(OperationChain::getChainWeight).max().orElse(0); return searchResult .stream() diff --git a/java/compiler/impl/src/com/intellij/compiler/chainsSearch/context/ChainCompletionContext.java b/java/compiler/impl/src/com/intellij/compiler/chainsSearch/context/ChainCompletionContext.java index 5234775f3278..1997981dec30 100644 --- a/java/compiler/impl/src/com/intellij/compiler/chainsSearch/context/ChainCompletionContext.java +++ b/java/compiler/impl/src/com/intellij/compiler/chainsSearch/context/ChainCompletionContext.java @@ -17,7 +17,7 @@ package com.intellij.compiler.chainsSearch.context; import com.intellij.compiler.CompilerReferenceService; import com.intellij.compiler.backwardRefs.CompilerReferenceServiceEx; -import com.intellij.compiler.chainsSearch.MethodIncompleteSignature; +import com.intellij.compiler.chainsSearch.MethodCall; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.NotNullLazyValue; @@ -31,6 +31,7 @@ import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.FactoryMap; import gnu.trove.THashSet; +import gnu.trove.TIntObjectHashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.backwardRefs.LightRef; @@ -62,22 +63,23 @@ public class ChainCompletionContext { @NotNull private final PsiResolveHelper myResolveHelper; @NotNull - private final Map myQualifierClassResolver; + private final TIntObjectHashMap myQualifierClassResolver; @NotNull - private final Map myResolver; + private final Map myResolver; + @NotNull + private final CompilerReferenceServiceEx myRefServiceEx; private final NotNullLazyValue> myContextClassReferences = new NotNullLazyValue>() { @NotNull @Override protected Set compute() { - CompilerReferenceServiceEx referenceServiceEx = (CompilerReferenceServiceEx)CompilerReferenceService.getInstance(myProject); return getContextTypes() .stream() .map(PsiUtil::resolveClassInType) .filter(Objects::nonNull) .map(c -> ClassUtil.getJVMClassName(c)) .filter(Objects::nonNull) - .mapToInt(c -> referenceServiceEx.getNameId(c)) + .mapToInt(c -> myRefServiceEx.getNameId(c)) .filter(n -> n != 0) .mapToObj(n -> new LightRef.JavaLightClassRef(n)).collect(Collectors.toSet()); } @@ -92,8 +94,9 @@ public class ChainCompletionContext { myResolveScope = context.getResolveScope(); myProject = context.getProject(); myResolveHelper = PsiResolveHelper.SERVICE.getInstance(myProject); - myQualifierClassResolver = FactoryMap.create(sign1 -> sign1.resolveQualifier(myProject, myResolveScope, accessValidator())); - myResolver = FactoryMap.create(sign -> sign.resolve(myProject, myResolveScope, accessValidator())); + myQualifierClassResolver = new TIntObjectHashMap<>(); + myResolver = FactoryMap.create(sign -> sign.resolve()); + myRefServiceEx = (CompilerReferenceServiceEx)CompilerReferenceService.getInstance(myProject); } @NotNull @@ -113,6 +116,11 @@ public class ChainCompletionContext { return false; } + @NotNull + public CompilerReferenceServiceEx getRefService() { + return myRefServiceEx; + } + @NotNull public PsiElement getContextPsi() { return myContext; @@ -159,16 +167,28 @@ public class ChainCompletionContext { } @Nullable - public PsiClass resolveQualifierClass(MethodIncompleteSignature sign) { - return myQualifierClassResolver.get(sign); + public PsiClass resolvePsiClass(LightRef.LightClassHierarchyElementDef aClass) { + int nameId = aClass.getName(); + if (myQualifierClassResolver.contains(nameId)) { + return myQualifierClassResolver.get(nameId); + } else { + PsiClass psiClass = null; + String name = myRefServiceEx.getName(nameId); + PsiClass resolvedClass = JavaPsiFacade.getInstance(getProject()).findClass(name, myResolveScope); + if (resolvedClass != null && accessValidator().test(resolvedClass)) { + psiClass = resolvedClass; + } + myQualifierClassResolver.put(nameId, psiClass); + return psiClass; + } } @NotNull - public PsiMethod[] resolve(MethodIncompleteSignature sign) { + public PsiMethod[] resolve(MethodCall sign) { return myResolver.get(sign); } - private Predicate accessValidator() { + public Predicate accessValidator() { return m -> myResolveHelper.isAccessible(m, myContext, null); } diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/CastingLookupElementDecorator.java b/java/java-impl/src/com/intellij/codeInsight/completion/CastingLookupElementDecorator.java index fc17c3c6fa2e..a6f4abfca45b 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/CastingLookupElementDecorator.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/CastingLookupElementDecorator.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -79,7 +79,7 @@ public class CastingLookupElementDecorator extends LookupElementDecorator + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/methodChains/testChainWithCastOnContextVariable/TestIndex.java b/java/java-tests/testData/codeInsight/completion/methodChains/testChainWithCastOnContextVariable/TestIndex.java new file mode 100644 index 000000000000..9e02d9826597 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/methodChains/testChainWithCastOnContextVariable/TestIndex.java @@ -0,0 +1,19 @@ +interface Editor {} +interface EditorEx extends Editor { + MarkupModelEx getMarkupModel(); +} +interface MarkupModelEx {} + +class Test { + void m(Editor editor) { + ((EditorEx)editor).getMarkupModel(); + ((EditorEx)editor).getMarkupModel(); + ((EditorEx)editor).getMarkupModel(); + ((EditorEx)editor).getMarkupModel(); + ((EditorEx)editor).getMarkupModel(); + ((EditorEx)editor).getMarkupModel(); + ((EditorEx)editor).getMarkupModel(); + ((EditorEx)editor).getMarkupModel(); + ((EditorEx)editor).getMarkupModel(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/methodChains/testChainWithCastOnStaticMethod/TestCompletion.java b/java/java-tests/testData/codeInsight/completion/methodChains/testChainWithCastOnStaticMethod/TestCompletion.java new file mode 100644 index 000000000000..762e124f58e4 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/methodChains/testChainWithCastOnStaticMethod/TestCompletion.java @@ -0,0 +1,15 @@ +interface InspectionManager { + static InspectionManager getInstance() { + return null; + } +} +interface InspectionManagerEx extends InspectionManager { + GlobalInspectionContext createContext(); +} +interface GlobalInspectionContext {} + +class Test { + void m() { + GlobalInspectionContext c = + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/methodChains/testChainWithCastOnStaticMethod/TestIndex.java b/java/java-tests/testData/codeInsight/completion/methodChains/testChainWithCastOnStaticMethod/TestIndex.java new file mode 100644 index 000000000000..81fd6179e38c --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/methodChains/testChainWithCastOnStaticMethod/TestIndex.java @@ -0,0 +1,24 @@ +interface InspectionManager { + static InspectionManager getInstance() { + return null; + } +} +interface InspectionManagerEx extends InspectionManager { + GlobalInspectionContext createContext(); +} +interface GlobalInspectionContext {} + +class Test { + void m() { + ((InspectionManagerEx)InspectionManager.getInstance()).createContext(); + ((InspectionManagerEx)InspectionManager.getInstance()).createContext(); + ((InspectionManagerEx)InspectionManager.getInstance()).createContext(); + ((InspectionManagerEx)InspectionManager.getInstance()).createContext(); + ((InspectionManagerEx)InspectionManager.getInstance()).createContext(); + ((InspectionManagerEx)InspectionManager.getInstance()).createContext(); + ((InspectionManagerEx)InspectionManager.getInstance()).createContext(); + ((InspectionManagerEx)InspectionManager.getInstance()).createContext(); + ((InspectionManagerEx)InspectionManager.getInstance()).createContext(); + ((InspectionManagerEx)InspectionManager.getInstance()).createContext(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/methodChains/testChainWithCastOnVariableOutsideContext/TestCompletion.java b/java/java-tests/testData/codeInsight/completion/methodChains/testChainWithCastOnVariableOutsideContext/TestCompletion.java new file mode 100644 index 000000000000..ffbe7a3383b8 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/methodChains/testChainWithCastOnVariableOutsideContext/TestCompletion.java @@ -0,0 +1,11 @@ +interface Editor {} +interface EditorEx extends Editor { + MarkupModelEx getMarkupModel(); +} +interface MarkupModelEx {} + +class Test { + void m() { + MarkupModelEx m = + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/methodChains/testChainWithCastOnVariableOutsideContext/TestIndex.java b/java/java-tests/testData/codeInsight/completion/methodChains/testChainWithCastOnVariableOutsideContext/TestIndex.java new file mode 100644 index 000000000000..9e02d9826597 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/methodChains/testChainWithCastOnVariableOutsideContext/TestIndex.java @@ -0,0 +1,19 @@ +interface Editor {} +interface EditorEx extends Editor { + MarkupModelEx getMarkupModel(); +} +interface MarkupModelEx {} + +class Test { + void m(Editor editor) { + ((EditorEx)editor).getMarkupModel(); + ((EditorEx)editor).getMarkupModel(); + ((EditorEx)editor).getMarkupModel(); + ((EditorEx)editor).getMarkupModel(); + ((EditorEx)editor).getMarkupModel(); + ((EditorEx)editor).getMarkupModel(); + ((EditorEx)editor).getMarkupModel(); + ((EditorEx)editor).getMarkupModel(); + ((EditorEx)editor).getMarkupModel(); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/MethodChainsCompletionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/MethodChainsCompletionTest.java index bd4b99d71002..97e8bc884a2f 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/MethodChainsCompletionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/MethodChainsCompletionTest.java @@ -23,7 +23,9 @@ import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.compiler.chainsSearch.ChainRelevance; import com.intellij.compiler.chainsSearch.completion.MethodChainCompletionContributor; import com.intellij.compiler.chainsSearch.completion.lookup.JavaRelevantChainLookupElement; +import com.intellij.openapi.roots.LanguageLevelProjectExtension; import com.intellij.openapi.util.registry.Registry; +import com.intellij.pom.java.LanguageLevel; import com.intellij.testFramework.SkipSlowTestLocally; import com.intellij.util.SmartList; @@ -44,6 +46,7 @@ public class MethodChainsCompletionTest extends AbstractCompilerAwareTest { Registry.get(MethodChainCompletionContributor.REGISTRY_KEY).setValue(true, myFixture.getTestRootDisposable()); myDefaultAutoCompleteOnCodeCompletion = CodeInsightSettings.getInstance().AUTOCOMPLETE_ON_CODE_COMPLETION; CodeInsightSettings.getInstance().AUTOCOMPLETE_ON_SMART_TYPE_COMPLETION = false; + LanguageLevelProjectExtension.getInstance(getProject()).setLanguageLevel(LanguageLevel.JDK_1_8); } @Override @@ -240,6 +243,20 @@ public class MethodChainsCompletionTest extends AbstractCompilerAwareTest { assertEquals("psiElement.getProject", element.getLookupString()); } + public void testChainWithCastOnContextVariable() { + JavaRelevantChainLookupElement element = assertOneElement(doCompletion()); + assertEquals("(EditorEx)editor.getMarkupModel", element.toString()); + } + + public void testChainWithCastOnVariableOutsideContext() { + assertEmpty(doCompletion()); + } + + public void testChainWithCastOnStaticMethod() { + JavaRelevantChainLookupElement element = assertOneElement(doCompletion()); + assertEquals("(InspectionManagerEx)getInstance().createContext", element.toString()); + } + public void assertAdvisorLookupElementEquals(String lookupText, int unreachableParametersCount, int chainSize, diff --git a/jps/jps-builders-6/src/org/jetbrains/jps/javac/ast/JavacReferenceCollectorListener.java b/jps/jps-builders-6/src/org/jetbrains/jps/javac/ast/JavacReferenceCollectorListener.java index 1a4e71ef5990..e0007ae56657 100644 --- a/jps/jps-builders-6/src/org/jetbrains/jps/javac/ast/JavacReferenceCollectorListener.java +++ b/jps/jps-builders-6/src/org/jetbrains/jps/javac/ast/JavacReferenceCollectorListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -21,10 +21,7 @@ import com.sun.source.util.*; import com.sun.tools.javac.util.ClientCodeException; import gnu.trove.TObjectIntHashMap; import org.jetbrains.annotations.Nullable; -import org.jetbrains.jps.javac.ast.api.JavacDef; -import org.jetbrains.jps.javac.ast.api.JavacFileData; -import org.jetbrains.jps.javac.ast.api.JavacNameTable; -import org.jetbrains.jps.javac.ast.api.JavacRef; +import org.jetbrains.jps.javac.ast.api.*; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -230,6 +227,7 @@ final class JavacReferenceCollectorListener implements TaskListener { myFileData = new JavacFileData(filePath, createReferenceHolder(), myDivideImportRefs ? createReferenceHolder() : EMPTY_T_OBJ_INT_MAP, + new ArrayList(), createDefinitionHolder()); myTreeHelper = new JavacTreeHelper(unitTree, myTreeUtility); } @@ -242,6 +240,10 @@ final class JavacReferenceCollectorListener implements TaskListener { myFileData.getDefs().add(def); } + public void sinkTypeCast(JavacTypeCast typeCast) { + myFileData.getCasts().add(typeCast); + } + @Nullable JavacRef.JavacElementRefBase asJavacRef(Element element) { return asJavacRef(element, null); diff --git a/jps/jps-builders-6/src/org/jetbrains/jps/javac/ast/JavacTreeRefScanner.java b/jps/jps-builders-6/src/org/jetbrains/jps/javac/ast/JavacTreeRefScanner.java index b85ef0b241e8..b621691eafa4 100644 --- a/jps/jps-builders-6/src/org/jetbrains/jps/javac/ast/JavacTreeRefScanner.java +++ b/jps/jps-builders-6/src/org/jetbrains/jps/javac/ast/JavacTreeRefScanner.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -21,6 +21,7 @@ import com.sun.source.util.TreeScanner; import org.jetbrains.jps.javac.ast.api.JavacDef; import org.jetbrains.jps.javac.ast.api.JavacNameTable; import org.jetbrains.jps.javac.ast.api.JavacRef; +import org.jetbrains.jps.javac.ast.api.JavacTypeCast; import javax.lang.model.element.*; import javax.lang.model.type.ArrayType; @@ -206,6 +207,24 @@ class JavacTreeRefScanner extends TreeScanner myRefs; private final TObjectIntHashMap myImportRefs; + private final List myCasts; private final List myDefs; public JavacFileData(@NotNull String path, @NotNull TObjectIntHashMap refs, @NotNull TObjectIntHashMap importRefs, + @NotNull List casts, @NotNull List defs) { myFilePath = path; myRefs = refs; myImportRefs = importRefs; + myCasts = casts; myDefs = defs; } @@ -62,6 +65,11 @@ public class JavacFileData { return myImportRefs; } + @NotNull + public List getCasts() { + return myCasts; + } + @NotNull public List getDefs() { return myDefs; @@ -75,6 +83,7 @@ public class JavacFileData { stream.writeUTF(getFilePath()); saveRefs(stream, getRefs()); saveRefs(stream, getImportRefs()); + saveCasts(stream, getCasts()); saveDefs(stream, getDefs()); } catch (IOException e) { @@ -91,6 +100,7 @@ public class JavacFileData { return new JavacFileData(in.readUTF(), readRefs(in), readRefs(in), + readCasts(in), readDefs(in)); } catch (IOException e) { @@ -246,4 +256,24 @@ public class JavacFileData { }); return modifierList.isEmpty() ? Collections.emptySet() : EnumSet.copyOf(modifierList); } + + private static void saveCasts(@NotNull final DataOutput output, @NotNull List casts) throws IOException { + DataInputOutputUtilRt.writeSeq(output, casts, new ThrowableConsumer() { + @Override + public void consume(JavacTypeCast cast) throws IOException { + writeJavacRef(output, cast.getOperandType()); + writeJavacRef(output, cast.getCastType()); + } + }); + } + + @NotNull + private static List readCasts(@NotNull final DataInput input) throws IOException { + return DataInputOutputUtilRt.readSeq(input, new ThrowableComputable() { + @Override + public JavacTypeCast compute() throws IOException { + return new JavacTypeCast((JavacRef.JavacClass)readJavacRef(input), (JavacRef.JavacClass)readJavacRef(input)); + } + }); + } } diff --git a/jps/jps-builders-6/src/org/jetbrains/jps/javac/ast/api/JavacRef.java b/jps/jps-builders-6/src/org/jetbrains/jps/javac/ast/api/JavacRef.java index c5947dd5ae46..277489319aea 100644 --- a/jps/jps-builders-6/src/org/jetbrains/jps/javac/ast/api/JavacRef.java +++ b/jps/jps-builders-6/src/org/jetbrains/jps/javac/ast/api/JavacRef.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. diff --git a/jps/jps-builders-6/src/org/jetbrains/jps/javac/ast/api/JavacTypeCast.java b/jps/jps-builders-6/src/org/jetbrains/jps/javac/ast/api/JavacTypeCast.java new file mode 100644 index 000000000000..5f81fa6f9351 --- /dev/null +++ b/jps/jps-builders-6/src/org/jetbrains/jps/javac/ast/api/JavacTypeCast.java @@ -0,0 +1,40 @@ +/* + * Copyright 2000-2017 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.jps.javac.ast.api; + +import org.jetbrains.annotations.NotNull; + +public class JavacTypeCast { + @NotNull + private final JavacRef.JavacClass myOperandType; + @NotNull + private final JavacRef.JavacClass myCastType; + + public JavacTypeCast(@NotNull JavacRef.JavacClass operandType, @NotNull JavacRef.JavacClass castType) { + myOperandType = operandType; + myCastType = castType; + } + + @NotNull + public JavacRef.JavacClass getOperandType() { + return myOperandType; + } + + @NotNull + public JavacRef.JavacClass getCastType() { + return myCastType; + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/backwardRefs/BackwardReferenceIndexUtil.java b/jps/jps-builders/src/org/jetbrains/jps/backwardRefs/BackwardReferenceIndexUtil.java index b7c738f03c99..b07f81632873 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/backwardRefs/BackwardReferenceIndexUtil.java +++ b/jps/jps-builders/src/org/jetbrains/jps/backwardRefs/BackwardReferenceIndexUtil.java @@ -24,25 +24,31 @@ import gnu.trove.TObjectIntHashMap; import org.jetbrains.jps.backwardRefs.index.CompiledFileData; import org.jetbrains.jps.javac.ast.api.JavacDef; import org.jetbrains.jps.javac.ast.api.JavacRef; +import org.jetbrains.jps.javac.ast.api.JavacTypeCast; import java.io.IOException; -import java.util.*; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; public class BackwardReferenceIndexUtil { private static final Logger LOG = Logger.getInstance(BackwardReferenceIndexUtil.class); static void registerFile(String filePath, TObjectIntHashMap refs, - List defs, + Collection defs, + Collection casts, final BackwardReferenceIndexWriter writer) { try { final int fileId = writer.enumeratePath(filePath); int funExprId = 0; - final Map definitions = new HashMap<>(defs.size()); - final Map> backwardHierarchyMap = new HashMap<>(); - final Map> signatureData = new THashMap<>(); + Map definitions = new HashMap<>(defs.size()); + Map> backwardHierarchyMap = new HashMap<>(); + Map> signatureData = new THashMap<>(); + THashMap> castMap = new THashMap<>(); + final AnonymousClassEnumerator anonymousClassEnumerator = new AnonymousClassEnumerator(); @@ -109,7 +115,16 @@ public class BackwardReferenceIndexUtil { if (exception[0] != null) { throw exception[0]; } - writer.writeData(fileId, new CompiledFileData(backwardHierarchyMap, convertedRefs, definitions, signatureData)); + + for (JavacTypeCast cast : casts) { + LightRef enumeratedCastType = writer.enumerateNames(cast.getCastType(), name -> null); + if (enumeratedCastType == null) continue; + LightRef enumeratedOperandType = writer.enumerateNames(cast.getOperandType(), name -> null); + if (enumeratedOperandType == null) continue; + castMap.computeIfAbsent(enumeratedCastType, t -> new SmartList<>()).add(enumeratedOperandType); + } + + writer.writeData(fileId, new CompiledFileData(backwardHierarchyMap, castMap, convertedRefs, definitions, signatureData)); } catch (IOException e) { writer.setRebuildCause(e); diff --git a/jps/jps-builders/src/org/jetbrains/jps/backwardRefs/BackwardReferenceRegistrar.java b/jps/jps-builders/src/org/jetbrains/jps/backwardRefs/BackwardReferenceRegistrar.java index 82163edd81f7..018659aec2fb 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/backwardRefs/BackwardReferenceRegistrar.java +++ b/jps/jps-builders/src/org/jetbrains/jps/backwardRefs/BackwardReferenceRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -16,11 +16,12 @@ package org.jetbrains.jps.backwardRefs; import gnu.trove.TObjectIntHashMap; +import org.jetbrains.jps.javac.ast.api.JavacTypeCast; import org.jetbrains.jps.javac.ast.api.JavacDef; import org.jetbrains.jps.javac.ast.api.JavacFileReferencesRegistrar; import org.jetbrains.jps.javac.ast.api.JavacRef; -import java.util.List; +import java.util.Collection; public class BackwardReferenceRegistrar implements JavacFileReferencesRegistrar { private volatile BackwardReferenceIndexWriter myWriter; @@ -41,7 +42,10 @@ public class BackwardReferenceRegistrar implements JavacFileReferencesRegistrar } @Override - public void registerFile(String filePath, TObjectIntHashMap refs, List defs) { - BackwardReferenceIndexUtil.registerFile(filePath, refs, defs, myWriter); + public void registerFile(String filePath, + TObjectIntHashMap refs, + Collection defs, + Collection casts) { + BackwardReferenceIndexUtil.registerFile(filePath, refs, defs, casts, myWriter); } } diff --git a/jps/jps-builders/src/org/jetbrains/jps/backwardRefs/index/CompiledFileData.java b/jps/jps-builders/src/org/jetbrains/jps/backwardRefs/index/CompiledFileData.java index f1d2ebb09aa1..af8af9e6488a 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/backwardRefs/index/CompiledFileData.java +++ b/jps/jps-builders/src/org/jetbrains/jps/backwardRefs/index/CompiledFileData.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -24,15 +24,18 @@ import java.util.Map; public class CompiledFileData { private final Map> myBackwardHierarchyMap; + private final Map> myCasts; private final Map myReferences; private final Map myDefinitions; private final Map> mySignatureData; public CompiledFileData(@NotNull Map> backwardHierarchyMap, + @NotNull Map> casts, @NotNull Map references, @NotNull Map definitions, @NotNull Map> signatureData) { myBackwardHierarchyMap = backwardHierarchyMap; + myCasts = casts; myReferences = references; myDefinitions = definitions; mySignatureData = signatureData; @@ -57,4 +60,9 @@ public class CompiledFileData { public Map> getSignatureData() { return mySignatureData; } + + @NotNull + public Map> getCasts() { + return myCasts; + } } diff --git a/jps/jps-builders/src/org/jetbrains/jps/backwardRefs/index/CompilerIndices.java b/jps/jps-builders/src/org/jetbrains/jps/backwardRefs/index/CompilerIndices.java index ec8f5a618e29..06908429ca47 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/backwardRefs/index/CompilerIndices.java +++ b/jps/jps-builders/src/org/jetbrains/jps/backwardRefs/index/CompilerIndices.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -16,7 +16,9 @@ package org.jetbrains.jps.backwardRefs.index; import com.intellij.openapi.util.io.DataInputOutputUtilRt; -import com.intellij.util.indexing.*; +import com.intellij.util.indexing.DataIndexer; +import com.intellij.util.indexing.IndexExtension; +import com.intellij.util.indexing.IndexId; import com.intellij.util.io.DataExternalizer; import com.intellij.util.io.DataInputOutputUtil; import com.intellij.util.io.KeyDescriptor; @@ -35,18 +37,53 @@ import java.util.List; public class CompilerIndices { //TODO manage version separately - public final static int VERSION = 5; + public final static int VERSION = 6; public final static IndexId BACK_USAGES = IndexId.create("back.refs"); public final static IndexId> BACK_HIERARCHY = IndexId.create("back.hierarchy"); public final static IndexId BACK_CLASS_DEF = IndexId.create("back.class.def"); public final static IndexId> BACK_MEMBER_SIGN = IndexId.create("back.member.sign"); + public final static IndexId> BACK_CAST = IndexId.create("back.cast"); public static List> getIndices() { return Arrays.asList(createBackwardClassDefinitionExtension(), createBackwardUsagesExtension(), createBackwardHierarchyExtension(), - createBackwardSignatureExtension()); + createBackwardSignatureExtension(), + createBackwardCastExtension()); + } + + private static IndexExtension, CompiledFileData> createBackwardCastExtension() { + return new IndexExtension, CompiledFileData>() { + @NotNull + @Override + public IndexId> getName() { + return BACK_CAST; + } + + @NotNull + @Override + public DataIndexer, CompiledFileData> getIndexer() { + return CompiledFileData::getCasts; + } + + @NotNull + @Override + public KeyDescriptor getKeyDescriptor() { + return LightRefDescriptor.INSTANCE; + } + + @NotNull + @Override + public DataExternalizer> getValueExternalizer() { + return createLightRefSeqExternalizer(); + } + + @Override + public int getVersion() { + return VERSION; + } + }; } private static IndexExtension createBackwardUsagesExtension() { diff --git a/jps/jps-builders/src/org/jetbrains/jps/javac/ast/InProcessRefCollectorCompilerToolExtension.java b/jps/jps-builders/src/org/jetbrains/jps/javac/ast/InProcessRefCollectorCompilerToolExtension.java index d776c938ef0c..a355a525d6cf 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/javac/ast/InProcessRefCollectorCompilerToolExtension.java +++ b/jps/jps-builders/src/org/jetbrains/jps/javac/ast/InProcessRefCollectorCompilerToolExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -78,7 +78,7 @@ public class InProcessRefCollectorCompilerToolExtension extends AbstractRefColle public void consume(JavacFileData data) { for (JavacFileReferencesRegistrar registrar : myRegistrars) { if (registrar.isEnabled()) { - registrar.registerFile(data.getFilePath(), registrar.onlyImports() ? data.getImportRefs() : data.getRefs(), data.getDefs()); + registrar.registerFile(data.getFilePath(), registrar.onlyImports() ? data.getImportRefs() : data.getRefs(), data.getDefs(), data.getCasts()); } } } diff --git a/jps/jps-builders/src/org/jetbrains/jps/javac/ast/api/JavacFileReferencesRegistrar.java b/jps/jps-builders/src/org/jetbrains/jps/javac/ast/api/JavacFileReferencesRegistrar.java index e7fde87db14f..54f748496837 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/javac/ast/api/JavacFileReferencesRegistrar.java +++ b/jps/jps-builders/src/org/jetbrains/jps/javac/ast/api/JavacFileReferencesRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -17,7 +17,7 @@ package org.jetbrains.jps.javac.ast.api; import gnu.trove.TObjectIntHashMap; -import java.util.List; +import java.util.Collection; public interface JavacFileReferencesRegistrar { void initialize(); @@ -26,5 +26,8 @@ public interface JavacFileReferencesRegistrar { boolean onlyImports(); - void registerFile(String filePath, TObjectIntHashMap refs, List defs); + void registerFile(String filePath, + TObjectIntHashMap refs, + Collection defs, + Collection casts); } diff --git a/jps/jps-builders/testData/referencesIndex/castData/Foo.java b/jps/jps-builders/testData/referencesIndex/castData/Foo.java new file mode 100644 index 000000000000..04f47d8c7f50 --- /dev/null +++ b/jps/jps-builders/testData/referencesIndex/castData/Foo.java @@ -0,0 +1,5 @@ +class Foo { + void m(CharSequence c) { + ((String)c).getBytes(); + } +} \ No newline at end of file diff --git a/jps/jps-builders/testData/referencesIndex/castData/initialIndex.txt b/jps/jps-builders/testData/referencesIndex/castData/initialIndex.txt new file mode 100644 index 000000000000..026a7ce3f392 --- /dev/null +++ b/jps/jps-builders/testData/referencesIndex/castData/initialIndex.txt @@ -0,0 +1,18 @@ +Backward Hierarchy: +java.lang.Object -> Foo + +Backward References: +Foo in Foo occurrences = 1 +Foo.m(1) in Foo occurrences = 1 +java.lang.CharSequence in Foo occurrences = 1 +java.lang.String in Foo occurrences = 1 +java.lang.String.getBytes(0) in Foo occurrences = 1 + +Class Definitions: +Foo in Foo + +Members Signatures: + + +Type Casts: +java.lang.String -> java.lang.CharSequence \ No newline at end of file diff --git a/jps/jps-builders/testData/referencesIndex/castDataArrays/Foo.java b/jps/jps-builders/testData/referencesIndex/castDataArrays/Foo.java new file mode 100644 index 000000000000..9f56446c850f --- /dev/null +++ b/jps/jps-builders/testData/referencesIndex/castDataArrays/Foo.java @@ -0,0 +1,7 @@ +import java.util.*; + +class Foo { + void m(Object[] os) { + int length = ((String[]) os).length; + } +} \ No newline at end of file diff --git a/jps/jps-builders/testData/referencesIndex/castDataArrays/initialIndex.txt b/jps/jps-builders/testData/referencesIndex/castDataArrays/initialIndex.txt new file mode 100644 index 000000000000..4aa3fcc046fe --- /dev/null +++ b/jps/jps-builders/testData/referencesIndex/castDataArrays/initialIndex.txt @@ -0,0 +1,14 @@ +Backward Hierarchy: +java.lang.Object -> Foo + +Backward References: +Array.length in Foo occurrences = 1 +Foo in Foo occurrences = 1 +Foo.m(1) in Foo occurrences = 1 +java.lang.Object in Foo occurrences = 1 +java.lang.String in Foo occurrences = 1 + +Class Definitions: +Foo in Foo + +Members Signatures: diff --git a/jps/jps-builders/testData/referencesIndex/castDataGenerics/Foo.java b/jps/jps-builders/testData/referencesIndex/castDataGenerics/Foo.java new file mode 100644 index 000000000000..c74ad7c3031a --- /dev/null +++ b/jps/jps-builders/testData/referencesIndex/castDataGenerics/Foo.java @@ -0,0 +1,7 @@ +import java.util.*; + +class Foo { + void m(Collection c) { + ((List)c).get(1); + } +} \ No newline at end of file diff --git a/jps/jps-builders/testData/referencesIndex/castDataGenerics/initialIndex.txt b/jps/jps-builders/testData/referencesIndex/castDataGenerics/initialIndex.txt new file mode 100644 index 000000000000..56806a7e8026 --- /dev/null +++ b/jps/jps-builders/testData/referencesIndex/castDataGenerics/initialIndex.txt @@ -0,0 +1,19 @@ +Backward Hierarchy: +java.lang.Object -> Foo + +Backward References: +Foo in Foo occurrences = 1 +Foo.m(1) in Foo occurrences = 1 +java.lang.String in Foo occurrences = 2 +java.util.Collection in Foo occurrences = 1 +java.util.List in Foo occurrences = 1 +java.util.List.get(1) in Foo occurrences = 1 + +Class Definitions: +Foo in Foo + +Members Signatures: + + +Type Casts: +java.util.List -> java.util.Collection \ No newline at end of file diff --git a/jps/jps-builders/testSrc/org/jetbrains/references/ReferenceIndexTest.kt b/jps/jps-builders/testSrc/org/jetbrains/references/ReferenceIndexTest.kt index 8840c7503136..f14ba9239ead 100644 --- a/jps/jps-builders/testSrc/org/jetbrains/references/ReferenceIndexTest.kt +++ b/jps/jps-builders/testSrc/org/jetbrains/references/ReferenceIndexTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -181,5 +181,17 @@ class ReferenceIndexTest : ReferenceIndexTestBase() { fun testDefaultConstructorUsage() { assertIndexOnRebuild("Foo.java") } + + fun testCastData() { + assertIndexOnRebuild("Foo.java") + } + + fun testCastDataArrays() { + assertIndexOnRebuild("Foo.java") + } + + fun testCastDataGenerics() { + assertIndexOnRebuild("Foo.java") + } } diff --git a/jps/jps-builders/testSrc/org/jetbrains/references/ReferenceIndexTestBase.kt b/jps/jps-builders/testSrc/org/jetbrains/references/ReferenceIndexTestBase.kt index 742ae34b7423..56bb70ed6315 100644 --- a/jps/jps-builders/testSrc/org/jetbrains/references/ReferenceIndexTestBase.kt +++ b/jps/jps-builders/testSrc/org/jetbrains/references/ReferenceIndexTestBase.kt @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -160,6 +160,25 @@ abstract class ReferenceIndexTestBase : JpsBuildTestCase() { signs.sort() result.append(signs.joinToString(separator = "\n")) + val typeCasts = mutableListOf() + storage(index, CompilerIndices.BACK_CAST).processKeys {castType -> + val operands = mutableListOf() + val valueIt = index[CompilerIndices.BACK_CAST].getData(castType).valueIterator + while (valueIt.hasNext()) { + val nextRefs = valueIt.next() + nextRefs.mapTo(operands) { it.asText(nameEnumerator) } + } + if (!operands.isEmpty()) { + typeCasts.add(castType.asText(nameEnumerator) + " -> " + operands.joinToString(separator = " ")) + } + true + } + if (typeCasts.isNotEmpty()) { + result.append("\n\nType Casts:\n") + typeCasts.sort() + result.append(typeCasts.joinToString(separator = "\n")) + } + return result.toString() } finally {