FunctionUtils polishing

- attempted to make code more functional (eat our own dog food) and concise
- addressed PR comment
- additional cleanup/polishing of FunctionUtils and related classes
- Removed Function/Supplier/ConsumerProxy classes by extending type info on their super interface
- Renamed FunctionUtils to FunctionFactoryUtils
- Added javadoc to FunctionFactoryUtils to explain its design considerations as well as what it can and can not doi

Fixes gh-90
This commit is contained in:
Oleg Zhurakousky
2017-07-14 16:36:50 -05:00
committed by Dave Syer
parent 2644ab3178
commit a973b678f1
21 changed files with 191 additions and 322 deletions

View File

@@ -1,34 +0,0 @@
/*
* 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.cloud.function.support;
import java.util.function.Consumer;
/**
* @author Mark Fisher
*
* @param <T> output type of target Consumer
*/
public interface ConsumerProxy<T> extends Consumer<T>, FunctionFactoryMetadata {
default boolean isFluxConsumer() {
return FunctionUtils.isFluxConsumer(getFactoryMethod());
}
Consumer<T> getTarget();
}

View File

@@ -23,8 +23,10 @@ import java.lang.reflect.Method;
*
* @param <T>
*/
public interface FunctionFactoryMetadata {
public interface FunctionFactoryMetadata<F> {
Method getFactoryMethod();
F getTarget();
}

View File

@@ -0,0 +1,135 @@
/*
* 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.cloud.function.support;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.context.annotation.Bean;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import reactor.core.publisher.Flux;
/**
* <p>
* Miscellaneous utility operations to interrogate functional
* components (beans) configured in BeanFactory.
* </p>
* <p>
* It is important to understand that it is not a general purpose utility
* to interrogate "any" functional component. Certain operations may/will
* not work as expected due to java type erasure. While BeanFactory is not
* the requirement, this utility is targeting only the components
* defined in such way where they could be configured as
* @{@link Component} or @{@link Bean} within BeanFactory.
* </p>
* It is primarily used internally by the framework.
*
* @author Mark Fisher
* @author Oleg Zhurakousky
*/
public abstract class FunctionFactoryUtils {
private static final String FLUX_CLASS_NAME = Flux.class.getName();
private FunctionFactoryUtils() {
}
public static boolean isFluxConsumer(Consumer<?> consumer) {
return consumer instanceof FunctionFactoryMetadata
? isFluxConsumer(((FunctionFactoryMetadata<?>) consumer).getFactoryMethod())
: isFlux(1, getParameterizedTypeNames(consumer, Consumer.class));
}
public static boolean isFluxConsumer(Method method) {
return isFlux(1, getParameterizedTypeNamesForMethod(method, Consumer.class));
}
public static boolean isFluxSupplier(Supplier<?> supplier) {
return supplier instanceof FunctionFactoryMetadata
? isFluxSupplier(((FunctionFactoryMetadata<?>) supplier).getFactoryMethod())
: isFlux(1, getParameterizedTypeNames(supplier, Supplier.class));
}
public static boolean isFluxSupplier(Method method) {
return isFlux(1, getParameterizedTypeNamesForMethod(method, Supplier.class));
}
public static boolean isFluxFunction(Function<?, ?> function) {
return function instanceof FunctionFactoryMetadata
? isFluxFunction(((FunctionFactoryMetadata<?>) function).getFactoryMethod())
: isFlux(1, getParameterizedTypeNames(function, Function.class));
}
public static boolean isFluxFunction(Method method) {
return isFlux(2, getParameterizedTypeNamesForMethod(method, Function.class));
}
private static String[] getParameterizedTypeNamesForMethod(Method method, Class<?> interfaceClass) {
String[] types = retrieveTypes(method.getGenericReturnType(), interfaceClass);
return types == null ? new String[0] : types;
}
private static String[] getParameterizedTypeNames(Object source, Class<?> interfaceClass) {
return Stream.of(source.getClass().getGenericInterfaces())
.map(gi -> retrieveTypes(gi, interfaceClass))
.filter(s -> s != null)
.findFirst()
.orElse(getSerializedLambdaParameterizedTypeNames(source));
}
private static String[] retrieveTypes(Type genericInterface, Class<?> interfaceClass){
if ((genericInterface instanceof ParameterizedType) && interfaceClass
.getTypeName().equals(((ParameterizedType) genericInterface).getRawType().getTypeName())) {
ParameterizedType type = (ParameterizedType) genericInterface;
Type[] args = type.getActualTypeArguments();
if (args != null) {
return Stream.of(args).map(arg -> arg.getTypeName()).toArray(String[]::new);
}
}
return null;
}
private static String[] getSerializedLambdaParameterizedTypeNames(Object source) {
Method method = ReflectionUtils.findMethod(source.getClass(), "writeReplace");
if (method == null) {
return null;
}
ReflectionUtils.makeAccessible(method);
SerializedLambda serializedLambda = (SerializedLambda) ReflectionUtils
.invokeMethod(method, source);
String signature = serializedLambda.getImplMethodSignature().replaceAll("[()]","");
List<String> typeNames = Stream.of(signature.split(";"))
.map(t -> t.substring(1).replace('/', '.')).collect(Collectors.toList());
return typeNames.toArray(new String[typeNames.size()]);
}
private static boolean isFlux(int length, String... types){
return !ObjectUtils.isEmpty(types) && types.length == length && Stream.of(types).allMatch(type -> type.startsWith(FLUX_CLASS_NAME));
}
}

View File

@@ -1,35 +0,0 @@
/*
* 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.cloud.function.support;
import java.util.function.Function;
/**
* @author Mark Fisher
*
* @param <T> input type of target Function
* @param <R> output type of target Function
*/
public interface FunctionProxy<T, R> extends Function<T, R>, FunctionFactoryMetadata {
default boolean isFluxFunction() {
return FunctionUtils.isFluxFunction(getFactoryMethod());
}
Function<T, R> getTarget();
}

View File

@@ -1,168 +0,0 @@
/*
* 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.cloud.function.support;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import reactor.core.publisher.Flux;
/**
* @author Mark Fisher
*/
public abstract class FunctionUtils {
private static final String FLUX_CLASS_NAME = Flux.class.getName();
private FunctionUtils() {
}
@SuppressWarnings("rawtypes")
public static boolean isFluxConsumer(Consumer<?> consumer) {
if (consumer instanceof ConsumerProxy) {
return ((ConsumerProxy) consumer).isFluxConsumer();
}
String[] types = getParameterizedTypeNames(consumer, Consumer.class);
if (ObjectUtils.isEmpty(types)) {
return false;
}
return (types[0].startsWith(FLUX_CLASS_NAME));
}
@SuppressWarnings("rawtypes")
public static boolean isFluxSupplier(Supplier<?> supplier) {
if (supplier instanceof SupplierProxy) {
return ((SupplierProxy) supplier).isFluxSupplier();
}
String[] types = getParameterizedTypeNames(supplier, Supplier.class);
if (ObjectUtils.isEmpty(types)) {
// Assume if there is no generic information then the function is not
// expecting a flux. N.B. this isn't very accurate. It is better to use a
// FunctionInspector if one is available.
return false;
}
return (types[0].startsWith(FLUX_CLASS_NAME));
}
@SuppressWarnings("rawtypes")
public static boolean isFluxFunction(Function<?, ?> function) {
if (function instanceof FunctionProxy) {
return ((FunctionProxy) function).isFluxFunction();
}
String[] types = getParameterizedTypeNames(function, Function.class);
if (ObjectUtils.isEmpty(types) || types.length != 2) {
return false;
}
return (types[0].startsWith(FLUX_CLASS_NAME)
&& types[1].startsWith(FLUX_CLASS_NAME));
}
private static String[] getParameterizedTypeNames(Object source,
Class<?> interfaceClass) {
Type[] genericInterfaces = source.getClass().getGenericInterfaces();
for (Type genericInterface : genericInterfaces) {
if ((genericInterface instanceof ParameterizedType) && interfaceClass
.getTypeName().equals(((ParameterizedType) genericInterface)
.getRawType().getTypeName())) {
ParameterizedType type = (ParameterizedType) genericInterface;
Type[] args = type.getActualTypeArguments();
if (args != null) {
String[] typeNames = new String[args.length];
for (int i = 0; i < args.length; i++) {
typeNames[i] = args[i].getTypeName();
}
return typeNames;
}
}
}
return getSerializedLambdaParameterizedTypeNames(source);
}
private static String[] getSerializedLambdaParameterizedTypeNames(Object source) {
Method method = ReflectionUtils.findMethod(source.getClass(), "writeReplace");
if (method == null) {
return null;
}
ReflectionUtils.makeAccessible(method);
SerializedLambda serializedLambda = (SerializedLambda) ReflectionUtils
.invokeMethod(method, source);
String signature = serializedLambda.getImplMethodSignature().replaceAll("[()]",
"");
List<String> typeNames = new ArrayList<>();
for (String types : signature.split(";")) {
typeNames.add(types.substring(1).replace('/', '.'));
}
return typeNames.toArray(new String[typeNames.size()]);
}
public static boolean isFluxSupplier(Method method) {
String[] types = getParameterizedTypeNamesForMethod(method, Supplier.class);
if (ObjectUtils.isEmpty(types)) {
return false;
}
return (types[0].startsWith(FLUX_CLASS_NAME));
}
public static boolean isFluxConsumer(Method method) {
String[] types = getParameterizedTypeNamesForMethod(method, Consumer.class);
if (ObjectUtils.isEmpty(types)) {
return false;
}
return (types[0].startsWith(FLUX_CLASS_NAME));
}
public static boolean isFluxFunction(Method method) {
String[] types = getParameterizedTypeNamesForMethod(method, Function.class);
if (ObjectUtils.isEmpty(types)) {
return false;
}
if (ObjectUtils.isEmpty(types) || types.length != 2) {
return false;
}
return (types[0].startsWith(FLUX_CLASS_NAME)
&& types[1].startsWith(FLUX_CLASS_NAME));
}
private static String[] getParameterizedTypeNamesForMethod(Method method,
Class<?> interfaceClass) {
Type genericInterface = method.getGenericReturnType();
if ((genericInterface instanceof ParameterizedType) && interfaceClass
.getTypeName().equals(((ParameterizedType) genericInterface)
.getRawType().getTypeName())) {
ParameterizedType type = (ParameterizedType) genericInterface;
Type[] args = type.getActualTypeArguments();
if (args != null) {
String[] typeNames = new String[args.length];
for (int i = 0; i < args.length; i++) {
typeNames[i] = args[i].getTypeName();
}
return typeNames;
}
}
return new String[0];
}
}

View File

@@ -1,33 +0,0 @@
/*
* 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.cloud.function.support;
import java.util.function.Supplier;
/**
* @author Mark Fisher
*
* @param <T> output type of target Supplier
*/
public interface SupplierProxy<T> extends Supplier<T>, FunctionFactoryMetadata {
default boolean isFluxSupplier() {
return FunctionUtils.isFluxSupplier(getFactoryMethod());
}
Supplier<T> getTarget();
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.function.gateway;
package org.springframework.cloud.function.support;
import java.lang.reflect.Method;
import java.util.function.Consumer;
@@ -23,7 +23,7 @@ import java.util.function.Supplier;
import org.junit.Test;
import org.springframework.cloud.function.support.FunctionUtils;
import org.springframework.cloud.function.support.FunctionFactoryUtils;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -34,30 +34,30 @@ import reactor.core.publisher.Flux;
* @author Dave Syer
*
*/
public class FunctionUtilsTests {
public class FunctionFactoryUtilsTests {
@Test
public void isFluxConsumer() {
Method method = ReflectionUtils.findMethod(FunctionUtilsTests.class, "fluxConsumer");
assertThat(FunctionUtils.isFluxConsumer(method)).isTrue();
assertThat(FunctionUtils.isFluxSupplier(method)).isFalse();
assertThat(FunctionUtils.isFluxFunction(method)).isFalse();
Method method = ReflectionUtils.findMethod(FunctionFactoryUtilsTests.class, "fluxConsumer");
assertThat(FunctionFactoryUtils.isFluxConsumer(method)).isTrue();
assertThat(FunctionFactoryUtils.isFluxSupplier(method)).isFalse();
assertThat(FunctionFactoryUtils.isFluxFunction(method)).isFalse();
}
@Test
public void isFluxSupplier() {
Method method = ReflectionUtils.findMethod(FunctionUtilsTests.class, "fluxSupplier");
assertThat(FunctionUtils.isFluxSupplier(method)).isTrue();
assertThat(FunctionUtils.isFluxConsumer(method)).isFalse();
assertThat(FunctionUtils.isFluxFunction(method)).isFalse();
Method method = ReflectionUtils.findMethod(FunctionFactoryUtilsTests.class, "fluxSupplier");
assertThat(FunctionFactoryUtils.isFluxSupplier(method)).isTrue();
assertThat(FunctionFactoryUtils.isFluxConsumer(method)).isFalse();
assertThat(FunctionFactoryUtils.isFluxFunction(method)).isFalse();
}
@Test
public void isFluxFunction() {
Method method = ReflectionUtils.findMethod(FunctionUtilsTests.class, "fluxFunction");
assertThat(FunctionUtils.isFluxFunction(method)).isTrue();
assertThat(FunctionUtils.isFluxSupplier(method)).isFalse();
assertThat(FunctionUtils.isFluxConsumer(method)).isFalse();
Method method = ReflectionUtils.findMethod(FunctionFactoryUtilsTests.class, "fluxFunction");
assertThat(FunctionFactoryUtils.isFluxFunction(method)).isTrue();
assertThat(FunctionFactoryUtils.isFluxSupplier(method)).isFalse();
assertThat(FunctionFactoryUtils.isFluxConsumer(method)).isFalse();
}
public Function<Flux<Foo>, Flux<Foo>> fluxFunction() {