Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/main/java/io/beanmapper/annotations/BeanFactoryMethod.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.beanmapper.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation to mark static factory methods for bean instantiation.
*
* <p>This annotation allows specifying static factory methods that can be used
* to instantiate objects during the bean mapping process. The factory method
* must be static since it will be called before any instance of the target
* type is available.</p>
*
* <p>The value array specifies the names of the fields that should be provided
* to the factory method as arguments, in the correct order.</p>
*
* <p>Example usage:</p>
* <pre>
* public class Person {
* private String name;
* private int age;
*
* &#64;BeanFactoryMethod({"name", "age"})
* public static Person create(String name, int age) {
* return new Person(name, age);
* }
* }
* </pre>
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BeanFactoryMethod {

/**
* The names of the fields that should be provided to the factory method
* as arguments, in the correct order.
*
* @return array of field names
*/
String[] value();
}
14 changes: 14 additions & 0 deletions src/main/java/io/beanmapper/config/BeanMapperBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import io.beanmapper.core.collections.QueueCollectionHandler;
import io.beanmapper.core.collections.SetCollectionHandler;
import io.beanmapper.core.constructor.BeanInitializer;
import io.beanmapper.core.constructor.FactoryMethodAwareBeanInitializer;
import io.beanmapper.core.converter.BeanConverter;
import io.beanmapper.core.converter.collections.CollectionConverter;
import io.beanmapper.core.converter.impl.AnyToEnumConverter;
Expand Down Expand Up @@ -134,6 +135,19 @@ public BeanMapperBuilder setBeanInitializer(BeanInitializer beanInitializer) {
return this;
}

/**
* Enables factory method support by setting a FactoryMethodAwareBeanInitializer that will
* look for static methods annotated with @BeanFactoryMethod and use them for object
* instantiation when available. Falls back to the default bean initializer when no
* factory method is found.
*
* @return the builder for fluent configuration
*/
public BeanMapperBuilder withFactoryMethodSupport() {
this.configuration.setBeanInitializer(new FactoryMethodAwareBeanInitializer());
return this;
}

public BeanMapperBuilder setBeanUnproxy(BeanUnproxy beanUnproxy) {
this.configuration.setBeanUnproxy(beanUnproxy);
return this;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* (C) 2014 42 bv (www.42.nl). All rights reserved.
*/
package io.beanmapper.core.constructor;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import io.beanmapper.annotations.BeanFactoryMethod;
import io.beanmapper.strategy.ConstructorArguments;
import io.beanmapper.utils.BeanMapperTraceLogger;

/**
* A BeanInitializer that is aware of factory methods but integrates with the existing
* strategy framework. This initializer extends DefaultBeanInitializer but first checks
* for factory methods when no ConstructorArguments are provided.
*/
public class FactoryMethodAwareBeanInitializer extends DefaultBeanInitializer {

private final FactoryMethodBeanInitializer factoryMethodInitializer;

public FactoryMethodAwareBeanInitializer() {
this.factoryMethodInitializer = new FactoryMethodBeanInitializer(this);
}

/**
* {@inheritDoc}
*/
@Override
public <T> T instantiate(Class<T> beanClass, ConstructorArguments arguments) {
// If we have constructor arguments, use the factory method initializer
// which can handle both factory methods and fallback to default behavior
if (arguments != null) {
return factoryMethodInitializer.instantiate(beanClass, arguments);
}

// If no constructor arguments, check if there's a factory method that requires no arguments
Method factoryMethod = findNoArgFactoryMethod(beanClass);
if (factoryMethod != null) {
return factoryMethodInitializer.instantiate(beanClass, null);
}

// Fall back to default behavior (no-arg constructor)
return super.instantiate(beanClass, null);
}

/**
* Finds a factory method that requires no arguments.
*
* @param beanClass the target class
* @return a no-arg factory method, or null if none found
*/
private Method findNoArgFactoryMethod(Class<?> beanClass) {
for (Method method : beanClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(BeanFactoryMethod.class) &&
Modifier.isStatic(method.getModifiers()) &&
beanClass.isAssignableFrom(method.getReturnType()) &&
method.getParameterCount() == 0) {

BeanFactoryMethod annotation = method.getAnnotation(BeanFactoryMethod.class);
if (annotation.value().length == 0) {
BeanMapperTraceLogger.log("Found no-arg factory method {} for class {}",
method.getName(), beanClass.getName());
return method;
}
}
}
return null;
}
}
Loading
Loading