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}