speedup functional expression search (IDEA-159107)

* filter inapplicable expressions without loading AST if possible, via approximate resolve
* for that, store the approximate call chains in index
* iterate over files once, not for each empty marker Runnable interface separately
* don't rely on generic method parameter index: it's huge, memory-hungry and works only in Java
This commit is contained in:
peter
2016-08-13 12:12:19 +02:00
parent e8ea2598f3
commit 9776d5a28d
12 changed files with 762 additions and 678 deletions
@@ -1,87 +0,0 @@
/*
* Copyright 2000-2016 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.psi.impl.java;
import com.intellij.lang.LighterAST;
import com.intellij.lang.LighterASTNode;
import com.intellij.psi.impl.source.JavaLightTreeUtil;
import com.intellij.psi.impl.source.tree.RecursiveLighterASTNodeWalkingVisitor;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.intellij.psi.impl.source.tree.JavaElementType.*;
/**
* @author peter
*/
public class StreamApiDetector {
public static final Set<String> STREAM_INTERMEDIATE_METHODS = ContainerUtil.newHashSet("filter",
"map", "mapToInt", "mapToLong", "mapToDouble",
"flatMap", "flatMapToInt", "flatMapToLong", "flatMapToDouble",
"distinct", "sorted", "peek", "limit", "skip",
"forEach", "forEachOrdered",
"reduce", "collect", "max",
"anyMatch", "allMatch", "noneMatch",
"findFirst", "findAny");
static boolean isStreamApiCall(LighterAST tree, LighterASTNode methodCall) {
LighterASTNode methodExpr = tree.getChildren(methodCall).get(0);
String name = JavaLightTreeUtil.getNameIdentifierText(tree, methodExpr);
if (STREAM_INTERMEDIATE_METHODS.contains(name)) {
LighterASTNode qualifier = tree.getChildren(methodExpr).get(0);
return qualifier.getTokenType() == METHOD_CALL_EXPRESSION && isStreamApiCall(tree, qualifier);
}
return isRootCall(tree, methodCall, methodExpr, name);
}
private static boolean isRootCall(LighterAST tree, LighterASTNode methodCall, LighterASTNode methodExpr, String name) {
if ("stream".equals(name) || "parallelStream".equals(name)) {
List<LighterASTNode> argList = JavaLightTreeUtil.getArgList(tree, methodCall);
if (argList == null) return false;
return argList.isEmpty() || !argList.isEmpty() && hasQualifier(tree, methodExpr, "Arrays");
}
if ("of".equals(name)) {
List<LighterASTNode> argList = JavaLightTreeUtil.getArgList(tree, methodCall);
return argList != null && !argList.isEmpty() && hasQualifier(tree, methodExpr, "Stream");
}
return false;
}
private static boolean hasQualifier(LighterAST tree, LighterASTNode methodExpr, String refName) {
LighterASTNode node = tree.getChildren(methodExpr).get(0);
return node.getTokenType() == REFERENCE_EXPRESSION &&
refName.equals(JavaLightTreeUtil.getNameIdentifierText(tree, node)) &&
!hasContextVariable(tree, node, refName);
}
private static boolean hasContextVariable(final LighterAST tree, LighterASTNode node, final String varName) {
final AtomicBoolean result = new AtomicBoolean(false);
new RecursiveLighterASTNodeWalkingVisitor(tree) {
@Override
public void visitNode(@NotNull LighterASTNode element) {
if (element.getTokenType() == LOCAL_VARIABLE && varName.equals(JavaLightTreeUtil.getNameIdentifierText(tree, element))) {
result.set(true);
}
super.visitNode(element);
}
}.visitNode(tree.getRoot());
return result.get();
}
}
@@ -16,10 +16,12 @@
package com.intellij.psi.impl.java.stubs;
import com.google.common.base.MoreObjects;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiType;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.io.IOUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.DataInput;
import java.io.DataOutput;
@@ -32,47 +34,26 @@ public class FunctionalExpressionKey {
public static final int UNKNOWN_PARAM_COUNT = -1;
private final int lambdaParameterCount;
private final CoarseType lambdaReturnType;
public final Location location;
@NotNull private final String knownType;
public FunctionalExpressionKey(int lambdaParameterCount, @NotNull CoarseType lambdaReturnType, @NotNull Location location) {
this.location = location;
public FunctionalExpressionKey(int lambdaParameterCount, @NotNull CoarseType lambdaReturnType, @Nullable String knownFunExprType) {
this.lambdaParameterCount = lambdaParameterCount;
this.lambdaReturnType = lambdaReturnType;
this.knownType = StringUtil.notNullize(knownFunExprType);
}
@NotNull
public static FunctionalExpressionKey deserializeKey(@NotNull DataInput dataStream) throws IOException {
int parameterCount = dataStream.readByte();
CoarseType type = CoarseType.values()[dataStream.readByte()];
return new FunctionalExpressionKey(parameterCount, type, deserializeLocation(dataStream));
String knownType = IOUtil.readUTF(dataStream);
return new FunctionalExpressionKey(parameterCount, type, knownType);
}
public void serializeKey(@NotNull DataOutput dataStream) throws IOException {
dataStream.writeByte(lambdaParameterCount);
dataStream.writeByte(lambdaReturnType.ordinal());
serializeLocation(dataStream);
}
private static Location deserializeLocation(DataInput dataStream) throws IOException {
byte locationType = dataStream.readByte();
if (locationType == 0) return Location.UNKNOWN;
if (locationType == 1) return CallLocation.deserializeCall(dataStream);
if (locationType == 2) return TypedLocation.deserializeField(dataStream);
throw new AssertionError(locationType);
}
private void serializeLocation(@NotNull DataOutput dataStream) throws IOException {
if (location == Location.UNKNOWN) {
dataStream.writeByte(0);
}
else if (location instanceof CallLocation) {
dataStream.writeByte(1);
((CallLocation)location).serializeCall(dataStream);
}
else if (location instanceof TypedLocation) {
dataStream.writeByte(2);
((TypedLocation)location).serializeVariable(dataStream);
}
IOUtil.writeUTF(dataStream, knownType);
}
public boolean canRepresent(int samParamCount, boolean booleanCompatible, boolean isVoid) {
@@ -99,7 +80,7 @@ public class FunctionalExpressionKey {
if (lambdaParameterCount != key.lambdaParameterCount) return false;
if (lambdaReturnType != key.lambdaReturnType) return false;
if (!location.equals(key.location)) return false;
if (!knownType.equals(key.knownType)) return false;
return true;
}
@@ -108,7 +89,7 @@ public class FunctionalExpressionKey {
public int hashCode() {
int result = lambdaParameterCount;
result = 31 * result + lambdaReturnType.ordinal();
result = 31 * result + location.hashCode();
result = 31 * result + knownType.hashCode();
return result;
}
@@ -117,130 +98,10 @@ public class FunctionalExpressionKey {
return MoreObjects.toStringHelper(this)
.add("lambdaParameterCount", lambdaParameterCount)
.add("type", lambdaReturnType)
.add("location", location)
.add("knownType", knownType)
.toString();
}
public interface Location {
Location UNKNOWN = new Location() {
@Override
public String toString() {
return "UNKNOWN";
}
@Override
public int hashCode() {
return 0;
}
};
}
public static class CallLocation implements Location {
public static final int MAX_ARG_COUNT = 10;
@NotNull public final String methodName;
public final int methodArgsLength;
public final int callArgIndex;
public final boolean streamApi;
public CallLocation(@NotNull String methodName, int methodArgsLength, int callArgIndex, boolean streamApi) {
this.methodName = methodName;
this.methodArgsLength = Math.min(methodArgsLength, MAX_ARG_COUNT);
this.callArgIndex = Math.min(callArgIndex, MAX_ARG_COUNT - 1);
this.streamApi = streamApi;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof CallLocation)) return false;
CallLocation location = (CallLocation)o;
if (methodArgsLength != location.methodArgsLength) return false;
if (callArgIndex != location.callArgIndex) return false;
if (!methodName.equals(location.methodName)) return false;
if (streamApi != location.streamApi) return false;
return true;
}
@Override
public int hashCode() {
int result = methodName.hashCode();
result = 31 * result + methodArgsLength;
result = 31 * result + callArgIndex;
result = 31 * result + (streamApi ? 0 : 1);
return result;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("methodName", methodName)
.add("methodArgsLength", methodArgsLength)
.add("callArgIndex", callArgIndex)
.add("streamApi", streamApi)
.toString();
}
@NotNull
private static Location deserializeCall(DataInput dataStream) throws IOException {
String methodName = IOUtil.readUTF(dataStream);
int methodArgsLength = dataStream.readByte();
int argIndex = dataStream.readByte();
boolean streamApi = dataStream.readBoolean();
return new CallLocation(methodName, methodArgsLength, argIndex, streamApi);
}
private void serializeCall(@NotNull DataOutput dataStream) throws IOException {
IOUtil.writeUTF(dataStream, methodName);
dataStream.writeByte(methodArgsLength);
dataStream.writeByte(callArgIndex);
dataStream.writeBoolean(streamApi);
}
}
public static class TypedLocation implements Location {
@NotNull public final String varType;
public TypedLocation(@NotNull String varType) {
this.varType = varType;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TypedLocation)) return false;
TypedLocation location = (TypedLocation)o;
if (!varType.equals(location.varType)) return false;
return true;
}
@Override
public int hashCode() {
return varType.hashCode();
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("fieldType", varType)
.toString();
}
public static TypedLocation deserializeField(DataInput dataStream) throws IOException {
return new TypedLocation(IOUtil.readUTF(dataStream));
}
public void serializeVariable(DataOutput dataStream) throws IOException {
IOUtil.writeUTF(dataStream, varType);
}
}
public enum CoarseType {
VOID, UNKNOWN, BOOLEAN, NON_VOID
}