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 bc The bean context to use to look up annotations. 179 * @param pojoClass The POJO class to check. 180 * @param cVis Minimum constructor visibility. 181 * @param mVis Minimum method visibility. 182 * @return A new swap instance, or <jk>null</jk> if class didn't have a builder class. 183 */ 184 @SuppressWarnings("rawtypes") 185 public static BuilderSwap<?,?> findSwapFromPojoClass(BeanContext bc, Class<?> pojoClass, Visibility cVis, Visibility mVis) { 186 Class<?> builderClass = null; 187 MethodInfo pojoCreateMethod, builderCreateMethod; 188 ConstructorInfo pojoConstructor = null; 189 ConstructorInfo builderConstructor; 190 191 org.apache.juneau.annotation.Builder b = bc.getAnnotation(org.apache.juneau.annotation.Builder.class, pojoClass); 192 193 if (b != null && b.value() != Null.class) 194 builderClass = b.value(); 195 196 ClassInfo pci = ClassInfo.of(pojoClass); 197 198 builderCreateMethod = pci.getBuilderCreateMethod(); 199 200 if (builderClass == null && builderCreateMethod != null) 201 builderClass = builderCreateMethod.getReturnType().inner(); 202 203 if (builderClass == null) { 204 for (ConstructorInfo cc : pci.getPublicConstructors()) { 205 if (cc.isVisible(cVis) && cc.hasNumParams(1)) { 206 ClassInfo pt = cc.getParamType(0); 207 if (pt.isChildOf(Builder.class)) { 208 pojoConstructor = cc; 209 builderClass = pt.inner(); 210 } 211 } 212 } 213 } 214 215 if (builderClass == null) 216 return null; 217 218 ClassInfo bci = ClassInfo.of(builderClass); 219 builderConstructor = bci.getNoArgConstructor(cVis); 220 if (builderConstructor == null && builderCreateMethod == null) 221 return null; 222 223 pojoCreateMethod = bci.getBuilderBuildMethod(); 224 if (pojoConstructor == null) 225 pojoConstructor = pci.getConstructor(cVis, builderClass); 226 227 if (pojoConstructor == null && pojoCreateMethod == null) 228 return null; 229 230 return new BuilderSwap(pojoClass, builderClass, pojoConstructor == null ? null : pojoConstructor.inner(), builderConstructor == null ? null : builderConstructor.inner(), builderCreateMethod, pojoCreateMethod); 231 } 232}