Browse Source
All overloaded methods are now available in SPeL expressions. Among methods with identical argument list from different sources in the same extension (extension, root object, aliases) the last one in the order in parens wins. If there is more than one method for an application the following rules are applied: if there is one method with exact matching types in the argument list it is used, otherwise an exception is thrown. Original pull request: #217.pull/218/head
8 changed files with 441 additions and 31 deletions
@ -0,0 +1,133 @@
@@ -0,0 +1,133 @@
|
||||
/* |
||||
* Copyright 2017 the original author or authors. |
||||
* |
||||
* 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.springframework.data.repository.query; |
||||
|
||||
import lombok.AllArgsConstructor; |
||||
import lombok.Value; |
||||
|
||||
import java.lang.reflect.Method; |
||||
import java.util.Collections; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
import java.util.stream.Collectors; |
||||
import java.util.stream.Stream; |
||||
|
||||
import org.springframework.core.convert.TypeDescriptor; |
||||
import org.springframework.data.repository.query.spi.Function; |
||||
import org.springframework.util.CollectionUtils; |
||||
import org.springframework.util.MultiValueMap; |
||||
|
||||
/** |
||||
* {@link MultiValueMap} like datastructure to keep lists of |
||||
* {@link org.springframework.data.repository.query.spi.Function}s indexed by name and argument list length, where the |
||||
* value lists are actually unique with respect to the signature. |
||||
* |
||||
* @author Jens Schauder |
||||
* @since 2.0 |
||||
*/ |
||||
class Functions { |
||||
|
||||
private final MultiValueMap<NameAndArgumentCount, Function> functions = CollectionUtils |
||||
.toMultiValueMap(new HashMap<>()); |
||||
|
||||
void addAll(Map<String, Function> newFunctions) { |
||||
|
||||
newFunctions.forEach((n, f) -> { |
||||
NameAndArgumentCount k = new NameAndArgumentCount(n, f.getParameterCount()); |
||||
List<Function> currentElements = get(k); |
||||
if (!contains(currentElements, f)) { |
||||
functions.add(k, f); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
void addAll(MultiValueMap<NameAndArgumentCount, Function> newFunctions) { |
||||
|
||||
newFunctions.forEach((k, list) -> { |
||||
List<Function> currentElements = get(k); |
||||
list.stream() //
|
||||
.filter(f -> !contains(currentElements, f)) //
|
||||
.forEach(f -> functions.add(k, f)); |
||||
}); |
||||
} |
||||
|
||||
List<Function> get(NameAndArgumentCount key) { |
||||
return functions.getOrDefault(key, Collections.emptyList()); |
||||
} |
||||
|
||||
/** |
||||
* Gets the function that best matches the parameters given. The {@code name} must match, and the |
||||
* {@code argumentTypes} must be compatible with parameter list of the function. In order to resolve ambiguity it |
||||
* checks for a method with exactly matching parameter list. |
||||
* |
||||
* @param name the name of the method |
||||
* @param argumentTypes types of arguments that the method must be able to accept |
||||
* @return a {@code Function} if a unique on gets found. {@code Optional.empty} if none matches. Throws |
||||
* {@link IllegalStateException} if multiple functions match the parameters. |
||||
*/ |
||||
Optional<Function> get(String name, List<TypeDescriptor> argumentTypes) { |
||||
|
||||
Stream<Function> candidates = get(new NameAndArgumentCount(name, argumentTypes.size())).stream() //
|
||||
.filter(f -> f.supports(argumentTypes)); |
||||
return bestMatch(candidates.collect(Collectors.toList()), argumentTypes); |
||||
} |
||||
|
||||
private static boolean contains(List<Function> elements, Function f) { |
||||
return elements.stream().anyMatch(f::isSignatureEqual); |
||||
} |
||||
|
||||
private static Optional<Function> bestMatch(List<Function> candidates, List<TypeDescriptor> argumentTypes) { |
||||
|
||||
if (candidates.isEmpty()) { |
||||
return Optional.empty(); |
||||
} |
||||
if (candidates.size() == 1) { |
||||
return Optional.of(candidates.get(0)); |
||||
} |
||||
|
||||
Optional<Function> exactMatch = candidates.stream().filter(f -> f.supportsExact(argumentTypes)).findFirst(); |
||||
if (!exactMatch.isPresent()) { |
||||
throw new IllegalStateException(createErrorMessage(candidates, argumentTypes)); |
||||
} |
||||
|
||||
return exactMatch; |
||||
} |
||||
|
||||
private static String createErrorMessage(List<Function> candidates, List<TypeDescriptor> argumentTypes) { |
||||
|
||||
String argumentTypeString = String.join( //
|
||||
",", //
|
||||
argumentTypes.stream().map(TypeDescriptor::getName).collect(Collectors.toList())); |
||||
|
||||
String messageTemplate = "There are multiple matching methods of name '%s' for parameter types (%s), but no " |
||||
+ "exact match. Make sure to provide only one matching overload or one with exactly those types."; |
||||
|
||||
return String.format(messageTemplate, candidates.get(0).getName(), argumentTypeString); |
||||
} |
||||
|
||||
@Value |
||||
@AllArgsConstructor |
||||
static class NameAndArgumentCount { |
||||
String name; |
||||
int count; |
||||
|
||||
static NameAndArgumentCount of(Method m) { |
||||
return new NameAndArgumentCount(m.getName(), m.getParameterCount()); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,97 @@
@@ -0,0 +1,97 @@
|
||||
/* |
||||
* Copyright 2017 the original author or authors. |
||||
* |
||||
* 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.springframework.data.util; |
||||
|
||||
import lombok.NonNull; |
||||
import lombok.RequiredArgsConstructor; |
||||
|
||||
import java.util.EnumSet; |
||||
import java.util.HashMap; |
||||
import java.util.Set; |
||||
import java.util.function.BiConsumer; |
||||
import java.util.function.BinaryOperator; |
||||
import java.util.function.Function; |
||||
import java.util.function.Supplier; |
||||
import java.util.stream.Collector; |
||||
|
||||
import org.springframework.util.CollectionUtils; |
||||
import org.springframework.util.MultiValueMap; |
||||
|
||||
/** |
||||
* A {@link Collector} for building a {@link MultiValueMap} from a {@link java.util.stream.Stream}. |
||||
* |
||||
* @author Jens Schauder |
||||
* @since 2.0 |
||||
*/ |
||||
@RequiredArgsConstructor |
||||
public class MultiValueMapCollector<T, K, V> implements Collector<T, MultiValueMap<K, V>, MultiValueMap<K, V>> { |
||||
|
||||
@NonNull private final Function<T, K> keyFunction; |
||||
@NonNull private final Function<T, V> valueFunction; |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see java.util.stream.Collector#supplier() |
||||
*/ |
||||
@Override |
||||
public Supplier<MultiValueMap<K, V>> supplier() { |
||||
return () -> CollectionUtils.toMultiValueMap(new HashMap<>()); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see java.util.stream.Collector#accumulator() |
||||
*/ |
||||
@Override |
||||
public BiConsumer<MultiValueMap<K, V>, T> accumulator() { |
||||
return (map, t) -> map.add(keyFunction.apply(t), valueFunction.apply(t)); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see java.util.stream.Collector#combiner() |
||||
*/ |
||||
@Override |
||||
public BinaryOperator<MultiValueMap<K, V>> combiner() { |
||||
|
||||
return (map1, map2) -> { |
||||
|
||||
for (K key : map2.keySet()) { |
||||
map1.addAll(key, map2.get(key)); |
||||
} |
||||
|
||||
return map1; |
||||
}; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see java.util.stream.Collector#finisher() |
||||
*/ |
||||
@Override |
||||
public Function<MultiValueMap<K, V>, MultiValueMap<K, V>> finisher() { |
||||
return Function.identity(); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see java.util.stream.Collector#characteristics() |
||||
*/ |
||||
@Override |
||||
public Set<Characteristics> characteristics() { |
||||
return EnumSet.of(Characteristics.IDENTITY_FINISH, Characteristics.UNORDERED); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue