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.swap; 018 019import static org.apache.juneau.commons.reflect.ReflectionUtils.*; 020import static org.apache.juneau.commons.utils.Utils.*; 021 022import java.lang.reflect.*; 023 024import org.apache.juneau.*; 025import org.apache.juneau.commons.lang.*; 026import org.apache.juneau.commons.reflect.*; 027import org.apache.juneau.commons.utils.*; 028 029/** 030 * Specialized transform for builder classes. 031 * 032 * <h5 class='section'>See Also:</h5><ul> 033 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/PojoBuilders">POJO Builders</a> 034 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SwapBasics">Swap Basics</a> 035 036 * </ul> 037 * 038 * @param <T> The bean class. 039 * @param <B> The builder class. 040 */ 041@SuppressWarnings("unchecked") 042public class BuilderSwap<T,B> { 043 044 /** 045 * Creates a BuilderSwap from the specified builder class if it qualifies as one. 046 * 047 * @param builderClass The potential builder class. 048 * @param cVis Minimum constructor visibility. 049 * @param mVis Minimum method visibility. 050 * @return A new swap instance, or <jk>null</jk> if class wasn't a builder class. 051 */ 052 @SuppressWarnings("rawtypes") 053 public static BuilderSwap<?,?> findSwapFromBuilderClass(Class<?> builderClass, Visibility cVis, Visibility mVis) { 054 var bci = info(builderClass); 055 if (bci.isNotPublic()) 056 return null; 057 058 var objectClass = info(builderClass).getParameterType(0, Builder.class); 059 060 MethodInfo createObjectMethod, createBuilderMethod; 061 ConstructorInfo objectConstructor; 062 ConstructorInfo builderConstructor; 063 064 createObjectMethod = getBuilderBuildMethod(bci); 065 if (nn(createObjectMethod)) 066 objectClass = createObjectMethod.getReturnType().inner(); 067 068 if (objectClass == null) 069 return null; 070 071 var pci = info(objectClass); 072 073 objectConstructor = pci.getDeclaredConstructor(x -> x.isVisible(cVis) && x.hasParameterTypes(builderClass)).orElse(null); 074 if (objectConstructor == null) 075 return null; 076 077 builderConstructor = bci.getNoArgConstructor(cVis).orElse(null); 078 createBuilderMethod = getBuilderCreateMethod(pci); 079 if (builderConstructor == null && createBuilderMethod == null) 080 return null; 081 082 return new BuilderSwap(objectClass, builderClass, objectConstructor.inner(), builderConstructor == null ? null : builderConstructor.inner(), createBuilderMethod, createObjectMethod); 083 } 084 085 /** 086 * Creates a BuilderSwap from the specified object class if it has one. 087 * 088 * @param bc The bean context to use to look up annotations. 089 * @param objectClass The object class to check. 090 * @param cVis Minimum constructor visibility. 091 * @param mVis Minimum method visibility. 092 * @return A new swap instance, or <jk>null</jk> if class didn't have a builder class. 093 */ 094 @SuppressWarnings("rawtypes") 095 public static BuilderSwap<?,?> findSwapFromObjectClass(BeanContext bc, Class<?> objectClass, Visibility cVis, Visibility mVis) { 096 var builderClass = Value.<Class<?>>empty(); 097 MethodInfo objectCreateMethod, builderCreateMethod; 098 var objectConstructor = (ConstructorInfo)null; 099 ConstructorInfo builderConstructor; 100 var pci = info(objectClass); 101 102 bc.getAnnotationProvider().find(org.apache.juneau.annotation.Builder.class, pci).stream().map(x -> x.inner().value()).filter(ClassUtils::isNotVoid).forEach(x -> builderClass.set(x)); 103 104 builderCreateMethod = getBuilderCreateMethod(pci); 105 106 if (builderClass.isEmpty() && nn(builderCreateMethod)) 107 builderClass.set(builderCreateMethod.getReturnType().inner()); 108 109 if (builderClass.isEmpty()) { 110 // @formatter:off 111 var cc = pci.getPublicConstructor( 112 x -> x.isVisible(cVis) 113 && x.hasNumParameters(1) 114 && x.getParameter(0).getParameterType().isChildOf(Builder.class) 115 ).orElse(null); 116 // @formatter:on 117 if (nn(cc)) { 118 objectConstructor = cc; 119 builderClass.set(cc.getParameter(0).getParameterType().inner()); 120 } 121 } 122 123 if (builderClass.isEmpty()) 124 return null; 125 126 var bci = info(builderClass.get()); 127 builderConstructor = bci.getNoArgConstructor(cVis).orElse(null); 128 if (builderConstructor == null && builderCreateMethod == null) 129 return null; 130 131 objectCreateMethod = getBuilderBuildMethod(bci); 132 var builderClass2 = builderClass.get(); 133 if (objectConstructor == null) 134 objectConstructor = pci.getDeclaredConstructor(x -> x.isVisible(cVis) && x.hasParameterTypes(builderClass2)).orElse(null); 135 136 if (objectConstructor == null && objectCreateMethod == null) 137 return null; 138 139 return new BuilderSwap(objectClass, builderClass.get(), objectConstructor == null ? null : objectConstructor.inner(), builderConstructor == null ? null : builderConstructor.inner(), 140 builderCreateMethod, objectCreateMethod); 141 } 142 143 private static MethodInfo getBuilderBuildMethod(ClassInfo c) { 144 // @formatter:off 145 return c.getDeclaredMethod( 146 x -> x.isNotStatic() 147 && x.getParameterCount() == 0 148 && (!x.hasReturnType(void.class)) 149 && x.hasName("build") 150 ).orElse(null); 151 // @formatter:on 152 } 153 154 private static MethodInfo getBuilderCreateMethod(ClassInfo c) { 155 // @formatter:off 156 return c.getPublicMethod( 157 x -> x.isStatic() 158 && (x.hasName("create") || x.hasName("builder")) 159 && ! x.hasReturnType(c) 160 && hasConstructorThatTakesType(c, x.getReturnType()) 161 ).orElse(null); 162 // @formatter:on 163 } 164 165 private static boolean hasConstructorThatTakesType(ClassInfo c, ClassInfo argType) { 166 // @formatter:off 167 return nn(c.getPublicConstructor( 168 x -> x.hasNumParameters(1) 169 && x.hasParameterTypes(argType) 170 )); 171 // @formatter:on 172 } 173 174 private final Class<T> objectClass; 175 private final Class<B> builderClass; 176 177 private final Constructor<T> objectConstructor; // public Pojo(Builder); 178 179 private final Constructor<B> builderConstructor; // protected Builder(); 180 181 private final MethodInfo createBuilderMethod; // Builder create(); 182 183 private final MethodInfo createObjectMethod; // Pojo build(); 184 185 private ClassMeta<?> builderClassMeta; 186 187 /** 188 * Constructor. 189 * 190 * @param objectClass The object class created by the builder class. 191 * @param builderClass The builder class. 192 * @param objectConstructor The object constructor that takes in a builder as a parameter. 193 * @param builderConstructor The builder no-arg constructor. 194 * @param createBuilderMethod The static create() method on the object class. 195 * @param createObjectMethod The build() method on the builder class. 196 */ 197 protected BuilderSwap(Class<T> objectClass, Class<B> builderClass, Constructor<T> objectConstructor, Constructor<B> builderConstructor, MethodInfo createBuilderMethod, 198 MethodInfo createObjectMethod) { 199 this.objectClass = objectClass; 200 this.builderClass = builderClass; 201 this.objectConstructor = objectConstructor; 202 this.builderConstructor = builderConstructor; 203 this.createBuilderMethod = createBuilderMethod; 204 this.createObjectMethod = createObjectMethod; 205 } 206 207 /** 208 * Creates a new object from the specified builder. 209 * 210 * @param session The current bean session. 211 * @param builder The object builder. 212 * @param hint A hint about the class type. 213 * @return A new object. 214 * @throws ExecutableException Exception occurred on invoked constructor/method/field. 215 */ 216 public T build(BeanSession session, B builder, ClassMeta<?> hint) throws ExecutableException { 217 if (nn(createObjectMethod)) 218 return (T)createObjectMethod.invoke(builder); 219 try { 220 return objectConstructor.newInstance(builder); 221 } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { 222 throw new ExecutableException(e); 223 } 224 } 225 226 /** 227 * Creates a new builder object. 228 * 229 * @param session The current bean session. 230 * @param hint A hint about the class type. 231 * @return A new object. 232 * @throws ExecutableException Exception occurred on invoked constructor/method/field. 233 */ 234 public B create(BeanSession session, ClassMeta<?> hint) throws ExecutableException { 235 if (nn(createBuilderMethod)) 236 return (B)createBuilderMethod.invoke(null); 237 try { 238 return builderConstructor.newInstance(); 239 } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { 240 throw new ExecutableException(e); 241 } 242 } 243 244 /** 245 * The builder class. 246 * 247 * @return The builder class. 248 */ 249 public Class<B> getBuilderClass() { return builderClass; } 250 251 /** 252 * Returns the {@link ClassMeta} of the transformed class type. 253 * 254 * <p> 255 * This value is cached for quick lookup. 256 * 257 * @param session 258 * The bean context to use to get the class meta. 259 * This is always going to be the same bean context that created this swap. 260 * @return The {@link ClassMeta} of the transformed class type. 261 */ 262 public ClassMeta<?> getBuilderClassMeta(BeanSession session) { 263 if (builderClassMeta == null) 264 builderClassMeta = session.getClassMeta(getBuilderClass()); 265 return builderClassMeta; 266 } 267 268 /** 269 * The object class. 270 * 271 * @return The object class. 272 */ 273 public Class<T> getObjectClass() { return objectClass; } 274}