/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.brooklyn.util.core.flags; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.reflect.TypeToken; import javax.annotation.Nullable; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.Arrays; import java.util.List; import static com.google.common.base.Preconditions.checkNotNull; /** * A way of binding a loosely-specified method call into a strongly-typed Java method call. */ public class MethodCoercions { /** * Returns a predicate that matches a method with the given name, and a single parameter that * {@link org.apache.brooklyn.util.core.flags.TypeCoercions#tryCoerce(Object, com.google.common.reflect.TypeToken)} can process * from the given argument. * * @param methodName name of the method * @param argument argument that is intended to be given * @return a predicate that will match a compatible method */ public static Predicate<Method> matchSingleParameterMethod(final String methodName, final Object argument) { checkNotNull(methodName, "methodName"); checkNotNull(argument, "argument"); return new Predicate<Method>() { @Override public boolean apply(@Nullable Method input) { if (input == null) return false; if (!input.getName().equals(methodName)) return false; Type[] parameterTypes = input.getGenericParameterTypes(); return parameterTypes.length == 1 && TypeCoercions.tryCoerce(argument, TypeToken.of(parameterTypes[0])).isPresentAndNonNull(); } }; } /** * Tries to find a single-parameter method with a parameter compatible with (can be coerced to) the argument, and * invokes it. * * @param instance the object to invoke the method on * @param methodName the name of the method to invoke * @param argument the argument to the method's parameter. * @return the result of the method call, or {@link org.apache.brooklyn.util.guava.Maybe#absent()} if method could not be matched. */ public static Maybe<?> tryFindAndInvokeSingleParameterMethod(final Object instance, final String methodName, final Object argument) { Class<?> clazz = instance.getClass(); Iterable<Method> methods = Arrays.asList(clazz.getMethods()); Optional<Method> matchingMethod = Iterables.tryFind(methods, matchSingleParameterMethod(methodName, argument)); if (matchingMethod.isPresent()) { Method method = matchingMethod.get(); try { Type paramType = method.getGenericParameterTypes()[0]; Object coercedArgument = TypeCoercions.coerce(argument, TypeToken.of(paramType)); return Maybe.of(method.invoke(instance, coercedArgument)); } catch (IllegalAccessException | InvocationTargetException e) { throw Exceptions.propagate(e); } } else { return Maybe.absent(); } } /** * Returns a predicate that matches a method with the given name, and parameters that * {@link org.apache.brooklyn.util.core.flags.TypeCoercions#tryCoerce(Object, com.google.common.reflect.TypeToken)} can process * from the given list of arguments. * * @param methodName name of the method * @param arguments arguments that is intended to be given * @return a predicate that will match a compatible method */ public static Predicate<Method> matchMultiParameterMethod(final String methodName, final List<?> arguments) { checkNotNull(methodName, "methodName"); checkNotNull(arguments, "arguments"); return new Predicate<Method>() { @Override public boolean apply(@Nullable Method input) { if (input == null) return false; if (!input.getName().equals(methodName)) return false; int numOptionParams = arguments.size(); Type[] parameterTypes = input.getGenericParameterTypes(); if (parameterTypes.length != numOptionParams) return false; for (int paramCount = 0; paramCount < numOptionParams; paramCount++) { if (!TypeCoercions.tryCoerce(((List<?>) arguments).get(paramCount), TypeToken.of(parameterTypes[paramCount])).isPresentAndNonNull()) return false; } return true; } }; } /** * Tries to find a multiple-parameter method with each parameter compatible with (can be coerced to) the * corresponding argument, and invokes it. * * @param instance the object to invoke the method on * @param methodName the name of the method to invoke * @param argument a list of the arguments to the method's parameters. * @return the result of the method call, or {@link org.apache.brooklyn.util.guava.Maybe#absent()} if method could not be matched. */ public static Maybe<?> tryFindAndInvokeMultiParameterMethod(final Object instance, final String methodName, final List<?> arguments) { Class<?> clazz = instance.getClass(); Iterable<Method> methods = Arrays.asList(clazz.getMethods()); Optional<Method> matchingMethod = Iterables.tryFind(methods, matchMultiParameterMethod(methodName, arguments)); if (matchingMethod.isPresent()) { Method method = matchingMethod.get(); try { int numOptionParams = ((List<?>)arguments).size(); Object[] coercedArguments = new Object[numOptionParams]; for (int paramCount = 0; paramCount < numOptionParams; paramCount++) { Object argument = arguments.get(paramCount); Type paramType = method.getGenericParameterTypes()[paramCount]; coercedArguments[paramCount] = TypeCoercions.coerce(argument, TypeToken.of(paramType)); } return Maybe.of(method.invoke(instance, coercedArguments)); } catch (IllegalAccessException | InvocationTargetException e) { throw Exceptions.propagate(e); } } else { return Maybe.absent(); } } /** * Tries to find a method with each parameter compatible with (can be coerced to) the corresponding argument, and invokes it. * * @param instance the object to invoke the method on * @param methodName the name of the method to invoke * @param argument a list of the arguments to the method's parameters, or a single argument for a single-parameter method. * @return the result of the method call, or {@link org.apache.brooklyn.util.guava.Maybe#absent()} if method could not be matched. */ public static Maybe<?> tryFindAndInvokeBestMatchingMethod(final Object instance, final String methodName, final Object argument) { if (argument instanceof List) { List<?> arguments = (List<?>) argument; // ambiguous case: we can't tell if the user is using the multi-parameter syntax, or the single-parameter // syntax for a method which takes a List parameter. So we try one, then fall back to the other. Maybe<?> maybe = tryFindAndInvokeMultiParameterMethod(instance, methodName, arguments); if (maybe.isAbsent()) maybe = tryFindAndInvokeSingleParameterMethod(instance, methodName, argument); return maybe; } else { return tryFindAndInvokeSingleParameterMethod(instance, methodName, argument); } } }