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 static org.apache.juneau.commons.reflect.ReflectionUtils.*; 020import static org.apache.juneau.commons.utils.AssertionUtils.*; 021import static org.apache.juneau.commons.utils.Utils.*; 022 023import java.util.*; 024import java.util.function.*; 025 026import org.apache.juneau.annotation.*; 027import org.apache.juneau.commons.reflect.*; 028 029/** 030 * Utility class for creating beans through creator methods. 031 * 032 * <p> 033 * Used for finding and invoking methods on an object that take in arbitrary parameters and returns bean instances. 034 * 035 * <p> 036 * This class is instantiated through the following methods: 037 * <ul class='javatree'> 038 * <li class='jc'>{@link BeanStore} 039 * <ul class='javatreec'> 040 * <li class='jm'>{@link BeanStore#createMethodFinder(Class)} 041 * <li class='jm'>{@link BeanStore#createMethodFinder(Class,Class)} 042 * <li class='jm'>{@link BeanStore#createMethodFinder(Class,Object)} 043 * </ul> 044 * </li> 045 * </ul> 046 * 047 * <h5 class='section'>Example:</h5> 048 * <p class='bjava'> 049 * <jc>// The bean we want to create.</jc> 050 * <jk>public class</jk> A {} 051 * 052 * <jc>// The bean that has a creator method for the bean above.</jc> 053 * <jk>public class</jk> B { 054 * 055 * <jc>// Creator method.</jc> 056 * <jc>// Bean store must have a C bean and optionally a D bean.</jc> 057 * <jk>public</jk> A createA(C <jv>c</jv>, Optional<D> <jv>d</jv>) { 058 * <jk>return new</jk> A(<jv>c</jv>, <jv>d</jv>.orElse(<jk>null</jk>)); 059 * } 060 * } 061 * 062 * <jc>// Instantiate the bean with the creator method.</jc> 063 * B <mv>b</mv> = <jk>new</jk> B(); 064 * 065 * <jc>// Create a bean store with some mapped beans.</jc> 066 * BeanStore <mv>beanStore</mv> = BeanStore.<jsm>create</jsm>().addBean(C.<jk>class</jk>, <jk>new</jk> C()); 067 * 068 * <jc>// Instantiate the bean using the creator method.</jc> 069 * A <mv>a</mv> = <mv>beanStore</mv> 070 * .createMethodFinder(A.<jk>class</jk>, <mv>b</mv>) <jc>// Looking for creator for A on b object.</jc> 071 * .find(<js>"createA"</js>) <jc>// Look for method called "createA".</jc> 072 * .thenFind(<js>"createA2"</js>) <jc>// Then look for method called "createA2".</jc> 073 * .withDefault(()-><jk>new</jk> A()) <jc>// Optionally supply a default value if method not found.</jc> 074 * .run(); <jc>// Execute.</jc> 075 * </p> 076 * 077 * <h5 class='section'>See Also:</h5><ul> 078 * <li class='jc'>{@link BeanStore} 079 * </ul> 080 * 081 * @param <T> The bean type being created. 082 */ 083public class BeanCreateMethodFinder<T> { 084 085 private Class<T> beanType; 086 private final Class<?> resourceClass; 087 private final Object resource; 088 private final BeanStore beanStore; 089 090 private MethodInfo method; 091 private Object[] args; 092 093 BeanCreateMethodFinder(Class<T> beanType, Class<?> resourceClass, BeanStore beanStore) { 094 this.beanType = assertArgNotNull("beanType", beanType); 095 this.resource = null; 096 this.resourceClass = assertArgNotNull("resourceClass", resourceClass); 097 this.beanStore = BeanStore.of(beanStore); 098 } 099 100 BeanCreateMethodFinder(Class<T> beanType, Object resource, BeanStore beanStore) { 101 this.beanType = assertArgNotNull("beanType", beanType); 102 this.resource = assertArgNotNull("resource", resource); 103 this.resourceClass = resource.getClass(); 104 this.beanStore = BeanStore.of(beanStore, resource); 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 // @formatter:off 146 if (method == null) { 147 info(resourceClass).getPublicMethod( 148 x -> x.isNotDeprecated() 149 && x.hasReturnType(beanType) 150 && ! x.hasAnnotation(BeanIgnore.class) 151 && filter.test(x) 152 && beanStore.hasAllParams(x) 153 && (x.isStatic() || nn(resource)) 154 ).ifPresent(m -> { 155 method = m; 156 args = beanStore.getParams(m); 157 }); 158 } 159 return this; 160 // @formatter:on 161 } 162 163 /** 164 * Executes the matched method and returns the result. 165 * 166 * @return The object returned by the method invocation, or the default value if method was not found. 167 * @throws ExecutableException If method invocation threw an exception. 168 */ 169 @SuppressWarnings("unchecked") 170 public T run() throws ExecutableException { 171 if (nn(method)) 172 return (T)method.invoke(resource, args); 173 return null; 174 } 175 176 /** 177 * Same as {@link #run()} but also executes a consumer if the returned value was not <jk>null</jk>. 178 * 179 * @param consumer The consumer of the response. 180 * @return The object returned by the method invocation, or the default value if method was not found, or {@link Optional#empty()}. 181 * @throws ExecutableException If method invocation threw an exception. 182 */ 183 public T run(Consumer<? super T> consumer) throws ExecutableException { 184 T t = run(); 185 if (nn(t)) 186 consumer.accept(t); 187 return t; 188 } 189}