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}