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