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}