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