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.ClassUtils.*; 016 017import java.lang.reflect.*; 018 019import org.apache.juneau.*; 020import org.apache.juneau.annotation.*; 021import org.apache.juneau.reflect.*; 022 023/** 024 * Specialized transform for builder classes. 025 * 026 * <ul class='seealso'> 027 * <li class='link'>{@doc juneau-marshall.Transforms.PojoBuilders} 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> pojoClass; 037 private final Class<B> builderClass; 038 private final Constructor<T> pojoConstructor; // public Pojo(Builder); 039 private final Constructor<B> builderConstructor; // public Builder(); 040 private final MethodInfo createBuilderMethod; // Builder create(); 041 private final MethodInfo createPojoMethod; // Pojo build(); 042 private ClassMeta<?> builderClassMeta; 043 044 /** 045 * Constructor. 046 * 047 * @param pojoClass The POJO class created by the builder class. 048 * @param builderClass The builder class. 049 * @param pojoConstructor The POJO 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 POJO class. 052 * @param createPojoMethod The build() method on the builder class. 053 */ 054 protected BuilderSwap(Class<T> pojoClass, Class<B> builderClass, Constructor<T> pojoConstructor, Constructor<B> builderConstructor, MethodInfo createBuilderMethod, MethodInfo createPojoMethod) { 055 this.pojoClass = pojoClass; 056 this.builderClass = builderClass; 057 this.pojoConstructor = pojoConstructor; 058 this.builderConstructor = builderConstructor; 059 this.createBuilderMethod = createBuilderMethod; 060 this.createPojoMethod = createPojoMethod; 061 } 062 063 /** 064 * The POJO class. 065 * 066 * @return The POJO class. 067 */ 068 public Class<T> getPojoClass() { 069 return pojoClass; 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 POJO. 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 POJO from the specified builder. 118 * 119 * @param session The current bean session. 120 * @param builder The POJO builder. 121 * @param hint A hint about the class type. 122 * @return A new POJO. 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 (createPojoMethod != null) 127 return (T)createPojoMethod.invoke(builder); 128 try { 129 return pojoConstructor.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 = getClassInfo(builderClass); 146 if (bci.isNotPublic()) 147 return null; 148 149// Class<?> pojoClass = getClassInfo(Builder.class).getParameterType(0, builderClass); 150 Class<?> pojoClass = getClassInfo(builderClass).getParameterType(0, Builder.class); 151 152 MethodInfo createPojoMethod, createBuilderMethod; 153 ConstructorInfo pojoConstructor; 154 ConstructorInfo builderConstructor; 155 156 createPojoMethod = bci.getBuilderBuildMethod(); 157 if (createPojoMethod != null) 158 pojoClass = createPojoMethod.getReturnType().inner(); 159 160 if (pojoClass == null) 161 return null; 162 163 ClassInfo pci = getClassInfo(pojoClass); 164 165 pojoConstructor = pci.getConstructor(cVis, builderClass); 166 if (pojoConstructor == null) 167 return null; 168 169 builderConstructor = bci.getNoArgConstructor(cVis); 170 createBuilderMethod = pci.getBuilderCreateMethod(); 171 if (builderConstructor == null && createBuilderMethod == null) 172 return null; 173 174 return new BuilderSwap(pojoClass, builderClass, pojoConstructor.inner(), builderConstructor == null ? null : builderConstructor.inner(), createBuilderMethod, createPojoMethod); 175 } 176 177 178 /** 179 * Creates a BuilderSwap from the specified POJO class if it has one. 180 * 181 * @param pojoClass The POJO 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<?,?> findSwapFromPojoClass(Class<?> pojoClass, Visibility cVis, Visibility mVis) { 188 Class<?> builderClass = null; 189 MethodInfo pojoCreateMethod, builderCreateMethod; 190 ConstructorInfo pojoConstructor = null; 191 ConstructorInfo builderConstructor; 192 193 org.apache.juneau.annotation.Builder b = pojoClass.getAnnotation(org.apache.juneau.annotation.Builder.class); 194 195 if (b != null && b.value() != Null.class) 196 builderClass = b.value(); 197 198 ClassInfo pci = getClassInfo(pojoClass); 199 200 builderCreateMethod = pci.getBuilderCreateMethod(); 201 202 if (builderClass == null && builderCreateMethod != null) 203 builderClass = builderCreateMethod.getReturnType().inner(); 204 205 if (builderClass == null) { 206 for (ConstructorInfo cc : pci.getPublicConstructors()) { 207 if (cc.isVisible(cVis) && cc.hasNumParams(1)) { 208 ClassInfo pt = cc.getParamType(0); 209 if (pt.isChildOf(Builder.class)) { 210 pojoConstructor = cc; 211 builderClass = pt.inner(); 212 } 213 } 214 } 215 } 216 217 if (builderClass == null) 218 return null; 219 220 ClassInfo bci = getClassInfo(builderClass); 221 builderConstructor = bci.getNoArgConstructor(cVis); 222 if (builderConstructor == null && builderCreateMethod == null) 223 return null; 224 225 pojoCreateMethod = bci.getBuilderBuildMethod(); 226 if (pojoConstructor == null) 227 pojoConstructor = pci.getConstructor(cVis, builderClass); 228 229 if (pojoConstructor == null && pojoCreateMethod == null) 230 return null; 231 232 return new BuilderSwap(pojoClass, builderClass, pojoConstructor == null ? null : pojoConstructor.inner(), builderConstructor == null ? null : builderConstructor.inner(), builderCreateMethod, pojoCreateMethod); 233 } 234}