├── misc └── houdini.png ├── src ├── main │ ├── resources │ │ └── META-INF │ │ │ └── spring.factories │ └── java │ │ └── com │ │ └── github │ │ └── vbauer │ │ └── houdini │ │ ├── annotation │ │ └── ObjectConverter.java │ │ ├── exception │ │ ├── base │ │ │ ├── ObjectConverterException.java │ │ │ └── MethodObjectConverterException.java │ │ ├── MissedObjectConverterException.java │ │ └── DuplicatedObjectConverterException.java │ │ ├── service │ │ ├── ObjectConverterRegistry.java │ │ ├── ObjectConverterService.java │ │ └── impl │ │ │ ├── ObjectConverterRegistryImpl.java │ │ │ ├── ReflectionUtils.java │ │ │ └── ObjectConverterServiceImpl.java │ │ ├── model │ │ ├── ObjectConverterInfoValue.java │ │ └── ObjectConverterInfoKey.java │ │ └── ext │ │ ├── spring │ │ ├── ObjectConverterBeanPostProcessor.java │ │ └── ObjectConverterConfiguration.java │ │ └── guice │ │ └── ObjectConverterModule.java └── test │ ├── java │ └── com │ │ └── github │ │ └── vbauer │ │ └── houdini │ │ ├── ext │ │ ├── spring │ │ │ ├── SpringBootTestContext.java │ │ │ └── SpringBootConverterServiceTest.java │ │ └── guice │ │ │ └── GuiceConverterServiceTest.java │ │ ├── core │ │ ├── BasicTest.java │ │ └── BasicIoCTest.java │ │ ├── model │ │ ├── User.java │ │ ├── UserDTO.java │ │ └── ModelTest.java │ │ ├── converter │ │ └── UserConverter.java │ │ ├── service │ │ └── impl │ │ │ └── ReflectionUtilsTest.java │ │ └── exception │ │ └── ObjectConverterExceptionTest.java │ └── resources │ └── checkstyle.xml ├── .travis.yml ├── deploy.sh ├── .gitignore ├── pom.xml ├── LICENSE └── README.md /misc/houdini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbauer/houdini/HEAD/misc/houdini.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | com.github.vbauer.houdini.ext.spring.ObjectConverterConfiguration 3 | -------------------------------------------------------------------------------- /src/main/java/com/github/vbauer/houdini/annotation/ObjectConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * This annotation allows to mark all methods in class or specific methods as object converters. 7 | * 8 | * @author Vladislav Bauer 9 | */ 10 | 11 | @Documented 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target({ ElementType.METHOD, ElementType.TYPE }) 14 | public @interface ObjectConverter { 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/github/vbauer/houdini/exception/base/ObjectConverterException.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.exception.base; 2 | 3 | /** 4 | * @author Vladislav Bauer 5 | */ 6 | 7 | @SuppressWarnings("serial") 8 | public abstract class ObjectConverterException extends RuntimeException { 9 | 10 | /** 11 | * {@inheritDoc} 12 | */ 13 | @Override 14 | public String getMessage() { 15 | return "Exception happened in conversion mechanism"; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/github/vbauer/houdini/ext/spring/SpringBootTestContext.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.ext.spring; 2 | 3 | import org.springframework.boot.autoconfigure.ImportAutoConfiguration; 4 | import org.springframework.context.annotation.ComponentScan; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | /** 8 | * @author Vladislav Bauer 9 | */ 10 | 11 | @Configuration 12 | @ComponentScan(basePackages = "com.github.vbauer.houdini") 13 | @ImportAutoConfiguration(ObjectConverterConfiguration.class) 14 | class SpringBootTestContext { 15 | } 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | script: mvn clean package -P strict 4 | 5 | after_success: 6 | - mvn jacoco:report coveralls:report 7 | - bash ./deploy.sh 8 | 9 | jdk: 10 | - oraclejdk8 11 | - oraclejdk9 12 | - openjdk10 13 | - openjdk11 14 | 15 | sudo: false 16 | cache: 17 | directories: 18 | - $HOME/.m2 19 | 20 | env: 21 | global: 22 | - GH_REF: github.com/vbauer/houdini.git 23 | - secure: "ay/H2CtvrjKol8rRT6eMj7dgkjOa1t8WDMY8gTiu1iXAGoAb6DusL+Xrn8I52GbifEZqQrIM9rCwvsENv83MUyJn8hbmnSBcdEtLmrCWcZynBsRCYfmAmqyT1HoLZnxUEflVSfW7UPXLxwf6Ny4ImiNg5KLLcc/HrNeplqDyLOE=" 24 | -------------------------------------------------------------------------------- /src/test/java/com/github/vbauer/houdini/core/BasicTest.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.core; 2 | 3 | import com.pushtorefresh.private_constructor_checker.PrivateConstructorChecker; 4 | import org.junit.runner.RunWith; 5 | import org.junit.runners.BlockJUnit4ClassRunner; 6 | 7 | /** 8 | * @author Vladislav Bauer 9 | */ 10 | 11 | @RunWith(BlockJUnit4ClassRunner.class) 12 | public abstract class BasicTest { 13 | 14 | protected Class checkUtilConstructorContract(final Class utilClass) throws Exception { 15 | PrivateConstructorChecker 16 | .forClass(utilClass) 17 | .expectedTypeOfException(UnsupportedOperationException.class) 18 | .check(); 19 | 20 | return utilClass; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/com/github/vbauer/houdini/model/User.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.model; 2 | 3 | /** 4 | * @author Vladislav Bauer 5 | */ 6 | 7 | public class User { 8 | 9 | private int id; 10 | private String login; 11 | private String password; 12 | 13 | 14 | public int getId() { 15 | return id; 16 | } 17 | 18 | public User setId(final int id) { 19 | this.id = id; 20 | return this; 21 | } 22 | 23 | public String getLogin() { 24 | return login; 25 | } 26 | 27 | public User setLogin(final String login) { 28 | this.login = login; 29 | return this; 30 | } 31 | 32 | public String getPassword() { 33 | return password; 34 | } 35 | 36 | public User setPassword(final String password) { 37 | this.password = password; 38 | return this; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/com/github/vbauer/houdini/model/UserDTO.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.model; 2 | 3 | /** 4 | * @author Vladislav Bauer 5 | */ 6 | 7 | public class UserDTO { 8 | 9 | private int id; 10 | private String login; 11 | private String password; 12 | 13 | 14 | public int getId() { 15 | return id; 16 | } 17 | 18 | public UserDTO setId(final int id) { 19 | this.id = id; 20 | return this; 21 | } 22 | 23 | public String getLogin() { 24 | return login; 25 | } 26 | 27 | public UserDTO setLogin(final String login) { 28 | this.login = login; 29 | return this; 30 | } 31 | 32 | public String getPassword() { 33 | return password; 34 | } 35 | 36 | public UserDTO setPassword(final String password) { 37 | this.password = password; 38 | return this; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/github/vbauer/houdini/exception/MissedObjectConverterException.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.exception; 2 | 3 | import com.github.vbauer.houdini.exception.base.MethodObjectConverterException; 4 | 5 | import java.util.Arrays; 6 | 7 | /** 8 | * @author Vladislav Bauer 9 | */ 10 | 11 | @SuppressWarnings("serial") 12 | public class MissedObjectConverterException extends MethodObjectConverterException { 13 | 14 | public MissedObjectConverterException( 15 | final Class returnType, final Class... parameterTypes 16 | ) { 17 | super(returnType, parameterTypes); 18 | } 19 | 20 | 21 | /** 22 | * {@inheritDoc} 23 | */ 24 | @Override 25 | public String getMessage() { 26 | return String.format( 27 | "Converter was not found. Parameter types: %s, return type: %s", 28 | Arrays.toString(getParameterTypes()), getReturnType() 29 | ); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/github/vbauer/houdini/exception/DuplicatedObjectConverterException.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.exception; 2 | 3 | import com.github.vbauer.houdini.exception.base.MethodObjectConverterException; 4 | 5 | import java.util.Arrays; 6 | 7 | /** 8 | * @author Vladislav Bauer 9 | */ 10 | 11 | @SuppressWarnings("serial") 12 | public class DuplicatedObjectConverterException extends MethodObjectConverterException { 13 | 14 | public DuplicatedObjectConverterException( 15 | final Class returnType, final Class... parameterTypes 16 | ) { 17 | super(returnType, parameterTypes); 18 | } 19 | 20 | 21 | /** 22 | * {@inheritDoc} 23 | */ 24 | @Override 25 | public String getMessage() { 26 | return String.format( 27 | "Duplicated converter detected. Parameter types: %s, return type: %s", 28 | Arrays.toString(getParameterTypes()), getReturnType() 29 | ); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/github/vbauer/houdini/service/ObjectConverterRegistry.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.service; 2 | 3 | import com.github.vbauer.houdini.model.ObjectConverterInfoValue; 4 | 5 | /** 6 | * Registry to store information about available converters. 7 | * 8 | * @author Vladislav Bauer 9 | */ 10 | 11 | public interface ObjectConverterRegistry { 12 | 13 | /** 14 | * Register a new converter in the store. 15 | * 16 | * @param bean object with converter methods 17 | */ 18 | void registerConverters(Object bean); 19 | 20 | /** 21 | * Find converter in the registry. 22 | * 23 | * @param resultClass class of result/output parameter 24 | * @param sources array with classes for input parameters 25 | * @param type of the result class 26 | * @return information about available converter or MissedObjectConverterException otherwise 27 | */ 28 | ObjectConverterInfoValue findConverter(Class resultClass, Object... sources); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit with nonzero exit code if anything fails 4 | set -e 5 | 6 | # Lets work only for master 7 | if ! [ "$TRAVIS_BRANCH" = "master" ] 8 | then 9 | echo "Not a master, not deploying" 10 | exit 0 11 | fi 12 | 13 | # Generate Maven site 14 | mvn site 15 | 16 | # Go to the generated directory and create a *new* Git repo 17 | cd target/site 18 | git init 19 | 20 | # Inside this git repo we'll pretend to be a new user 21 | git config user.name "Vladislav Bauer" 22 | git config user.email "bauer.vlad@gmail.com" 23 | 24 | # The first and only commit to this new Git repo contains all the 25 | # files present with the commit message "Generate Maven Site" 26 | git add . 27 | git commit -m "Generate Maven Site" 28 | 29 | # Force push from the current repo's master branch to the remote 30 | # repo's gh-pages branch. (All previous history on the gh-pages branch 31 | # will be lost, since we are overwriting it.) We redirect any output to 32 | # /dev/null to hide any sensitive credential data that might otherwise be exposed 33 | git push --force --quiet "https://${GH_TOKEN}@${GH_REF}" master:gh-pages > /dev/null 2>&1 34 | -------------------------------------------------------------------------------- /src/main/java/com/github/vbauer/houdini/exception/base/MethodObjectConverterException.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.exception.base; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | * @author Vladislav Bauer 7 | */ 8 | 9 | @SuppressWarnings("serial") 10 | public abstract class MethodObjectConverterException extends ObjectConverterException { 11 | 12 | private final Class[] parameterTypes; 13 | private final Class returnType; 14 | 15 | 16 | public MethodObjectConverterException(final Class returnType, final Class... parameterTypes) { 17 | this.returnType = returnType; 18 | this.parameterTypes = parameterTypes; 19 | } 20 | 21 | 22 | /** 23 | * Retrieve a copy of the input parameter types. 24 | * 25 | * @return input parameter types 26 | */ 27 | public Class[] getParameterTypes() { 28 | return Arrays.copyOf(parameterTypes, parameterTypes.length); 29 | } 30 | 31 | /** 32 | * Retrieve an output / return type. 33 | * 34 | * @return return type 35 | */ 36 | public Class getReturnType() { 37 | return returnType; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/com/github/vbauer/houdini/ext/spring/SpringBootConverterServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.ext.spring; 2 | 3 | import com.github.vbauer.houdini.converter.UserConverter; 4 | import com.github.vbauer.houdini.core.BasicIoCTest; 5 | import com.github.vbauer.houdini.service.ObjectConverterService; 6 | import org.junit.Before; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.context.ApplicationContext; 10 | import org.springframework.test.context.ContextConfiguration; 11 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 12 | 13 | /** 14 | * @author Vladislav Bauer 15 | */ 16 | 17 | @SuppressWarnings("all") 18 | @RunWith(SpringJUnit4ClassRunner.class) 19 | @ContextConfiguration(classes = SpringBootTestContext.class) 20 | public class SpringBootConverterServiceTest extends BasicIoCTest { 21 | 22 | @Autowired 23 | private ApplicationContext context; 24 | 25 | 26 | @Before 27 | public void onInit() { 28 | this.converterService = context.getBean(ObjectConverterService.class); 29 | this.userConverter = context.getBean(UserConverter.class); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/github/vbauer/houdini/model/ObjectConverterInfoValue.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.model; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | /** 6 | * Class represents internal information about object converter. 7 | * 8 | * @param class type 9 | * @author Vladislav Bauer 10 | */ 11 | 12 | public final class ObjectConverterInfoValue { 13 | 14 | private final Method method; 15 | private final T object; 16 | 17 | 18 | public ObjectConverterInfoValue(final Method method, final T object) { 19 | this.method = method; 20 | this.object = object; 21 | } 22 | 23 | 24 | /** 25 | * Get method that is used to convert data. 26 | * 27 | * @return method for conversion 28 | */ 29 | public Method getMethod() { 30 | return method; 31 | } 32 | 33 | /** 34 | * Get object that contains method for conversion. 35 | * @return root objectF 36 | */ 37 | public T getObject() { 38 | return object; 39 | } 40 | 41 | /** 42 | * {@inheritDoc} 43 | */ 44 | @Override 45 | public String toString() { 46 | return String.format("[%s %s]", getMethod(), getObject()); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/com/github/vbauer/houdini/converter/UserConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.converter; 2 | 3 | import com.github.vbauer.houdini.annotation.ObjectConverter; 4 | import com.github.vbauer.houdini.model.User; 5 | import com.github.vbauer.houdini.model.UserDTO; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * @author Vladislav Bauer 10 | */ 11 | 12 | @SuppressWarnings("all") 13 | @Component 14 | @ObjectConverter 15 | public class UserConverter { 16 | 17 | public static UserDTO fullInfo(final User user, final Boolean full) { 18 | final UserDTO userDTO = new UserDTO() 19 | .setId(user.getId()) 20 | .setLogin(user.getLogin()); 21 | 22 | if (Boolean.TRUE.equals(full)) { 23 | userDTO.setPassword(user.getPassword()); 24 | } 25 | 26 | return userDTO; 27 | } 28 | 29 | public UserDTO shortInfo(final User user) { 30 | return fullInfo(user, false); 31 | } 32 | 33 | public UserDTO badConverter(final User user, final Boolean full, final Boolean male) { 34 | throw new UnsupportedOperationException("Converter is not supported"); 35 | } 36 | 37 | private UserDTO privateConverter(final String login) { 38 | return shortInfo(new User().setLogin(login)); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/com/github/vbauer/houdini/ext/guice/GuiceConverterServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.ext.guice; 2 | 3 | import com.github.vbauer.houdini.converter.UserConverter; 4 | import com.github.vbauer.houdini.core.BasicIoCTest; 5 | import com.github.vbauer.houdini.service.ObjectConverterRegistry; 6 | import com.github.vbauer.houdini.service.ObjectConverterService; 7 | import com.google.inject.Guice; 8 | import com.google.inject.Injector; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | 12 | import static org.hamcrest.Matchers.notNullValue; 13 | import static org.junit.Assert.assertThat; 14 | 15 | /** 16 | * @author Vladislav Bauer 17 | */ 18 | 19 | public class GuiceConverterServiceTest extends BasicIoCTest { 20 | 21 | private Injector injector; 22 | 23 | 24 | @Before 25 | public void onInit() { 26 | this.injector = Guice.createInjector(new ObjectConverterModule()); 27 | this.converterService = injector.getInstance(ObjectConverterService.class); 28 | this.userConverter = injector.getInstance(UserConverter.class); 29 | } 30 | 31 | 32 | @Test 33 | public void testDefinedServices() { 34 | assertThat(injector.getInstance(ObjectConverterService.class), notNullValue()); 35 | assertThat(injector.getInstance(ObjectConverterRegistry.class), notNullValue()); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/github/vbauer/houdini/ext/spring/ObjectConverterBeanPostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.ext.spring; 2 | 3 | import com.github.vbauer.houdini.service.ObjectConverterRegistry; 4 | import com.github.vbauer.houdini.service.ObjectConverterService; 5 | import org.springframework.beans.factory.config.BeanPostProcessor; 6 | 7 | /** 8 | * Spring {@link BeanPostProcessor} to collect object converters. 9 | * 10 | * It finds all corresponding class and methods with {@link com.github.vbauer.houdini.annotation.ObjectConverter} 11 | * annotation and register it in {@link ObjectConverterRegistry}. 12 | * 13 | * @author Vladislav Bauer 14 | */ 15 | 16 | public class ObjectConverterBeanPostProcessor implements BeanPostProcessor { 17 | 18 | private final ObjectConverterService converterService; 19 | 20 | 21 | public ObjectConverterBeanPostProcessor(final ObjectConverterService converterService) { 22 | this.converterService = converterService; 23 | } 24 | 25 | 26 | /** 27 | * {@inheritDoc} 28 | */ 29 | @Override 30 | public Object postProcessBeforeInitialization(final Object bean, final String beanName) { 31 | return bean; 32 | } 33 | 34 | /** 35 | * {@inheritDoc} 36 | */ 37 | @Override 38 | public Object postProcessAfterInitialization(final Object bean, final String beanName) { 39 | final ObjectConverterRegistry registry = converterService.getConverterRegistry(); 40 | registry.registerConverters(bean); 41 | return bean; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/github/vbauer/houdini/ext/guice/ObjectConverterModule.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.ext.guice; 2 | 3 | import com.github.vbauer.houdini.service.ObjectConverterRegistry; 4 | import com.github.vbauer.houdini.service.ObjectConverterService; 5 | import com.github.vbauer.houdini.service.impl.ObjectConverterRegistryImpl; 6 | import com.github.vbauer.houdini.service.impl.ObjectConverterServiceImpl; 7 | import com.google.inject.AbstractModule; 8 | import com.google.inject.TypeLiteral; 9 | import com.google.inject.matcher.Matcher; 10 | import com.google.inject.matcher.Matchers; 11 | import com.google.inject.spi.InjectionListener; 12 | import com.google.inject.spi.TypeEncounter; 13 | import com.google.inject.spi.TypeListener; 14 | 15 | /** 16 | * Guice module to work with Houdini. 17 | * 18 | * @author Vladislav Bauer 19 | */ 20 | 21 | public class ObjectConverterModule extends AbstractModule { 22 | 23 | private final Matcher typeMatcher; 24 | 25 | 26 | public ObjectConverterModule() { 27 | this(Matchers.any()); 28 | } 29 | 30 | public ObjectConverterModule(final Matcher typeMatcher) { 31 | this.typeMatcher = typeMatcher; 32 | } 33 | 34 | 35 | /** 36 | * {@inheritDoc} 37 | */ 38 | @Override 39 | protected void configure() { 40 | final ObjectConverterRegistry registry = new ObjectConverterRegistryImpl(); 41 | final ObjectConverterServiceImpl converter = new ObjectConverterServiceImpl(registry); 42 | 43 | bind(ObjectConverterRegistry.class).toInstance(registry); 44 | bind(ObjectConverterService.class).toInstance(converter); 45 | 46 | bindListener(typeMatcher, createTypeListener(registry)); 47 | } 48 | 49 | 50 | private TypeListener createTypeListener(final ObjectConverterRegistry registry) { 51 | return new TypeListener() { 52 | @Override 53 | public void hear(final TypeLiteral typeLiteral, final TypeEncounter typeEncounter) { 54 | typeEncounter.register((InjectionListener) registry::registerConverters); 55 | } 56 | }; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/github/vbauer/houdini/ext/spring/ObjectConverterConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.ext.spring; 2 | 3 | import com.github.vbauer.houdini.service.ObjectConverterRegistry; 4 | import com.github.vbauer.houdini.service.ObjectConverterService; 5 | import com.github.vbauer.houdini.service.impl.ObjectConverterRegistryImpl; 6 | import com.github.vbauer.houdini.service.impl.ObjectConverterServiceImpl; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | /** 12 | * Auto-configuration component for Spring Boot. 13 | * 14 | * @author Vladislav Bauer 15 | */ 16 | 17 | @Configuration 18 | public class ObjectConverterConfiguration { 19 | 20 | /** 21 | * Create {@link ObjectConverterRegistry} and register it in application context. 22 | * 23 | * @return object converter registry 24 | */ 25 | @Bean 26 | @ConditionalOnMissingBean 27 | public ObjectConverterRegistry objectConverterRegistry() { 28 | return new ObjectConverterRegistryImpl(); 29 | } 30 | 31 | /** 32 | * Create {@link ObjectConverterService} and register it in application context. 33 | * 34 | * @param objectConverterRegistry object converter registry 35 | * @return object converter service 36 | */ 37 | @Bean 38 | @ConditionalOnMissingBean 39 | public ObjectConverterService objectConverterService( 40 | final ObjectConverterRegistry objectConverterRegistry 41 | ) { 42 | return new ObjectConverterServiceImpl(objectConverterRegistry); 43 | } 44 | 45 | /** 46 | * Create {@link ObjectConverterBeanPostProcessor} and register it in application context. 47 | * 48 | * @param objectConverterService object converter service 49 | * @return bean post processor to collect object converters 50 | */ 51 | @Bean 52 | @ConditionalOnMissingBean 53 | public ObjectConverterBeanPostProcessor objectConverterBeanPostProcessor( 54 | final ObjectConverterService objectConverterService 55 | ) { 56 | return new ObjectConverterBeanPostProcessor(objectConverterService); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/com/github/vbauer/houdini/model/ModelTest.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.model; 2 | 3 | import com.github.vbauer.houdini.core.BasicTest; 4 | import org.junit.Test; 5 | 6 | import static org.hamcrest.Matchers.equalTo; 7 | import static org.hamcrest.Matchers.not; 8 | import static org.hamcrest.Matchers.notNullValue; 9 | import static org.hamcrest.Matchers.nullValue; 10 | import static org.hamcrest.collection.IsArrayContainingInAnyOrder.arrayContainingInAnyOrder; 11 | import static org.junit.Assert.assertThat; 12 | 13 | /** 14 | * @author Vladislav Bauer 15 | */ 16 | 17 | public class ModelTest extends BasicTest { 18 | 19 | @Test 20 | public void testObjectConverterInfoKey() { 21 | final Class returnType1 = Integer.class; 22 | final Class returnType2 = String.class; 23 | 24 | final Class[] sourceTypes1 = new Class[] { String.class }; 25 | final Class[] sourceTypes2 = new Class[] { Integer.class }; 26 | 27 | final ObjectConverterInfoKey key1 = new ObjectConverterInfoKey<>(returnType1, sourceTypes1); 28 | 29 | assertThat(key1.getSources(), arrayContainingInAnyOrder(sourceTypes1)); 30 | assertThat(key1.getTarget(), equalTo(returnType1)); 31 | 32 | assertThat(key1.hashCode(), not(equalTo(0))); 33 | assertThat(key1.toString(), notNullValue()); 34 | 35 | assertThat(new ObjectConverterInfoKey<>(returnType1, sourceTypes1), equalTo(key1)); 36 | assertThat(new ObjectConverterInfoKey<>(returnType1), not(equalTo(key1))); 37 | assertThat(new ObjectConverterInfoKey<>(returnType1, sourceTypes2), not(equalTo(key1))); 38 | 39 | assertThat( 40 | new ObjectConverterInfoKey<>(returnType2, sourceTypes1), 41 | not(equalTo((ObjectConverterInfoKey) key1)) 42 | ); 43 | 44 | assertThat( 45 | new ObjectConverterInfoKey<>(returnType1), 46 | equalTo(new ObjectConverterInfoKey<>(returnType1)) 47 | ); 48 | 49 | assertThat(key1, not(equalTo(new Object()))); 50 | } 51 | 52 | @Test 53 | public void testObjectConverterInfoValue() { 54 | final Integer object = 1; 55 | final ObjectConverterInfoValue value = 56 | new ObjectConverterInfoValue<>(null, object); 57 | 58 | assertThat(value.getObject(), equalTo(object)); 59 | assertThat(value.getMethod(), nullValue()); 60 | assertThat(value.toString(), notNullValue()); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/github/vbauer/houdini/service/ObjectConverterService.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.service; 2 | 3 | import java.util.List; 4 | import java.util.Set; 5 | 6 | /** 7 | * The main service to make conversions. 8 | * 9 | * @author Vladislav Bauer 10 | */ 11 | 12 | public interface ObjectConverterService { 13 | 14 | /** 15 | * Get the corresponding {@link ObjectConverterRegistry}. 16 | * 17 | * @return object converter registry 18 | */ 19 | ObjectConverterRegistry getConverterRegistry(); 20 | 21 | /** 22 | * Convert data to the result class. 23 | * 24 | * @param resultClass result class 25 | * @param sources array with input parameters for the corresponding converter 26 | * @param type of the result class 27 | * @return converted object 28 | */ 29 | R convert(Class resultClass, Object... sources); 30 | 31 | /** 32 | * Convert a set of objects to the set of objects with the different type. 33 | * 34 | * @param resultClass result class 35 | * @param sources set with input objects 36 | * @param type of the result class 37 | * @param type of the input parameters 38 | * @return set of converted objects 39 | */ 40 | Set convert(Class resultClass, Set sources); 41 | 42 | /** 43 | * Convert a list of objects to the list of objects with the different type. 44 | * 45 | * @param resultClass result class 46 | * @param sources list with input objects 47 | * @param type of the result class 48 | * @param type of the input parameters 49 | * @return list of converted objects 50 | */ 51 | List convert(Class resultClass, List sources); 52 | 53 | /** 54 | * Convert a set of objects to the set of objects with the different type. 55 | * If the result set contains a single element it returns only this element instead of set. 56 | * 57 | * @param resultClass result class 58 | * @param sources set with input objects 59 | * @param type of the result class 60 | * @param type of the input parameters 61 | * @return set of objects or single element 62 | */ 63 | Object convertToOneOrSet(Class resultClass, Set sources); 64 | 65 | /** 66 | * Convert a list of objects to the list of objects with the different type. 67 | * If the result list contains a single element it returns only this element instead of list. 68 | * 69 | * @param resultClass result class 70 | * @param sources list with input objects 71 | * @param type of the result class 72 | * @param type of the input parameters 73 | * @return list of converted objects or single element 74 | */ 75 | Object convertToOneOrList(Class resultClass, List sources); 76 | 77 | } 78 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | ### Java ### 4 | *.class 5 | 6 | # Mobile Tools for Java (J2ME) 7 | .mtj.tmp/ 8 | 9 | # Package Files # 10 | *.jar 11 | *.war 12 | *.ear 13 | 14 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 15 | hs_err_pid* 16 | 17 | 18 | ### JetBrains ### 19 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 20 | 21 | *.iml 22 | 23 | ## Directory-based project format: 24 | .idea/ 25 | # if you remove the above rule, at least ignore the following: 26 | 27 | # User-specific stuff: 28 | # .idea/workspace.xml 29 | # .idea/tasks.xml 30 | # .idea/dictionaries 31 | 32 | # Sensitive or high-churn files: 33 | # .idea/dataSources.ids 34 | # .idea/dataSources.xml 35 | # .idea/sqlDataSources.xml 36 | # .idea/dynamic.xml 37 | # .idea/uiDesigner.xml 38 | 39 | # Gradle: 40 | # .idea/gradle.xml 41 | # .idea/libraries 42 | 43 | # Mongo Explorer plugin: 44 | # .idea/mongoSettings.xml 45 | 46 | ## File-based project format: 47 | *.ipr 48 | *.iws 49 | 50 | ## Plugin-specific files: 51 | 52 | # IntelliJ 53 | out/ 54 | 55 | # mpeltonen/sbt-idea plugin 56 | .idea_modules/ 57 | 58 | # JIRA plugin 59 | atlassian-ide-plugin.xml 60 | 61 | # Crashlytics plugin (for Android Studio and IntelliJ) 62 | com_crashlytics_export_strings.xml 63 | crashlytics.properties 64 | crashlytics-build.properties 65 | 66 | 67 | ### Maven ### 68 | target/ 69 | pom.xml.tag 70 | pom.xml.releaseBackup 71 | pom.xml.versionsBackup 72 | pom.xml.next 73 | release.properties 74 | 75 | 76 | ### Eclipse ### 77 | *.pydevproject 78 | .metadata 79 | .gradle 80 | bin/ 81 | tmp/ 82 | *.tmp 83 | *.bak 84 | *.swp 85 | *~.nib 86 | local.properties 87 | .settings/ 88 | .loadpath 89 | 90 | # Eclipse Core 91 | .project 92 | 93 | # External tool builders 94 | .externalToolBuilders/ 95 | 96 | # Locally stored "Eclipse launch configurations" 97 | *.launch 98 | 99 | # CDT-specific 100 | .cproject 101 | 102 | # JDT-specific (Eclipse Java Development Tools) 103 | .classpath 104 | 105 | # PDT-specific 106 | .buildpath 107 | 108 | # sbteclipse plugin 109 | .target 110 | 111 | # TeXlipse plugin 112 | .texlipse 113 | 114 | 115 | ### Windows ### 116 | # Windows image file caches 117 | Thumbs.db 118 | ehthumbs.db 119 | 120 | # Folder config file 121 | Desktop.ini 122 | 123 | # Recycle Bin used on file shares 124 | $RECYCLE.BIN/ 125 | 126 | # Windows Installer files 127 | *.cab 128 | *.msi 129 | *.msm 130 | *.msp 131 | 132 | # Windows shortcuts 133 | *.lnk 134 | 135 | 136 | ### OSX ### 137 | .DS_Store 138 | .AppleDouble 139 | .LSOverride 140 | 141 | # Icon must end with two \r 142 | Icon 143 | 144 | 145 | # Thumbnails 146 | ._* 147 | 148 | # Files that might appear on external disk 149 | .Spotlight-V100 150 | .Trashes 151 | 152 | # Directories potentially created on remote AFP share 153 | .AppleDB 154 | .AppleDesktop 155 | Network Trash Folder 156 | Temporary Items 157 | .apdisk 158 | -------------------------------------------------------------------------------- /src/main/java/com/github/vbauer/houdini/model/ObjectConverterInfoKey.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.model; 2 | 3 | import java.util.Arrays; 4 | import java.util.Objects; 5 | 6 | /** 7 | * Class represents key information about input and output parameters for object converter. 8 | * 9 | * @param class type 10 | * @author Vladislav Bauer 11 | */ 12 | 13 | public final class ObjectConverterInfoKey { 14 | 15 | private final Class[] sources; 16 | private final Class target; 17 | 18 | 19 | public ObjectConverterInfoKey(final Class target, final Class... sources) { 20 | this.target = target; 21 | this.sources = sources; 22 | } 23 | 24 | 25 | /** 26 | * Get array with input parameter classes. 27 | * 28 | * @return source classes 29 | */ 30 | @SuppressWarnings("all") 31 | public Class[] getSources() { 32 | return sources; 33 | } 34 | 35 | /** 36 | * Get target / output class. 37 | * 38 | * @return target class 39 | */ 40 | @SuppressWarnings("all") 41 | public Class getTarget() { 42 | return target; 43 | } 44 | 45 | /** 46 | * {@inheritDoc} 47 | */ 48 | @Override 49 | public int hashCode() { 50 | return Arrays.hashCode(new Object[]{sources.length, target}); 51 | } 52 | 53 | /** 54 | * {@inheritDoc} 55 | */ 56 | @SuppressWarnings("unchecked") 57 | @Override 58 | public boolean equals(final Object obj) { 59 | if (!(obj instanceof ObjectConverterInfoKey)) { 60 | return false; 61 | } 62 | 63 | final ObjectConverterInfoKey other = (ObjectConverterInfoKey) obj; 64 | return hasSameTarget(other) && hasSameSources(other); 65 | } 66 | 67 | /** 68 | * {@inheritDoc} 69 | */ 70 | @Override 71 | public String toString() { 72 | return String.format("[%s %s]", Arrays.toString(getSources()), getTarget()); 73 | } 74 | 75 | 76 | /* 77 | * Internal API. 78 | */ 79 | 80 | private boolean hasSameTarget(final ObjectConverterInfoKey other) { 81 | return Objects.equals(getTarget(), other.getTarget()); 82 | } 83 | 84 | private boolean hasSameSources(final ObjectConverterInfoKey other) { 85 | final Class[] selfSources = getSources(); 86 | final Class[] otherSources = other.getSources(); 87 | 88 | final int selfLength = selfSources.length; 89 | final int otherLength = otherSources.length; 90 | 91 | if (selfLength != otherLength) { 92 | return false; 93 | } else if (selfLength == 0) { 94 | return true; 95 | } 96 | 97 | for (int i = 0; i < selfLength; i++) { 98 | final Class selfClass = selfSources[i]; 99 | final Class otherClass = otherSources[i]; 100 | 101 | if (!Objects.equals(selfClass, otherClass) 102 | && !Objects.equals(selfClass, Object.class) 103 | && !Objects.equals(otherClass, Object.class)) { 104 | return false; 105 | } 106 | } 107 | 108 | return true; 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/test/java/com/github/vbauer/houdini/service/impl/ReflectionUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.service.impl; 2 | 3 | import com.github.vbauer.houdini.core.BasicTest; 4 | import org.junit.Test; 5 | 6 | import java.lang.reflect.InvocationTargetException; 7 | import java.lang.reflect.UndeclaredThrowableException; 8 | 9 | import static org.hamcrest.Matchers.arrayWithSize; 10 | import static org.hamcrest.Matchers.equalTo; 11 | import static org.hamcrest.Matchers.notNullValue; 12 | import static org.hamcrest.Matchers.nullValue; 13 | import static org.junit.Assert.assertThat; 14 | 15 | /** 16 | * @author Vladislav Bauer 17 | */ 18 | 19 | public class ReflectionUtilsTest extends BasicTest { 20 | 21 | @Test 22 | public void testGetClassWithoutProxies() { 23 | assertThat(ReflectionUtils.getClassWithoutProxies(null), nullValue()); 24 | assertThat(ReflectionUtils.getClassWithoutProxies(new Object()), equalTo(Object.class)); 25 | assertThat(ReflectionUtils.getClassWithoutProxies("test"), equalTo(String.class)); 26 | } 27 | 28 | @Test 29 | public void testGetClassesWithoutProxies() { 30 | assertThat(ReflectionUtils.getClassesWithoutProxies(null), arrayWithSize(0)); 31 | assertThat(ReflectionUtils.getClassesWithoutProxies(new Object[]{}), arrayWithSize(0)); 32 | } 33 | 34 | @Test 35 | public void testConstructorContract() throws Exception { 36 | assertThat(checkUtilConstructorContract(ReflectionUtils.class), notNullValue()); 37 | } 38 | 39 | @Test(expected = IllegalStateException.class) 40 | public void testHandleReflectionException1() { 41 | ReflectionUtils.handleReflectionException(new NoSuchMethodException()); 42 | } 43 | 44 | @Test(expected = IllegalStateException.class) 45 | public void testHandleReflectionException2() { 46 | ReflectionUtils.handleReflectionException(new IllegalAccessException()); 47 | } 48 | 49 | @Test(expected = RuntimeException.class) 50 | public void testHandleReflectionException3() { 51 | ReflectionUtils.handleReflectionException(new RuntimeException()); 52 | } 53 | 54 | @Test(expected = UndeclaredThrowableException.class) 55 | public void testHandleReflectionException4() { 56 | ReflectionUtils.handleReflectionException(new Exception()); 57 | } 58 | 59 | @Test(expected = Error.class) 60 | public void testHandleReflectionException5() { 61 | ReflectionUtils.handleReflectionException(new InvocationTargetException(new Error())); 62 | } 63 | 64 | @Test(expected = RuntimeException.class) 65 | public void testRethrowRuntimeException1() { 66 | ReflectionUtils.rethrowRuntimeException(new RuntimeException()); 67 | } 68 | 69 | @Test(expected = Error.class) 70 | public void testRethrowRuntimeException2() { 71 | ReflectionUtils.rethrowRuntimeException(new Error()); 72 | } 73 | 74 | @Test(expected = UndeclaredThrowableException.class) 75 | public void testRethrowRuntimeException3() { 76 | ReflectionUtils.rethrowRuntimeException(new Exception()); 77 | } 78 | 79 | @Test(expected = Error.class) 80 | public void testHandleInvocationTargetException() { 81 | ReflectionUtils.handleInvocationTargetException( 82 | new InvocationTargetException(new Error()) 83 | ); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/com/github/vbauer/houdini/exception/ObjectConverterExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.exception; 2 | 3 | import com.github.vbauer.houdini.core.BasicTest; 4 | import com.github.vbauer.houdini.exception.base.MethodObjectConverterException; 5 | import com.github.vbauer.houdini.exception.base.ObjectConverterException; 6 | import org.junit.Test; 7 | 8 | import java.lang.reflect.Modifier; 9 | 10 | import static org.hamcrest.Matchers.equalTo; 11 | import static org.hamcrest.Matchers.notNullValue; 12 | import static org.hamcrest.collection.IsArrayContainingInAnyOrder.arrayContainingInAnyOrder; 13 | import static org.junit.Assert.assertThat; 14 | 15 | /** 16 | * @author Vladislav Bauer 17 | */ 18 | 19 | public class ObjectConverterExceptionTest extends BasicTest { 20 | 21 | @Test 22 | public void testObjectConverterException() { 23 | final Class clazz = ObjectConverterException.class; 24 | assertThat(RuntimeException.class.isAssignableFrom(clazz), equalTo(true)); 25 | assertThat(Modifier.isAbstract(clazz.getModifiers()), equalTo(true)); 26 | 27 | // Create empty exception instance. 28 | final ObjectConverterException ex = new ObjectConverterException() { 29 | private static final long serialVersionUID = 1L; 30 | }; 31 | assertThat(ex.getMessage(), notNullValue()); 32 | } 33 | 34 | @Test 35 | public void testMethodObjectConverterException() { 36 | final Class clazz = MethodObjectConverterException.class; 37 | assertThat(ObjectConverterException.class.isAssignableFrom(clazz), equalTo(true)); 38 | assertThat(Modifier.isAbstract(clazz.getModifiers()), equalTo(true)); 39 | 40 | // Create empty exception instance. 41 | final MethodObjectConverterException ex = new MethodObjectConverterException(null) { 42 | private static final long serialVersionUID = 1L; 43 | }; 44 | assertThat(ex.getMessage(), notNullValue()); 45 | } 46 | 47 | @Test(expected = DuplicatedObjectConverterException.class) 48 | public void testDuplicatedObjectConverterException() { 49 | final Class returnType = Object.class; 50 | final Class[] parametersType = new Class[] { Object.class }; 51 | final DuplicatedObjectConverterException ex = 52 | new DuplicatedObjectConverterException(returnType, parametersType); 53 | 54 | assertThat(ex.getReturnType(), equalTo((Class) returnType)); 55 | assertThat(ex.getParameterTypes(), arrayContainingInAnyOrder(parametersType)); 56 | assertThat(ex.getMessage(), notNullValue()); 57 | 58 | throw ex; 59 | } 60 | 61 | @Test(expected = MissedObjectConverterException.class) 62 | public void testMissedObjectConverterException() { 63 | final Class returnType = Object.class; 64 | final Class[] parametersType = new Class[] { Object.class }; 65 | final MissedObjectConverterException ex = 66 | new MissedObjectConverterException(returnType, parametersType); 67 | 68 | assertThat(ex.getReturnType(), equalTo((Class) returnType)); 69 | assertThat(ex.getParameterTypes(), arrayContainingInAnyOrder(parametersType)); 70 | assertThat(ex.getMessage(), notNullValue()); 71 | 72 | throw ex; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/github/vbauer/houdini/service/impl/ObjectConverterRegistryImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.service.impl; 2 | 3 | import com.github.vbauer.houdini.annotation.ObjectConverter; 4 | import com.github.vbauer.houdini.exception.DuplicatedObjectConverterException; 5 | import com.github.vbauer.houdini.exception.MissedObjectConverterException; 6 | import com.github.vbauer.houdini.model.ObjectConverterInfoKey; 7 | import com.github.vbauer.houdini.model.ObjectConverterInfoValue; 8 | import com.github.vbauer.houdini.service.ObjectConverterRegistry; 9 | 10 | import java.lang.reflect.Method; 11 | import java.lang.reflect.Modifier; 12 | import java.util.concurrent.ConcurrentHashMap; 13 | import java.util.concurrent.ConcurrentMap; 14 | 15 | /** 16 | * {@link ObjectConverterRegistry}. 17 | * 18 | * @author Vladislav Bauer 19 | */ 20 | 21 | public class ObjectConverterRegistryImpl implements ObjectConverterRegistry { 22 | 23 | private final ConcurrentMap, ObjectConverterInfoValue> converters = 24 | new ConcurrentHashMap<>(); 25 | 26 | 27 | /** 28 | * {@inheritDoc} 29 | */ 30 | @Override 31 | public void registerConverters(final Object bean) { 32 | final Class beanClass = ReflectionUtils.getClassWithoutProxies(bean); 33 | 34 | if (beanClass != null) { 35 | final Method[] methods = beanClass.getDeclaredMethods(); 36 | for (final Method method : methods) { 37 | if (isConverterMethod(beanClass, method)) { 38 | registerConverter(bean, method); 39 | } 40 | } 41 | } 42 | } 43 | 44 | /** 45 | * {@inheritDoc} 46 | */ 47 | @SuppressWarnings("unchecked") 48 | @Override 49 | public ObjectConverterInfoValue findConverter( 50 | final Class resultClass, final Object... sources 51 | ) { 52 | final Class[] sourceClasses = ReflectionUtils.getClassesWithoutProxies(sources); 53 | final ObjectConverterInfoKey key = new ObjectConverterInfoKey<>(resultClass, sourceClasses); 54 | final ObjectConverterInfoValue value = (ObjectConverterInfoValue) converters.get(key); 55 | 56 | if (value == null) { 57 | throw new MissedObjectConverterException(resultClass, sourceClasses); 58 | } 59 | 60 | return value; 61 | } 62 | 63 | 64 | /* 65 | Internal API. 66 | */ 67 | 68 | private boolean isConverterMethod(final Class beanClass, final Method method) { 69 | final boolean isDeclaredMethod = method.getDeclaringClass() == beanClass; 70 | final boolean isProxyMethod = method.isBridge() || method.isSynthetic(); 71 | final boolean hasAnnotation = method.getAnnotation(ObjectConverter.class) != null 72 | || beanClass.getAnnotation(ObjectConverter.class) != null; 73 | final boolean isPublic = Modifier.isPublic(method.getModifiers()); 74 | 75 | return !isProxyMethod && isDeclaredMethod && hasAnnotation && isPublic; 76 | } 77 | 78 | @SuppressWarnings({ "unchecked", "rawtypes" }) 79 | private void registerConverter(final Object bean, final Method method) { 80 | final Class returnType = method.getReturnType(); 81 | final Class[] parameterTypes = method.getParameterTypes(); 82 | 83 | final ObjectConverterInfoKey key = new ObjectConverterInfoKey(returnType, parameterTypes); 84 | final ObjectConverterInfoValue value = new ObjectConverterInfoValue<>(method, bean); 85 | final ObjectConverterInfoValue result = converters.putIfAbsent(key, value); 86 | 87 | if (result != null) { 88 | throw new DuplicatedObjectConverterException(returnType, parameterTypes); 89 | } 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/github/vbauer/houdini/service/impl/ReflectionUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.service.impl; 2 | 3 | import com.google.common.annotations.VisibleForTesting; 4 | 5 | import java.lang.reflect.InvocationTargetException; 6 | import java.lang.reflect.UndeclaredThrowableException; 7 | 8 | /** 9 | * Util-class which provides additional operation to work with reflection mechanism. 10 | * 11 | * @author Vladislav Bauer 12 | */ 13 | 14 | final class ReflectionUtils { 15 | 16 | private ReflectionUtils() { 17 | throw new UnsupportedOperationException(); 18 | } 19 | 20 | 21 | /** 22 | * Get class without possible proxies. It works only with Hibernate library. 23 | * Otherwise it returns the {@link Object#getClass()}. 24 | * 25 | * @param object object 26 | * @param type of the result object 27 | * @return type of the object without some proxy wrappers 28 | */ 29 | @SuppressWarnings("unchecked") 30 | @VisibleForTesting 31 | static Class getClassWithoutProxies(final T object) { 32 | try { 33 | // XXX: Use HibernateProxyHelper to un-proxy object and get the original class. 34 | return (Class) Class.forName("org.hibernate.proxy.HibernateProxyHelper") 35 | .getDeclaredMethod("getClassWithoutInitializingProxy", Object.class) 36 | .invoke(null, object); 37 | } catch (final Exception ex) { 38 | try { 39 | return (Class) object.getClass(); 40 | } catch (final Exception e) { 41 | return null; 42 | } 43 | } 44 | } 45 | 46 | /** 47 | * Get array of classes which represents un-proxy classes of the given objects. 48 | * 49 | * @param sources array with objects 50 | * @return un-proxy classes 51 | */ 52 | @VisibleForTesting 53 | static Class[] getClassesWithoutProxies(final Object[] sources) { 54 | final int size = sources == null ? 0 : sources.length; 55 | final Class[] sourceClasses = new Class[size]; 56 | 57 | for (int i = 0; i < size; i++) { 58 | final Object source = sources[i]; 59 | sourceClasses[i] = ReflectionUtils.getClassWithoutProxies(source); 60 | } 61 | 62 | return sourceClasses; 63 | } 64 | 65 | /** 66 | * Convert exception to the {@link RuntimeException}. 67 | * 68 | * @param ex exception 69 | */ 70 | @VisibleForTesting 71 | static void handleReflectionException(final Exception ex) { 72 | if (ex instanceof NoSuchMethodException) { 73 | throw new IllegalStateException("Method not found: " + ex.getMessage()); 74 | } else if (ex instanceof IllegalAccessException) { 75 | throw new IllegalStateException("Could not access method: " + ex.getMessage()); 76 | } else { 77 | if (ex instanceof InvocationTargetException) { 78 | handleInvocationTargetException((InvocationTargetException) ex); 79 | } 80 | 81 | if (ex instanceof RuntimeException) { 82 | throw (RuntimeException) ex; 83 | } else { 84 | throw new UndeclaredThrowableException(ex); 85 | } 86 | } 87 | } 88 | 89 | @VisibleForTesting 90 | static void handleInvocationTargetException(final InvocationTargetException ex) { 91 | final Throwable targetException = ex.getTargetException(); 92 | rethrowRuntimeException(targetException); 93 | } 94 | 95 | @VisibleForTesting 96 | static void rethrowRuntimeException(final Throwable ex) { 97 | if (ex instanceof RuntimeException) { 98 | throw (RuntimeException) ex; 99 | } else if (ex instanceof Error) { 100 | throw (Error) ex; 101 | } else { 102 | throw new UndeclaredThrowableException(ex); 103 | } 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/github/vbauer/houdini/service/impl/ObjectConverterServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.service.impl; 2 | 3 | import com.github.vbauer.houdini.model.ObjectConverterInfoValue; 4 | import com.github.vbauer.houdini.service.ObjectConverterRegistry; 5 | import com.github.vbauer.houdini.service.ObjectConverterService; 6 | 7 | import java.lang.reflect.Method; 8 | import java.util.ArrayList; 9 | import java.util.Collection; 10 | import java.util.HashSet; 11 | import java.util.List; 12 | import java.util.Set; 13 | 14 | /** 15 | * {@link ObjectConverterService}. 16 | * 17 | * @author Vladislav Bauer 18 | */ 19 | 20 | public class ObjectConverterServiceImpl implements ObjectConverterService { 21 | 22 | private final ObjectConverterRegistry converterRegistry; 23 | 24 | 25 | public ObjectConverterServiceImpl() { 26 | this(new ObjectConverterRegistryImpl()); 27 | } 28 | 29 | public ObjectConverterServiceImpl(final ObjectConverterRegistry converterRegistry) { 30 | this.converterRegistry = converterRegistry; 31 | } 32 | 33 | 34 | /** 35 | * {@inheritDoc} 36 | */ 37 | @Override 38 | public ObjectConverterRegistry getConverterRegistry() { 39 | return converterRegistry; 40 | } 41 | 42 | /** 43 | * {@inheritDoc} 44 | */ 45 | @Override 46 | public R convert(final Class resultClass, final Object... sources) { 47 | final ObjectConverterRegistry registry = getConverterRegistry(); 48 | final ObjectConverterInfoValue converterInfo = registry.findConverter(resultClass, sources); 49 | return processObject(converterInfo, sources); 50 | } 51 | 52 | /** 53 | * {@inheritDoc} 54 | */ 55 | @Override 56 | public Set convert(final Class resultClass, final Set sources) { 57 | return processObjects(sources, resultClass, new HashSet<>()); 58 | } 59 | 60 | /** 61 | * {@inheritDoc} 62 | */ 63 | @Override 64 | public List convert(final Class resultClass, final List sources) { 65 | return processObjects(sources, resultClass, new ArrayList<>()); 66 | } 67 | 68 | /** 69 | * {@inheritDoc} 70 | */ 71 | @Override 72 | public Object convertToOneOrList(final Class resultClass, final List sources) { 73 | return oneOrMany(processObjects(sources, resultClass, new ArrayList<>())); 74 | } 75 | 76 | /** 77 | * {@inheritDoc} 78 | */ 79 | @Override 80 | public Object convertToOneOrSet(final Class resultClass, final Set sources) { 81 | return oneOrMany(processObjects(sources, resultClass, new HashSet<>())); 82 | } 83 | 84 | 85 | /* 86 | * Internal API. 87 | */ 88 | 89 | private > C processObjects( 90 | final Collection sources, final Class resultClass, final C result 91 | ) { 92 | for (final Object source : sources) { 93 | final ObjectConverterRegistry registry = getConverterRegistry(); 94 | final ObjectConverterInfoValue converterInfo = registry.findConverter(resultClass, source); 95 | 96 | result.add(processObject(converterInfo, source)); 97 | } 98 | return result; 99 | } 100 | 101 | @SuppressWarnings("unchecked") 102 | private R processObject( 103 | final ObjectConverterInfoValue converterInfo, final Object... sources 104 | ) { 105 | final Method method = converterInfo.getMethod(); 106 | final Object object = converterInfo.getObject(); 107 | 108 | try { 109 | return (R) method.invoke(object, sources); 110 | } catch (final Exception ex) { 111 | ReflectionUtils.handleReflectionException(ex); 112 | return null; 113 | } 114 | } 115 | 116 | private Object oneOrMany(final Collection collection) { 117 | final int size = collection == null ? 0 : collection.size(); 118 | switch (size) { 119 | case 0: 120 | return null; 121 | case 1: 122 | return collection.iterator().next(); 123 | default: 124 | return collection; 125 | } 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/test/java/com/github/vbauer/houdini/core/BasicIoCTest.java: -------------------------------------------------------------------------------- 1 | package com.github.vbauer.houdini.core; 2 | 3 | import com.github.vbauer.houdini.converter.UserConverter; 4 | import com.github.vbauer.houdini.exception.DuplicatedObjectConverterException; 5 | import com.github.vbauer.houdini.exception.MissedObjectConverterException; 6 | import com.github.vbauer.houdini.model.User; 7 | import com.github.vbauer.houdini.model.UserDTO; 8 | import com.github.vbauer.houdini.service.ObjectConverterRegistry; 9 | import com.github.vbauer.houdini.service.ObjectConverterService; 10 | import com.github.vbauer.houdini.service.impl.ObjectConverterServiceImpl; 11 | import org.junit.Test; 12 | 13 | import java.util.Collections; 14 | import java.util.List; 15 | import java.util.Set; 16 | 17 | import static org.hamcrest.Matchers.equalTo; 18 | import static org.hamcrest.Matchers.hasSize; 19 | import static org.hamcrest.Matchers.notNullValue; 20 | import static org.hamcrest.Matchers.nullValue; 21 | import static org.junit.Assert.assertThat; 22 | 23 | /** 24 | * @author Vladislav Bauer 25 | */ 26 | 27 | public abstract class BasicIoCTest { 28 | 29 | private static final int ID = 1; 30 | private static final String LOGIN = "vbauer"; 31 | private static final String PASSWORD = "password"; 32 | 33 | 34 | protected ObjectConverterService converterService; 35 | protected UserConverter userConverter; 36 | 37 | 38 | @Test 39 | public void testShortUserInfo() { 40 | final User user = createUser(); 41 | final UserDTO userDTO = converterService.convert(UserDTO.class, user); 42 | 43 | assertThat(checkUserDTO(userDTO, false), notNullValue()); 44 | } 45 | 46 | @Test 47 | public void testFullUserInfo() { 48 | final User user = createUser(); 49 | final UserDTO userDTO = converterService.convert(UserDTO.class, user, true); 50 | 51 | assertThat(checkUserDTO(userDTO, true), notNullValue()); 52 | } 53 | 54 | @Test(expected = MissedObjectConverterException.class) 55 | public void testPrivateConverter() { 56 | converterService.convert(UserDTO.class, LOGIN); 57 | } 58 | 59 | @Test(expected = UnsupportedOperationException.class) 60 | public void testBadConverter() { 61 | final User user = createUser(); 62 | converterService.convert(UserDTO.class, user, true, true); 63 | } 64 | 65 | @Test 66 | public void testShortUserInfoSet() { 67 | final Set users = Collections.singleton(createUser()); 68 | final Set userDTOs = converterService.convert(UserDTO.class, users); 69 | 70 | assertThat(userDTOs, hasSize(1)); 71 | assertThat(checkUserDTO(userDTOs.iterator().next(), false), notNullValue()); 72 | } 73 | 74 | @Test 75 | public void testShortUserInfoList() { 76 | final List users = Collections.singletonList(createUser()); 77 | final List userDTOs = converterService.convert(UserDTO.class, users); 78 | 79 | assertThat(userDTOs, hasSize(1)); 80 | assertThat(checkUserDTO(userDTOs.iterator().next(), false), notNullValue()); 81 | } 82 | 83 | @Test 84 | public void testShortUserInfoOneOrSet() { 85 | final Set users = Collections.singleton(createUser()); 86 | final UserDTO userDTO = (UserDTO) converterService.convertToOneOrSet(UserDTO.class, users); 87 | 88 | assertThat(checkUserDTO(userDTO, false), notNullValue()); 89 | } 90 | 91 | @Test 92 | public void testShortUserInfoOneOrList() { 93 | final List users = Collections.singletonList(createUser()); 94 | final UserDTO userDTO = (UserDTO) converterService.convertToOneOrList(UserDTO.class, users); 95 | 96 | assertThat(checkUserDTO(userDTO, false), notNullValue()); 97 | } 98 | 99 | @Test(expected = MissedObjectConverterException.class) 100 | public void testMissedConverter() { 101 | converterService.convert(Object.class, (Object) null); 102 | } 103 | 104 | @Test(expected = MissedObjectConverterException.class) 105 | public void testNullSource() { 106 | converterService.convert(UserDTO.class, (User) null); 107 | } 108 | 109 | @Test(expected = DuplicatedObjectConverterException.class) 110 | public void testDuplicatedConverter() throws Exception { 111 | final ObjectConverterRegistry registry = converterService.getConverterRegistry(); 112 | registry.registerConverters(userConverter); 113 | } 114 | 115 | @Test 116 | public void testWithoutIoC() { 117 | final ObjectConverterService converterService = new ObjectConverterServiceImpl(); 118 | final ObjectConverterRegistry registry = converterService.getConverterRegistry(); 119 | registry.registerConverters(new UserConverter()); 120 | 121 | final User user = createUser(); 122 | final UserDTO userDTO = converterService.convert(UserDTO.class, user, true); 123 | assertThat(checkUserDTO(userDTO, true), notNullValue()); 124 | } 125 | 126 | 127 | /* 128 | * Internal API. 129 | */ 130 | 131 | private UserDTO checkUserDTO(final UserDTO userDTO, final boolean hasPassword) { 132 | assertThat(userDTO.getId(), equalTo(ID)); 133 | assertThat(userDTO.getLogin(), equalTo(LOGIN)); 134 | 135 | if (hasPassword) { 136 | assertThat(userDTO.getPassword(), equalTo(PASSWORD)); 137 | } else { 138 | assertThat(userDTO.getPassword(), nullValue()); 139 | } 140 | 141 | return userDTO; 142 | } 143 | 144 | private User createUser() { 145 | return new User() 146 | .setId(ID) 147 | .setLogin(LOGIN) 148 | .setPassword(PASSWORD); 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /src/test/resources/checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.github.vbauer 8 | houdini 9 | 1.4.2 10 | 11 | Type conversion system for Spring 12 | https://github.com/vbauer/houdini 13 | 2015 14 | 15 | 16 | 17 | Apache License, Version 2.0 18 | http://www.apache.org/licenses/LICENSE-2.0 19 | manual 20 | 21 | 22 | 23 | 24 | GitHub Issues 25 | https://github.com/vbauer/houdini/issues 26 | 27 | 28 | 29 | Travis 30 | https://travis-ci.org/vbauer/houdini 31 | 32 | 33 | 34 | 35 | vbauer 36 | Vladislav Bauer 37 | bauer.vlad@gmail.com 38 | http://linkedin.com/in/vladislavbauer 39 | 40 | architect 41 | developer 42 | 43 | 44 | 45 | 46 | 47 | 3.4.0 48 | 49 | 50 | 51 | UTF-8 52 | 1.8 53 | 54 | 5.1.0.RELEASE 55 | 2.0.4.RELEASE 56 | 4.2.1 57 | 58 | 4.12 59 | 1.3 60 | 1.2.0 61 | 62 | 3.8.0 63 | 3.0.1 64 | 2.22.0 65 | 3.0.0 66 | 3.10.0 67 | 0.8.2 68 | 4.3.0 69 | 3.0.0 70 | 3.0.1 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | org.springframework.boot 79 | spring-boot-starter 80 | ${spring.boot.version} 81 | provided 82 | true 83 | 84 | 85 | 86 | com.google.inject 87 | guice 88 | ${guice.version} 89 | provided 90 | true 91 | 92 | 93 | 94 | 95 | 96 | org.springframework 97 | spring-test 98 | ${spring.version} 99 | test 100 | 101 | 102 | 103 | junit 104 | junit 105 | ${junit.version} 106 | test 107 | 108 | 109 | 110 | org.hamcrest 111 | hamcrest-all 112 | ${hamcrest.version} 113 | test 114 | 115 | 116 | 117 | com.pushtorefresh.java-private-constructor-checker 118 | checker 119 | ${checker.version} 120 | test 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | org.apache.maven.plugins 130 | maven-compiler-plugin 131 | ${maven.compiler.plugin.version} 132 | 133 | ${java.version} 134 | ${java.version} 135 | ${project.build.sourceEncoding} 136 | true 137 | 138 | 139 | 140 | 141 | org.apache.maven.plugins 142 | maven-source-plugin 143 | ${maven.source.plugin.version} 144 | 145 | 146 | attach-sources 147 | verify 148 | 149 | jar-no-fork 150 | 151 | 152 | 153 | 154 | 155 | 156 | org.apache.maven.plugins 157 | maven-surefire-plugin 158 | ${maven.surefire.plugin.version} 159 | 160 | 161 | 162 | org.jacoco 163 | jacoco-maven-plugin 164 | ${maven.jacoco.plugin.version} 165 | 166 | 167 | prepare-agent 168 | 169 | prepare-agent 170 | 171 | 172 | 173 | 174 | 175 | 176 | org.eluder.coveralls 177 | coveralls-maven-plugin 178 | ${maven.coveralls.plugin.version} 179 | 180 | 181 | 182 | org.apache.maven.plugins 183 | maven-project-info-reports-plugin 184 | ${maven.project.info.reports.plugin.version} 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | strict 194 | 195 | 196 | 197 | 198 | org.apache.maven.plugins 199 | maven-checkstyle-plugin 200 | ${maven.checkstyle.plugin.version} 201 | 202 | true 203 | src/test/resources/checkstyle.xml 204 | false 205 | 206 | 207 | 208 | package 209 | 210 | check 211 | 212 | 213 | 214 | 215 | 216 | 217 | org.apache.maven.plugins 218 | maven-pmd-plugin 219 | ${maven.pmd.plugin.version} 220 | 221 | true 222 | true 223 | false 224 | false 225 | 226 | 227 | 228 | package 229 | 230 | check 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | org.apache.maven.plugins 247 | maven-javadoc-plugin 248 | ${maven.javadoc.plugin.version} 249 | 250 | 251 | 252 | org.apache.maven.plugins 253 | maven-surefire-report-plugin 254 | ${maven.surefire.plugin.version} 255 | 256 | 257 | 258 | 259 | 260 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Houdini 3 | 4 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Houdini-brightgreen.svg?style=flat)](http://android-arsenal.com/details/1/1979) 5 | [![Build Status](https://travis-ci.org/vbauer/houdini.svg)](https://travis-ci.org/vbauer/houdini) 6 | [![Coverage Status](https://coveralls.io/repos/vbauer/houdini/badge.svg?branch=master)](https://coveralls.io/r/vbauer/houdini?branch=master) 7 | [![Maven](https://img.shields.io/github/tag/vbauer/houdini.svg?label=maven)](https://jitpack.io/#vbauer/houdini) 8 | [![Codacy Badge](https://api.codacy.com/project/badge/grade/ab45dbcdf6474b98aaeccb15aab4980d)](https://www.codacy.com/app/bauer-vlad/houdini) 9 | 10 | > No performer should attempt to bite off red-hot iron unless he has a good set of teeth. 11 | 12 | 13 | 14 | **Houdini** is a simple and humane type conversion system, which allows you to prevent a lot of unnecessary code. 15 | 16 | For example, when you work with [Spring Type Conversion](http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#core-convert) API, 17 | you have to implement each converter as a separate class. It produces a lot of excessive code. 18 | Houdini allows you to aggregate different converters in a single place and re-use common logic without additional classes. 19 | 20 | *The project was named in honor of Harry Houdini (born Erik Weisz, later Ehrich Weiss or Harry Weiss) who was a Hungarian-American illusionist and stunt performer, noted for his sensational escape acts.* 21 | 22 | See an *[Example](https://github.com/vbauer/houdini#example)* section for a quick start. 23 | 24 | **Online documentation:** 25 | 26 | * [Maven site](https://vbauer.github.io/houdini) 27 | * [Javadoc](https://vbauer.github.io/houdini/apidocs) 28 | 29 | 30 | ## Main features 31 | 32 | * Ready-To-Use solution 33 | * Compact and very simple API 34 | * Completely re-usable components 35 | * Direct usage of converters if necessary 36 | * Preventing unnecessary code 37 | * Compatible with: 38 | * Java SE/EE 39 | * [Spring Framework](https://spring.io) 40 | * [Spring Boot](http://projects.spring.io/spring-boot/) 41 | * [Guice](https://github.com/google/guice) 42 | * [RoboGuice](https://github.com/roboguice/roboguice) 43 | * [Android Platform](http://developer.android.com) 44 | 45 | 46 | ## Comparison with other conversion systems 47 | 48 | Usually, each big project with some conversion system (ex. Spring's Converters) has the following problems: 49 | 50 | **A huge number of converter classes**
51 | Houdini allows to join several converters into a single bean (unlike Spring converters), so it will minimize the 52 | number of classes and prevent unnecessary code. For example, you can join converters by functionality or by modules. 53 | It also simplifies source code navigation for developer. 54 | An additional feature is that the smaller class number of classes also decreases compilation time a little bit. 55 | 56 | **Duplicated code in converters**
57 | Some converters could be very similar to other (except filling of some fields). 58 | Using Spring it is most likely to see basic classes or shared components to resolve this situation. 59 | Using Houdini it is unnecessary to create additional classes, you can just put this logic in a new method. 60 | 61 | **Conditional converters**
62 | It is a typical situation for REST services: 63 | * sometimes it is necessary to send full information, 64 | * sometimes short information, 65 | * or need to combine it somehow using conditionals. 66 | 67 | Using Spring converters you need to create new POJO and new converters to resolve it. 68 | Houdini allows to use additional conditional parameters. (See an Example section). 69 | 70 | **Out of IOC context**
71 | This is a rare case for JEE developers (and typical case for others), but sometimes it's necessary to use some 72 | converters without something like [ConversionService](http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/convert/ConversionService.html). 73 | It could be needed when our code is out of Spring context (ex: shared code for Spring and [GWT](http://www.gwtproject.org) apps). 74 | Using Houdini we could put all needed converters into a single bean and use it like a simple Java class. 75 | 76 | 77 | ## Setup 78 | 79 | ### Gradle 80 | ```groovy 81 | repositories { 82 | maven { 83 | url "https://jitpack.io" 84 | } 85 | } 86 | dependencies { 87 | compile 'com.github.vbauer:houdini:1.4.2' 88 | } 89 | ``` 90 | 91 | ### Maven 92 | ```xml 93 | 94 | jitpack.io 95 | https://jitpack.io 96 | 97 | 98 | 99 | com.github.vbauer 100 | houdini 101 | 1.4.2 102 | 103 | ``` 104 | 105 | 106 | ## Configuration 107 | 108 | To work with Houdini's magic, you have 2 basic interfaces and corresponding implementations: 109 | * ObjectConverterRegistry (ObjectConverterRegistryImpl) is needed to store and search converters. 110 | * ObjectConverterService (ObjectConverterServiceImpl) allows to convert some data to another data. 111 | 112 | **Reminder:** 113 | 114 | Houdini isn't dependent on third-party dependencies, but it has extra integration with Spring and Guice frameworks. 115 | Of course, it can be used with pure Java or Android projects. 116 | 117 | ### Java / Android configuration 118 | 119 | How to configure Houdini with pure Java: 120 | 121 | ```java 122 | // Create registry service to store converters: 123 | final ObjectConverterRegistry registry = new ObjectConverterRegistryImpl(); 124 | 125 | // After that you can specify all needed converters: 126 | // registry.registerConverters(new UserConverter()); 127 | // registry.registerConverters(new RoleConverter()); 128 | // registry.registerConverters(new CompanyConverter()); 129 | 130 | // Create service which makes conversions (it could be a simple singleton object): 131 | final ObjectConverterService converterService = new ObjectConverterServiceImpl(registry); 132 | ``` 133 | 134 | You can also use default registry object with `ObjectConverterService`, *1LOC*: 135 | ```java 136 | final ObjectConverterService converterService = new ObjectConverterServiceImpl(); 137 | ``` 138 | 139 | ### Guice / RoboGuice configuration 140 | 141 | **Houdini** contains the specific Guice module to support converters (`com.github.vbauer.houdini.ext.guice.ObjectConverterModule`): 142 | 143 | ```java 144 | final Injector injector = Guice.createInjector(new ObjectConverterModule()); 145 | ``` 146 | 147 | Now, all your beans will be checked for converters and all found converters will be registered. 148 | `ObjectConverterModule` module also defines 2 beans: `ObjectConverterRegistry` and `ObjectConverterService`. 149 | 150 | ### Spring configuration 151 | 152 | **Houdini** has a good integration with Spring framework. Just choose the most appropriate way how to configure it: 153 | 154 | * Configure application context using Java code 155 | * Use XML file to configure context 156 | 157 | #### Java based configuration 158 | 159 | You need to configure 2 beans: 160 | * `ObjectConverterService` which you will use to convert objects 161 | * and `ObjectConverterBeanPostProcessor` which is necessary to detect converters in the Spring context 162 | 163 | ```java 164 | @Configuration 165 | public class AppContext { 166 | @Bean 167 | public ObjectConverterService objectConverterService() { 168 | return new ObjectConverterServiceImpl(); 169 | } 170 | @Bean 171 | public ObjectConverterBeanPostProcessor objectConverterBeanPostProcessor() { 172 | return new ObjectConverterBeanPostProcessor(objectConverterService()); 173 | } 174 | } 175 | ``` 176 | 177 | You also need to define converter beans in the `AppContext` or using `@ComponentScan` annotation. 178 | 179 | #### XML Schema-based configuration 180 | 181 | You still need to configure the same 2 beans: 182 | 183 | ```xml 184 | 185 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | ``` 198 | 199 | #### Spring Boot support 200 | 201 | Houdini has out of the box integration with Spring Boot. You do not need to define `ObjectConverterService` and `ObjectConverterBeanPostProcessor` in your application context. 202 | Spring Boot [auto-configuration](https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-auto-configuration.html) attempts to automatically configure your Spring application based on the jar dependencies that you have added. 203 | 204 | See [ObjectConverterConfiguration](src/main/java/com/github/vbauer/houdini/ext/spring/ObjectConverterConfiguration.java) for more details. 205 | 206 | 207 | ## Conversion API 208 | 209 | To make new converter, you need to create a new class (or use an existing one) and mark this bean as converter using `@ObjectConverter` annotation. 210 | 211 | It is possible to add this annotation on: 212 | * **Bean class** - all public methods from this class will be registered as converters. 213 | * **Bean's public method** - only this method will be registered as converter. 214 | 215 | All necessary converters will be registered in bean `ObjectConverterService`. 216 | This service also provides all necessary methods for data conversion: 217 | 218 | * Register converter methods. 219 | * Convert some data object (with parameters) to another data object. 220 | * Convert `Set` or `List` of data to `Set`/`List` of data. 221 | * Convert `Set`/`List` of data to one element or `Set`/`List` (it could be useful for some REST API services). 222 | 223 | That's all! 224 | 225 | 226 | ## Example 227 | 228 | This tiny example shows the power of Houdini: one class, two converters, re-usable logic, no one line of excessive code. 229 | 230 | ```java 231 | // Use also @Component annotation in case of Spring framework. 232 | @ObjectConverter 233 | public class UserConverter { 234 | 235 | public UserDTO shortInfo(final User user) { 236 | return fullInfo(user, false); 237 | } 238 | 239 | public UserDTO fullInfo(final User user, Boolean full) { 240 | final UserDTO userDTO = new UserDTO() 241 | .setId(user.getId()) 242 | .setLogin(user.getLogin()); 243 | 244 | if (Boolean.TRUE.equals(full)) { 245 | userDTO.setPassword(user.getPassword()); 246 | } 247 | 248 | return userDTO; 249 | } 250 | 251 | } 252 | ``` 253 | 254 | Converting User domain object to DTO: 255 | ```java 256 | final User user = new User() 257 | .setId(ID) 258 | .setLogin(LOGIN) 259 | .setPassword(PASSWORD) 260 | 261 | final UserDTO shortUserDTO1 = converterService.convert(UserDTO.class, user); 262 | final UserDTO shortUserDTO2 = converterService.convert(UserDTO.class, user, false); 263 | final UserDTO fullUserDTO = converterService.convert(UserDTO.class, user, true); 264 | ``` 265 | 266 | 267 | ## Development 268 | 269 | To build project in strict mode with tests, you can run: 270 | 271 | ```bash 272 | mvn -P strict clean package 273 | ``` 274 | 275 | 276 | ## Might also like 277 | 278 | * [jconditions](https://github.com/vbauer/jconditions) - Extra conditional annotations for JUnit. 279 | * [jackdaw](https://github.com/vbauer/jackdaw) - Java Annotation Processor which allows to simplify development. 280 | * [herald](https://github.com/vbauer/herald) - Logging annotation for Spring framework. 281 | * [caesar](https://github.com/vbauer/caesar) - Library that allows to create async beans from sync beans. 282 | * [commons-vfs2-cifs](https://github.com/vbauer/commons-vfs2-cifs) - SMB/CIFS provider for Commons VFS. 283 | * [avconv4java](https://github.com/vbauer/avconv4java) - Java interface to avconv tool. 284 | 285 | 286 | ## License 287 | 288 | Copyright 2015 Vladislav Bauer 289 | 290 | Licensed under the Apache License, Version 2.0 (the "License"); 291 | you may not use this file except in compliance with the License. 292 | You may obtain a copy of the License at 293 | 294 | http://www.apache.org/licenses/LICENSE-2.0 295 | 296 | Unless required by applicable law or agreed to in writing, software 297 | distributed under the License is distributed on an "AS IS" BASIS, 298 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 299 | See the License for the specific language governing permissions and 300 | limitations under the License. 301 | --------------------------------------------------------------------------------