All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.juneau.swap.BuilderSwap Maven / Gradle / Ivy

// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
// * with the License.  You may obtain a copy of the License at                                                              *
// *                                                                                                                         *
// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
// *                                                                                                                         *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
// * specific language governing permissions and limitations under the License.                                              *
// ***************************************************************************************************************************
package org.apache.juneau.swap;

import static org.apache.juneau.internal.ClassUtils.*;

import java.lang.reflect.*;
import org.apache.juneau.*;
import org.apache.juneau.reflect.*;

/**
 * Specialized transform for builder classes.
 *
 * 
See Also:
* * @param The bean class. * @param The builder class. */ @SuppressWarnings("unchecked") public class BuilderSwap { private final Class objectClass; private final Class builderClass; private final Constructor objectConstructor; // public Pojo(Builder); private final Constructor builderConstructor; // protected Builder(); private final MethodInfo createBuilderMethod; // Builder create(); private final MethodInfo createObjectMethod; // Pojo build(); private ClassMeta builderClassMeta; /** * Constructor. * * @param objectClass The object class created by the builder class. * @param builderClass The builder class. * @param objectConstructor The object constructor that takes in a builder as a parameter. * @param builderConstructor The builder no-arg constructor. * @param createBuilderMethod The static create() method on the object class. * @param createObjectMethod The build() method on the builder class. */ protected BuilderSwap(Class objectClass, Class builderClass, Constructor objectConstructor, Constructor builderConstructor, MethodInfo createBuilderMethod, MethodInfo createObjectMethod) { this.objectClass = objectClass; this.builderClass = builderClass; this.objectConstructor = objectConstructor; this.builderConstructor = builderConstructor; this.createBuilderMethod = createBuilderMethod; this.createObjectMethod = createObjectMethod; } /** * The object class. * * @return The object class. */ public Class getObjectClass() { return objectClass; } /** * The builder class. * * @return The builder class. */ public Class getBuilderClass() { return builderClass; } /** * Returns the {@link ClassMeta} of the transformed class type. * *

* This value is cached for quick lookup. * * @param session * The bean context to use to get the class meta. * This is always going to be the same bean context that created this swap. * @return The {@link ClassMeta} of the transformed class type. */ public ClassMeta getBuilderClassMeta(BeanSession session) { if (builderClassMeta == null) builderClassMeta = session.getClassMeta(getBuilderClass()); return builderClassMeta; } /** * Creates a new builder object. * * @param session The current bean session. * @param hint A hint about the class type. * @return A new object. * @throws ExecutableException Exception occurred on invoked constructor/method/field. */ public B create(BeanSession session, ClassMeta hint) throws ExecutableException { if (createBuilderMethod != null) return (B)createBuilderMethod.invoke(null); try { return builderConstructor.newInstance(); } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new ExecutableException(e); } } /** * Creates a new object from the specified builder. * * @param session The current bean session. * @param builder The object builder. * @param hint A hint about the class type. * @return A new object. * @throws ExecutableException Exception occurred on invoked constructor/method/field. */ public T build(BeanSession session, B builder, ClassMeta hint) throws ExecutableException { if (createObjectMethod != null) return (T)createObjectMethod.invoke(builder); try { return objectConstructor.newInstance(builder); } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new ExecutableException(e); } } /** * Creates a BuilderSwap from the specified builder class if it qualifies as one. * * @param builderClass The potential builder class. * @param cVis Minimum constructor visibility. * @param mVis Minimum method visibility. * @return A new swap instance, or null if class wasn't a builder class. */ @SuppressWarnings("rawtypes") public static BuilderSwap findSwapFromBuilderClass(Class builderClass, Visibility cVis, Visibility mVis) { ClassInfo bci = ClassInfo.of(builderClass); if (bci.isNotPublic()) return null; Class objectClass = ClassInfo.of(builderClass).getParameterType(0, Builder.class); MethodInfo createObjectMethod, createBuilderMethod; ConstructorInfo objectConstructor; ConstructorInfo builderConstructor; createObjectMethod = getBuilderBuildMethod(bci); if (createObjectMethod != null) objectClass = createObjectMethod.getReturnType().inner(); if (objectClass == null) return null; ClassInfo pci = ClassInfo.of(objectClass); objectConstructor = pci.getDeclaredConstructor(x -> x.isVisible(cVis) && x.hasParamTypes(builderClass)); if (objectConstructor == null) return null; builderConstructor = bci.getNoArgConstructor(cVis); createBuilderMethod = getBuilderCreateMethod(pci); if (builderConstructor == null && createBuilderMethod == null) return null; return new BuilderSwap(objectClass, builderClass, objectConstructor.inner(), builderConstructor == null ? null : builderConstructor.inner(), createBuilderMethod, createObjectMethod); } /** * Creates a BuilderSwap from the specified object class if it has one. * * @param bc The bean context to use to look up annotations. * @param objectClass The object class to check. * @param cVis Minimum constructor visibility. * @param mVis Minimum method visibility. * @return A new swap instance, or null if class didn't have a builder class. */ @SuppressWarnings("rawtypes") public static BuilderSwap findSwapFromObjectClass(BeanContext bc, Class objectClass, Visibility cVis, Visibility mVis) { Value> builderClass = Value.empty(); MethodInfo objectCreateMethod, builderCreateMethod; ConstructorInfo objectConstructor = null; ConstructorInfo builderConstructor; bc.forEachAnnotation(org.apache.juneau.annotation.Builder.class, objectClass, x -> isNotVoid(x.value()), x -> builderClass.set(x.value())); ClassInfo pci = ClassInfo.of(objectClass); builderCreateMethod = getBuilderCreateMethod(pci); if (builderClass.isEmpty() && builderCreateMethod != null) builderClass.set(builderCreateMethod.getReturnType().inner()); if (builderClass.isEmpty()) { ConstructorInfo cc = pci.getPublicConstructor( x -> x.isVisible(cVis) && x.hasNumParams(1) && x.getParamType(0).isChildOf(Builder.class) ); if (cc != null) { objectConstructor = cc; builderClass.set(cc.getParamType(0).inner()); } } if (builderClass.isEmpty()) return null; ClassInfo bci = ClassInfo.of(builderClass.get()); builderConstructor = bci.getNoArgConstructor(cVis); if (builderConstructor == null && builderCreateMethod == null) return null; objectCreateMethod = getBuilderBuildMethod(bci); Class builderClass2 = builderClass.get(); if (objectConstructor == null) objectConstructor = pci.getDeclaredConstructor(x -> x.isVisible(cVis) && x.hasParamTypes(builderClass2)); if (objectConstructor == null && objectCreateMethod == null) return null; return new BuilderSwap(objectClass, builderClass.get(), objectConstructor == null ? null : objectConstructor.inner(), builderConstructor == null ? null : builderConstructor.inner(), builderCreateMethod, objectCreateMethod); } private static MethodInfo getBuilderCreateMethod(ClassInfo c) { return c.getPublicMethod( x -> x.isStatic() && x.hasName("create") && ! x.hasReturnType(c) && hasConstructorThatTakesType(c, x.getReturnType()) ); } private static boolean hasConstructorThatTakesType(ClassInfo c, ClassInfo argType) { return c.getPublicConstructor( x -> x.hasNumParams(1) && x.hasParamTypes(argType) ) != null; } private static MethodInfo getBuilderBuildMethod(ClassInfo c) { return c.getDeclaredMethod( x -> x.isNotStatic() && x.hasNoParams() && (!x.hasReturnType(void.class)) && x.hasName("build") ); } }