001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.cp; 014 015import static org.apache.juneau.common.internal.ArgUtils.*; 016 017import java.util.*; 018import java.util.function.*; 019 020import org.apache.juneau.*; 021import org.apache.juneau.annotation.*; 022import org.apache.juneau.reflect.*; 023 024/** 025 * Utility class for creating beans through creator methods. 026 * 027 * <p> 028 * Used for finding and invoking methods on an object that take in arbitrary parameters and returns bean instances. 029 * 030 * <p> 031 * This class is instantiated through the following methods: 032 * <ul class='javatree'> 033 * <li class='jc'>{@link BeanStore} 034 * <ul class='javatreec'> 035 * <li class='jm'>{@link BeanStore#createMethodFinder(Class)} 036 * <li class='jm'>{@link BeanStore#createMethodFinder(Class,Class)} 037 * <li class='jm'>{@link BeanStore#createMethodFinder(Class,Object)} 038 * </ul> 039 * </li> 040 * </ul> 041 * 042 * <h5 class='section'>Example:</h5> 043 * <p class='bjava'> 044 * <jc>// The bean we want to create.</jc> 045 * <jk>public class</jk> A {} 046 * 047 * <jc>// The bean that has a creator method for the bean above.</jc> 048 * <jk>public class</jk> B { 049 * 050 * <jc>// Creator method.</jc> 051 * <jc>// Bean store must have a C bean and optionally a D bean.</jc> 052 * <jk>public</jk> A createA(C <jv>c</jv>, Optional<D> <jv>d</jv>) { 053 * <jk>return new</jk> A(<jv>c</jv>, <jv>d</jv>.orElse(<jk>null</jk>)); 054 * } 055 * } 056 * 057 * <jc>// Instantiate the bean with the creator method.</jc> 058 * B <mv>b</mv> = <jk>new</jk> B(); 059 * 060 * <jc>// Create a bean store with some mapped beans.</jc> 061 * BeanStore <mv>beanStore</mv> = BeanStore.<jsm>create</jsm>().addBean(C.<jk>class</jk>, <jk>new</jk> C()); 062 * 063 * <jc>// Instantiate the bean using the creator method.</jc> 064 * A <mv>a</mv> = <mv>beanStore</mv> 065 * .createMethodFinder(A.<jk>class</jk>, <mv>b</mv>) <jc>// Looking for creator for A on b object.</jc> 066 * .find(<js>"createA"</js>) <jc>// Look for method called "createA".</jc> 067 * .thenFind(<js>"createA2"</js>) <jc>// Then look for method called "createA2".</jc> 068 * .withDefault(()-><jk>new</jk> A()) <jc>// Optionally supply a default value if method not found.</jc> 069 * .run(); <jc>// Execute.</jc> 070 * </p> 071 * 072 * <h5 class='section'>See Also:</h5><ul> 073 * <li class='jc'>{@link BeanStore} 074 * </ul> 075 * 076 * @param <T> The bean type being created. 077 */ 078public class BeanCreateMethodFinder<T> { 079 080 private Class<T> beanType; 081 private final Class<?> resourceClass; 082 private final Object resource; 083 private final BeanStore beanStore; 084 085 private MethodInfo method; 086 private Object[] args; 087 088 private Supplier<T> def = ()->null; 089 090 BeanCreateMethodFinder(Class<T> beanType, Object resource, BeanStore beanStore) { 091 this.beanType = assertArgNotNull("beanType", beanType); 092 this.resource = assertArgNotNull("resource", resource); 093 this.resourceClass = resource.getClass(); 094 this.beanStore = BeanStore.of(beanStore, resource); 095 } 096 097 BeanCreateMethodFinder(Class<T> beanType, Class<?> resourceClass, BeanStore beanStore) { 098 this.beanType = assertArgNotNull("beanType", beanType); 099 this.resource = null; 100 this.resourceClass = assertArgNotNull("resourceClass", resourceClass); 101 this.beanStore = BeanStore.of(beanStore); 102 } 103 104 /** 105 * Adds a bean to the lookup for parameters. 106 * 107 * @param <T2> The bean type. 108 * @param c The bean type. 109 * @param t The bean. 110 * @return This object. 111 */ 112 public <T2> BeanCreateMethodFinder<T> addBean(Class<T2> c, T2 t) { 113 beanStore.addBean(c, t); 114 return this; 115 } 116 117 /** 118 * Find the method matching the specified predicate. 119 * 120 * <p> 121 * In order for the method to be used, it must adhere to the following restrictions: 122 * <ul> 123 * <li>The method must be public. 124 * <li>The method can be static. 125 * <li>The method name must match exactly. 126 * <li>The method must not be deprecated or annotated with {@link BeanIgnore}. 127 * <li>The method must have all parameter types specified in <c>requiredParams</c>. 128 * <li>The bean store must contain beans for all parameter types. 129 * <li>The bean store may contain beans for all {@link Optional} parameter types. 130 * </ul> 131 * 132 * <p> 133 * This method can be called multiple times with different method names or required parameters until a match is found. 134 * <br>Once a method is found, subsequent calls to this method will be no-ops. 135 * 136 * See {@link BeanStore#createMethodFinder(Class, Object)} for usage. 137 * 138 * @param filter The predicate to apply. 139 * @return This object. 140 */ 141 public BeanCreateMethodFinder<T> find(Predicate<MethodInfo> filter) { 142 if (method == null) { 143 method = ClassInfo.of(resourceClass).getPublicMethod( 144 x -> x.isNotDeprecated() 145 && x.hasReturnType(beanType) 146 && x.hasNoAnnotation(BeanIgnore.class) 147 && filter.test(x) 148 && beanStore.hasAllParams(x) 149 && (x.isStatic() || resource != null) 150 ); 151 if (method != null) 152 args = beanStore.getParams(method); 153 } 154 return this; 155 } 156 157 /** 158 * Shortcut for calling <c>find(<jv>x</jv> -> <jv>x</jv>.hasName(<jv>methodName</jv>))</c>. 159 * 160 * @param methodName The method name to match. 161 * @return This object. 162 */ 163 public BeanCreateMethodFinder<T> find(String methodName) { 164 return find(x -> x.hasName(methodName)); 165 } 166 167 /** 168 * Identical to {@link #find(Predicate)} but named for fluent-style calls. 169 * 170 * @param filter The predicate to apply. 171 * @return This object. 172 */ 173 public BeanCreateMethodFinder<T> thenFind(Predicate<MethodInfo> filter) { 174 return find(filter); 175 } 176 177 /** 178 * Identical to {@link #find(Predicate)} but named for fluent-style calls. 179 * 180 * @param methodName The method name to match. 181 * @return This object. 182 */ 183 public BeanCreateMethodFinder<T> thenFind(String methodName) { 184 return find(methodName); 185 } 186 187 /** 188 * A default value to return if no matching methods were found. 189 * 190 * @param def The default value. Can be <jk>null</jk>. 191 * @return This object. 192 */ 193 public BeanCreateMethodFinder<T> withDefault(T def) { 194 return withDefault(()->def); 195 } 196 197 /** 198 * A default value to return if no matching methods were found. 199 * 200 * @param def The default value. 201 * @return This object. 202 */ 203 public BeanCreateMethodFinder<T> withDefault(Supplier<T> def) { 204 assertArgNotNull("def", def); 205 this.def = def; 206 return this; 207 } 208 209 /** 210 * Executes the matched method and returns the result. 211 * 212 * @return The object returned by the method invocation, or the default value if method was not found. 213 * @throws ExecutableException If method invocation threw an exception. 214 */ 215 @SuppressWarnings("unchecked") 216 public T run() throws ExecutableException { 217 if (method != null) 218 return (T)method.invoke(resource, args); 219 return def.get(); 220 } 221 222 /** 223 * Same as {@link #run()} but also executes a consumer if the returned value was not <jk>null</jk>. 224 * 225 * @param consumer The consumer of the response. 226 * @return The object returned by the method invocation, or the default value if method was not found, or {@link Optional#empty()}. 227 * @throws ExecutableException If method invocation threw an exception. 228 */ 229 public T run(Consumer<? super T> consumer) throws ExecutableException { 230 T t = run(); 231 if (t != null) 232 consumer.accept(t); 233 return t; 234 } 235}