mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-08-20 20:45:15 +07:00
CallMatcher/CallMapper introduced experimentally; used in SimplifyStreamApiCallChainsInspection.java
This commit is contained in:
+470
-574
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -4,6 +4,6 @@ import java.util.Arrays;
|
||||
|
||||
class Test {
|
||||
void print() {
|
||||
Arrays.asList('d', 'e', 'f').st<caret>ream().forEach(c -> System.out.print(" " + c));
|
||||
Arrays.asList('d', 'e', 'f').stream().fo<caret>rEach(c -> System.out.print(" " + c));
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -4,6 +4,6 @@ import java.util.Arrays;
|
||||
|
||||
class Test {
|
||||
void print() {
|
||||
Arrays.asList('d', 'e', 'f').str<caret>eam().forEachOrdered(c -> System.out.print(" " + c));
|
||||
Arrays.asList('d', 'e', 'f').stream().forEac<caret>hOrdered(c -> System.out.print(" " + c));
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.siyeh.ig.callMatcher;
|
||||
|
||||
import com.intellij.psi.PsiMethodCallExpression;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* A pair of {@link CallMatcher} and a transformer function which maps a call to some new object.
|
||||
*
|
||||
* @author Tagir Valeev
|
||||
*/
|
||||
public class CallHandler<T> implements Function<PsiMethodCallExpression, T> {
|
||||
private final CallMatcher myMatcher;
|
||||
private final Function<PsiMethodCallExpression, T> myTransformer;
|
||||
|
||||
public CallHandler(CallMatcher matcher, Function<PsiMethodCallExpression, T> transformer) {
|
||||
myMatcher = matcher;
|
||||
myTransformer = transformer;
|
||||
}
|
||||
|
||||
public final CallMatcher matcher() {
|
||||
return myMatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param call method call to transform
|
||||
* @return null if call does not pass matcher check or the result of original transformer otherwise
|
||||
*/
|
||||
@Override
|
||||
public T apply(PsiMethodCallExpression call) {
|
||||
return matcher().test(call) ? myTransformer.apply(call) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CallHandler with specific matcher and specific transformer function
|
||||
* @param matcher a matcher to be applied to the elements
|
||||
* @param transformer a transformer which accepts a method call which successfully passes matcher check
|
||||
* @param <T> a type of transformer return value
|
||||
* @return a new CallHandler
|
||||
*/
|
||||
public static <T> CallHandler<T> of(CallMatcher matcher, Function<PsiMethodCallExpression, T> transformer) {
|
||||
return new CallHandler<>(matcher, transformer);
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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.siyeh.ig.callMatcher;
|
||||
|
||||
import com.intellij.psi.PsiMethodCallExpression;
|
||||
import one.util.streamex.StreamEx;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A mutable bunch of CallHandlers which allows to dispatch a transformer call based on CallMatcher
|
||||
*
|
||||
* @author Tagir Valeev
|
||||
*/
|
||||
public class CallMapper<T> {
|
||||
private Map<String, List<Function<PsiMethodCallExpression, T>>> myMap = new HashMap<>();
|
||||
|
||||
public CallMapper() {}
|
||||
|
||||
public CallMapper(CallHandler<T>... handlers) {
|
||||
for (CallHandler<T> handler : handlers) {
|
||||
register(handler);
|
||||
}
|
||||
}
|
||||
|
||||
public CallMapper<T> register(CallHandler<T> handler) {
|
||||
handler.matcher().names().forEach(name -> myMap.computeIfAbsent(name, k -> new ArrayList<>()).add(handler));
|
||||
return this;
|
||||
}
|
||||
|
||||
public CallMapper<T> register(CallMatcher matcher, Function<PsiMethodCallExpression, T> handler) {
|
||||
return register(CallHandler.of(matcher, handler));
|
||||
}
|
||||
|
||||
public CallMapper<T> register(CallMatcher matcher, T value) {
|
||||
return register(CallHandler.of(matcher, call -> value));
|
||||
}
|
||||
|
||||
public CallMapper<T> registerAll(List<CallHandler<T>> handlers) {
|
||||
handlers.forEach(this::register);
|
||||
return this;
|
||||
}
|
||||
|
||||
public T mapFirst(PsiMethodCallExpression call) {
|
||||
if (call == null) return null;
|
||||
List<Function<PsiMethodCallExpression, T>> functions = myMap.get(call.getMethodExpression().getReferenceName());
|
||||
if (functions == null) return null;
|
||||
for (Function<PsiMethodCallExpression, T> function : functions) {
|
||||
T t = function.apply(call);
|
||||
if (t != null) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Stream<T> mapAll(PsiMethodCallExpression call) {
|
||||
if (call == null) return null;
|
||||
List<Function<PsiMethodCallExpression, T>> functions = myMap.get(call.getMethodExpression().getReferenceName());
|
||||
if (functions == null) return StreamEx.empty();
|
||||
return StreamEx.of(functions).map(fn -> fn.apply(call)).nonNull();
|
||||
}
|
||||
}
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* 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.siyeh.ig.callMatcher;
|
||||
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.siyeh.ig.psiutils.MethodCallUtils;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* This interface represents a condition upon method call
|
||||
*
|
||||
* @author Tagir Valeev
|
||||
*/
|
||||
public interface CallMatcher extends Predicate<PsiMethodCallExpression> {
|
||||
/**
|
||||
* @return names of the methods for which this matcher may return true. For any other method it guaranteed to return false
|
||||
*/
|
||||
Stream<String> names();
|
||||
|
||||
@Contract("null -> false")
|
||||
boolean test(@Nullable PsiMethodCallExpression call);
|
||||
|
||||
/**
|
||||
* Returns a new matcher which will return true if any of supplied matchers return true
|
||||
*
|
||||
* @param matchers
|
||||
* @return a new matcher
|
||||
*/
|
||||
static CallMatcher anyOf(CallMatcher... matchers) {
|
||||
return new CallMatcher() {
|
||||
@Override
|
||||
public Stream<String> names() {
|
||||
return Stream.of(matchers).flatMap(CallMatcher::names);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(PsiMethodCallExpression call) {
|
||||
for (CallMatcher m : matchers) {
|
||||
if (m.test(call)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Stream.of(matchers).map(CallMatcher::toString).collect(Collectors.joining(" or ", "{", "}"));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a matcher which matches an instance method having one of supplied names which class (or any of superclasses) is className
|
||||
*
|
||||
* @param className fully-qualified class name
|
||||
* @param methodNames names of the methods
|
||||
* @return a new matcher
|
||||
*/
|
||||
static Simple instanceCall(@NotNull String className, String... methodNames) {
|
||||
return new Simple(className, ContainerUtil.newTroveSet(methodNames), null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a matcher which matches a static method having one of supplied names which class is className
|
||||
*
|
||||
* @param className fully-qualified class name
|
||||
* @param methodNames names of the methods
|
||||
* @return a new matcher
|
||||
*/
|
||||
static Simple staticCall(@NotNull String className, String... methodNames) {
|
||||
return new Simple(className, ContainerUtil.newTroveSet(methodNames), null, true);
|
||||
}
|
||||
|
||||
class Simple implements CallMatcher {
|
||||
private final @NotNull String myClassName;
|
||||
private final @NotNull Set<String> myNames;
|
||||
private final @Nullable String[] myParameters;
|
||||
private final boolean myStatic;
|
||||
|
||||
private Simple(@NotNull String className, @NotNull Set<String> names, @Nullable String[] parameters, boolean aStatic) {
|
||||
myClassName = className;
|
||||
myNames = names;
|
||||
myParameters = parameters;
|
||||
myStatic = aStatic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> names() {
|
||||
return myNames.stream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new matcher which in addition to current matcher checks the number of parameters of the called method
|
||||
*
|
||||
* @param count expected number of parameters
|
||||
* @return a new matcher
|
||||
* @throws IllegalStateException if this matcher is already limited to parameters count or types
|
||||
*/
|
||||
public Simple parameterCount(int count) {
|
||||
if (myParameters != null) {
|
||||
throw new IllegalStateException("Parameter count is already set to " + count);
|
||||
}
|
||||
return new Simple(myClassName, myNames, count == 0 ? ArrayUtil.EMPTY_STRING_ARRAY : new String[count], myStatic);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new matcher which in addition to current matcher checks the number of parameters of the called method
|
||||
* and their types
|
||||
*
|
||||
* @param types textual representation of parameter types (may contain null to ignore checking parameter type of specific argument)
|
||||
* @return a new matcher
|
||||
* @throws IllegalStateException if this matcher is already limited to parameters count or types
|
||||
*/
|
||||
public Simple parameterTypes(@NotNull String... types) {
|
||||
if (myParameters != null) {
|
||||
throw new IllegalStateException("Parameters are already registered");
|
||||
}
|
||||
return new Simple(myClassName, myNames, types.length == 0 ? ArrayUtil.EMPTY_STRING_ARRAY : types.clone(), myStatic);
|
||||
}
|
||||
|
||||
private static boolean parameterTypeMatches(String type, PsiParameter parameter) {
|
||||
if (type == null) return true;
|
||||
PsiType psiType = parameter.getType();
|
||||
return psiType.equalsToText(type) ||
|
||||
psiType instanceof PsiClassType && ((PsiClassType)psiType).rawType().equalsToText(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(PsiMethodCallExpression call) {
|
||||
String name = call.getMethodExpression().getReferenceName();
|
||||
if (!myNames.contains(name)) return false;
|
||||
PsiExpression[] args = call.getArgumentList().getExpressions();
|
||||
if (myParameters != null && myParameters.length > 0) {
|
||||
if (args.length < myParameters.length) return false;
|
||||
}
|
||||
PsiMethod method = call.resolveMethod();
|
||||
if (method == null) return false;
|
||||
PsiClass aClass = method.getContainingClass();
|
||||
if (aClass == null) return false;
|
||||
if (myStatic != method.getModifierList().hasExplicitModifier(PsiModifier.STATIC) ||
|
||||
(myStatic && !myClassName.equals(aClass.getQualifiedName())) ||
|
||||
(!myStatic && !InheritanceUtil.isInheritor(aClass, myClassName))) {
|
||||
return false;
|
||||
}
|
||||
PsiParameterList parameterList = method.getParameterList();
|
||||
if (parameterList.getParametersCount() > args.length ||
|
||||
(!MethodCallUtils.isVarArgCall(call) && parameterList.getParametersCount() < args.length)) {
|
||||
return false;
|
||||
}
|
||||
if (myParameters != null) {
|
||||
if (myParameters.length != parameterList.getParametersCount()) return false;
|
||||
return StreamEx.zip(myParameters, parameterList.getParameters(),
|
||||
Simple::parameterTypeMatches).allMatch(Boolean.TRUE::equals);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return myClassName + "." + String.join("|", myNames);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user