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.*; 021 022/** 023 * Specialized transform for builder classes. 024 * 025 * <h5 class='section'>See Also:</h5> 026 * <ul> 027 * <li class='link'><a class="doclink" href="../../../../overview-summary.html#juneau-marshall.PojoBuilders">Overview > juneau-marshall > POJO Builders</a> 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 Method createBuilderMethod; // Builder create(); 041 private final Method 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, Method createBuilderMethod, Method 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 Exception 105 */ 106 public B create(BeanSession session, ClassMeta<?> hint) throws Exception { 107 if (createBuilderMethod != null) 108 return (B)createBuilderMethod.invoke(null); 109 return builderConstructor.newInstance(); 110 } 111 112 /** 113 * Creates a new POJO from the specified builder. 114 * 115 * @param session The current bean session. 116 * @param builder The POJO builder. 117 * @param hint A hint about the class type. 118 * @return A new POJO. 119 * @throws Exception 120 */ 121 public T build(BeanSession session, B builder, ClassMeta<?> hint) throws Exception { 122 if (createPojoMethod != null) 123 return (T)createPojoMethod.invoke(builder); 124 return pojoConstructor.newInstance(builder); 125 } 126 127 /** 128 * Creates a BuilderSwap from the specified builder class if it qualifies as one. 129 * 130 * @param builderClass The potential builder class. 131 * @param cVis Minimum constructor visibility. 132 * @param mVis Minimum method visibility. 133 * @return A new swap instance, or <jk>null</jk> if class wasn't a builder class. 134 */ 135 @SuppressWarnings("rawtypes") 136 public static BuilderSwap<?,?> findSwapFromBuilderClass(Class<?> builderClass, Visibility cVis, Visibility mVis) { 137 if (! isPublic(builderClass)) 138 return null; 139 140 Class<?> pojoClass = resolveParameterType(Builder.class, 0, builderClass); 141 142 Method createPojoMethod, createBuilderMethod; 143 Constructor<?> pojoConstructor, builderConstructor; 144 145 createPojoMethod = findCreatePojoMethod(builderClass); 146 if (createPojoMethod != null) 147 pojoClass = createPojoMethod.getReturnType(); 148 149 if (pojoClass == null) 150 return null; 151 152 pojoConstructor = findConstructor(pojoClass, cVis, false, builderClass); 153 if (pojoConstructor == null) 154 return null; 155 156 builderConstructor = findNoArgConstructor(builderClass, cVis); 157 createBuilderMethod = findBuilderCreateMethod(pojoClass); 158 if (builderConstructor == null && createBuilderMethod == null) 159 return null; 160 161 return new BuilderSwap(pojoClass, builderClass, pojoConstructor, builderConstructor, createBuilderMethod, createPojoMethod); 162 } 163 164 165 /** 166 * Creates a BuilderSwap from the specified POJO class if it has one. 167 * 168 * @param pojoClass The POJO class to check. 169 * @param cVis Minimum constructor visibility. 170 * @param mVis Minimum method visibility. 171 * @return A new swap instance, or <jk>null</jk> if class didn't have a builder class. 172 */ 173 @SuppressWarnings("rawtypes") 174 public static BuilderSwap<?,?> findSwapFromPojoClass(Class<?> pojoClass, Visibility cVis, Visibility mVis) { 175 Class<?> builderClass = null; 176 Method pojoCreateMethod, builderCreateMethod; 177 Constructor<?> pojoConstructor = null, builderConstructor; 178 179 org.apache.juneau.annotation.Builder b = pojoClass.getAnnotation(org.apache.juneau.annotation.Builder.class); 180 181 if (b != null && b.value() != Null.class) 182 builderClass = b.value(); 183 184 builderCreateMethod = findBuilderCreateMethod(pojoClass); 185 186 if (builderClass == null && builderCreateMethod != null) 187 builderClass = builderCreateMethod.getReturnType(); 188 189 if (builderClass == null) { 190 for (Constructor cc : pojoClass.getConstructors()) { 191 if (cVis.isVisible(cc)) { 192 Class<?>[] pt = cc.getParameterTypes(); 193 if (pt.length == 1 && isParentClass(Builder.class, pt[0])) { 194 pojoConstructor = cc; 195 builderClass = pt[0]; 196 } 197 } 198 } 199 } 200 201 if (builderClass == null) 202 return null; 203 204 builderConstructor = findNoArgConstructor(builderClass, cVis); 205 if (builderConstructor == null && builderCreateMethod == null) 206 return null; 207 208 pojoCreateMethod = findCreatePojoMethod(builderClass); 209 if (pojoConstructor == null) 210 pojoConstructor = findConstructor(pojoClass, cVis, false, builderClass); 211 212 if (pojoConstructor == null && pojoCreateMethod == null) 213 return null; 214 215 return new BuilderSwap(pojoClass, builderClass, pojoConstructor, builderConstructor, builderCreateMethod, pojoCreateMethod); 216 } 217 218 private static Method findBuilderCreateMethod(Class<?> pojoClass) { 219 for (Method m : pojoClass.getDeclaredMethods()) 220 if (isPublic(m) && isStatic(m) && m.getName().equals("create") && m.getReturnType() != Void.class) 221 return m; 222 return null; 223 } 224 225 private static Method findCreatePojoMethod(Class<?> builderClass) { 226 for (Method m : builderClass.getDeclaredMethods()) 227 if ("build".equals(m.getName()) && ! (isStatic(m) || m.getReturnType() == Void.class)) 228 return m; 229 return null; 230 } 231}