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