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