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.transform;
014
015import static org.apache.juneau.internal.ClassFlags.*;
016import static org.apache.juneau.internal.ClassUtils.*;
017
018import java.lang.reflect.*;
019
020import org.apache.juneau.*;
021import org.apache.juneau.annotation.*;
022
023/**
024 * Specialized transform for builder classes.
025 *
026 * <h5 class='section'>See Also:</h5>
027 * <ul>
028 *    <li class='link'>{@doc juneau-marshall.Transforms.PojoBuilders}
029 * </ul>
030 *
031 * @param <T> The bean class.
032 * @param <B> The builder class.
033 */
034@SuppressWarnings("unchecked")
035public class BuilderSwap<T,B> {
036
037   private final Class<T> pojoClass;
038   private final Class<B> builderClass;
039   private final Constructor<T> pojoConstructor;      // public Pojo(Builder);
040   private final Constructor<B> builderConstructor;   // public Builder();
041   private final Method createBuilderMethod;          // Builder create();
042   private final Method createPojoMethod;             // Pojo build();
043   private ClassMeta<?> builderClassMeta;
044
045   /**
046    * Constructor.
047    *
048    * @param pojoClass The POJO class created by the builder class.
049    * @param builderClass The builder class.
050    * @param pojoConstructor The POJO constructor that takes in a builder as a parameter.
051    * @param builderConstructor The builder no-arg constructor.
052    * @param createBuilderMethod The static create() method on the POJO class.
053    * @param createPojoMethod The build() method on the builder class.
054    */
055   protected BuilderSwap(Class<T> pojoClass, Class<B> builderClass, Constructor<T> pojoConstructor, Constructor<B> builderConstructor, Method createBuilderMethod, Method createPojoMethod) {
056      this.pojoClass = pojoClass;
057      this.builderClass = builderClass;
058      this.pojoConstructor = pojoConstructor;
059      this.builderConstructor = builderConstructor;
060      this.createBuilderMethod = createBuilderMethod;
061      this.createPojoMethod = createPojoMethod;
062   }
063
064   /**
065    * The POJO class.
066    *
067    * @return The POJO class.
068    */
069   public Class<T> getPojoClass() {
070      return pojoClass;
071   }
072
073   /**
074    * The builder class.
075    *
076    * @return The builder class.
077    */
078   public Class<B> getBuilderClass() {
079      return builderClass;
080   }
081
082   /**
083    * Returns the {@link ClassMeta} of the transformed class type.
084    *
085    * <p>
086    * This value is cached for quick lookup.
087    *
088    * @param session
089    *    The bean context to use to get the class meta.
090    *    This is always going to be the same bean context that created this swap.
091    * @return The {@link ClassMeta} of the transformed class type.
092    */
093   public ClassMeta<?> getBuilderClassMeta(BeanSession session) {
094      if (builderClassMeta == null)
095         builderClassMeta = session.getClassMeta(getBuilderClass());
096      return builderClassMeta;
097   }
098
099   /**
100    * Creates a new builder object.
101    *
102    * @param session The current bean session.
103    * @param hint A hint about the class type.
104    * @return A new POJO.
105    * @throws Exception
106    */
107   public B create(BeanSession session, ClassMeta<?> hint) throws Exception {
108      if (createBuilderMethod != null)
109         return (B)createBuilderMethod.invoke(null);
110      return builderConstructor.newInstance();
111   }
112
113   /**
114    * Creates a new POJO from the specified builder.
115    *
116    * @param session The current bean session.
117    * @param builder The POJO builder.
118    * @param hint A hint about the class type.
119    * @return A new POJO.
120    * @throws Exception
121    */
122   public T build(BeanSession session, B builder, ClassMeta<?> hint) throws Exception {
123      if (createPojoMethod != null)
124         return (T)createPojoMethod.invoke(builder);
125      return pojoConstructor.newInstance(builder);
126   }
127
128   /**
129    * Creates a BuilderSwap from the specified builder class if it qualifies as one.
130    *
131    * @param builderClass The potential builder class.
132    * @param cVis Minimum constructor visibility.
133    * @param mVis Minimum method visibility.
134    * @return A new swap instance, or <jk>null</jk> if class wasn't a builder class.
135    */
136   @SuppressWarnings("rawtypes")
137   public static BuilderSwap<?,?> findSwapFromBuilderClass(Class<?> builderClass, Visibility cVis, Visibility mVis) {
138      if (! isPublic(builderClass))
139         return null;
140
141      Class<?> pojoClass = resolveParameterType(Builder.class, 0, builderClass);
142
143      Method createPojoMethod, createBuilderMethod;
144      Constructor<?> pojoConstructor, builderConstructor;
145
146      createPojoMethod = findCreatePojoMethod(builderClass);
147      if (createPojoMethod != null)
148         pojoClass = createPojoMethod.getReturnType();
149
150      if (pojoClass == null)
151         return null;
152
153      pojoConstructor = findConstructor(pojoClass, cVis, false, builderClass);
154      if (pojoConstructor == null)
155         return null;
156
157      builderConstructor = findNoArgConstructor(builderClass, cVis);
158      createBuilderMethod = findBuilderCreateMethod(pojoClass);
159      if (builderConstructor == null && createBuilderMethod == null)
160         return null;
161
162      return new BuilderSwap(pojoClass, builderClass, pojoConstructor, builderConstructor, createBuilderMethod, createPojoMethod);
163   }
164
165
166   /**
167    * Creates a BuilderSwap from the specified POJO class if it has one.
168    *
169    * @param pojoClass The POJO class to check.
170    * @param cVis Minimum constructor visibility.
171    * @param mVis Minimum method visibility.
172    * @return A new swap instance, or <jk>null</jk> if class didn't have a builder class.
173    */
174   @SuppressWarnings("rawtypes")
175   public static BuilderSwap<?,?> findSwapFromPojoClass(Class<?> pojoClass, Visibility cVis, Visibility mVis) {
176      Class<?> builderClass = null;
177      Method pojoCreateMethod, builderCreateMethod;
178      Constructor<?> pojoConstructor = null, builderConstructor;
179
180      org.apache.juneau.annotation.Builder b = pojoClass.getAnnotation(org.apache.juneau.annotation.Builder.class);
181
182      if (b != null && b.value() != Null.class)
183         builderClass = b.value();
184
185      builderCreateMethod = findBuilderCreateMethod(pojoClass);
186
187      if (builderClass == null && builderCreateMethod != null)
188         builderClass = builderCreateMethod.getReturnType();
189
190      if (builderClass == null) {
191         for (Constructor cc : pojoClass.getConstructors()) {
192            if (cVis.isVisible(cc) && hasNumArgs(cc, 1)) {
193               Class<?>[] pt = cc.getParameterTypes();
194               if (isParentClass(Builder.class, pt[0])) {
195                  pojoConstructor = cc;
196                  builderClass = pt[0];
197               }
198            }
199         }
200      }
201
202      if (builderClass == null)
203         return null;
204
205      builderConstructor = findNoArgConstructor(builderClass, cVis);
206      if (builderConstructor == null && builderCreateMethod == null)
207         return null;
208
209      pojoCreateMethod = findCreatePojoMethod(builderClass);
210      if (pojoConstructor == null)
211         pojoConstructor = findConstructor(pojoClass, cVis, false, builderClass);
212
213      if (pojoConstructor == null && pojoCreateMethod == null)
214         return null;
215
216      return new BuilderSwap(pojoClass, builderClass, pojoConstructor, builderConstructor, builderCreateMethod, pojoCreateMethod);
217   }
218
219   private static Method findBuilderCreateMethod(Class<?> pojoClass) {
220      for (Method m : pojoClass.getDeclaredMethods())
221         if (isAll(m, PUBLIC, STATIC) && hasName(m, "create") && ! hasReturnType(m, Void.class))
222            return m;
223      return null;
224   }
225
226   private static Method findCreatePojoMethod(Class<?> builderClass) {
227      for (Method m : builderClass.getDeclaredMethods())
228         if (isAll(m, NOT_STATIC) && hasName(m, "build") && ! hasReturnType(m, Void.class))
229            return m;
230      return null;
231   }
232}