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
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package pl.jsolve.oven.annotationdriven;

import pl.jsolve.oven.annotationdriven.annotation.Alias;
import pl.jsolve.oven.annotationdriven.annotation.MappableTo;
import pl.jsolve.oven.annotationdriven.annotation.MappableToAlias;
import pl.jsolve.oven.annotationdriven.exception.MappingException;
import pl.jsolve.sweetener.core.Reflections;
import pl.jsolve.typeconverter.TypeConverter;
Expand Down Expand Up @@ -50,7 +52,15 @@ public static <T, V> boolean isMappableToTargetClass(T object, Class<V> targetCl
return false;
}
MappableTo mappableTo = object.getClass().getAnnotation(MappableTo.class);
return mappableTo != null && Arrays.asList(mappableTo.value()).contains(targetClass);
MappableToAlias mappableToAlias = object.getClass().getAnnotation(MappableToAlias.class);

boolean mappable = mappableToAlias != null &&
targetClass.getAnnotation(Alias.class) != null &&
Arrays.asList(mappableToAlias.value()).contains(targetClass.getAnnotation(Alias.class).value());

mappable = mappable || (mappableTo != null && Arrays.asList(mappableTo.value()).contains(targetClass)) ;

return mappable;
}

private static <V, T> void applyAllMappings(T sourceObject, V targetObject) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
package pl.jsolve.oven.annotationdriven;

import pl.jsolve.oven.annotationdriven.annotation.Map;
import pl.jsolve.oven.annotationdriven.annotation.Mappings;
import pl.jsolve.oven.annotationdriven.annotation.*;
import pl.jsolve.oven.annotationdriven.exception.MappingException;
import pl.jsolve.oven.builder.MapperBuilder;
import pl.jsolve.sweetener.collection.Collections;
import pl.jsolve.sweetener.core.Reflections;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;

class MapAnnotationMapping implements AnnotationMapping {

private static final String NESTING_CHARACTER = ".";
private static final Class<Map> MAP_ANNOTATION_CLASS = Map.class;
private static final Class<MapToAlias> MAP_TO_ALIAS_ANNOTATION_CLASS = MapToAlias.class;
private static final Class<Mappings> MAPPINGS_ANNOTATION_CLASS = Mappings.class;
private static final Class<MappingsForAliases> MAPPINGS_FOR_ALIASES_ANNOTATION_CLASS = MappingsForAliases.class;
private final AnnotationProvider annotationProvider = new AnnotationProvider();

@Override
Expand All @@ -24,11 +26,16 @@ public <S, T> void apply(S sourceObject, T targetObject) {
}

private <S, T> void applyOnFieldsAnnotatedByMap(S sourceObject, T targetObject) {
List<AnnotatedField> annotatedFields = annotationProvider.getFieldsAnnotatedBy(sourceObject, MAP_ANNOTATION_CLASS);
for (AnnotatedField field : annotatedFields) {
List<AnnotatedField> annotatedFieldsForMapAlias = annotationProvider.getFieldsAnnotatedBy(sourceObject, MAP_TO_ALIAS_ANNOTATION_CLASS);
List<AnnotatedField> annotatedFieldsForMap = annotationProvider.getFieldsAnnotatedBy(sourceObject, MAP_ANNOTATION_CLASS);
for (AnnotatedField field : annotatedFieldsForMap) {
Map mapAnnotation = (Map) field.getAnnotation();
applyOnFieldWithAnnotation(sourceObject, targetObject, field.get(), mapAnnotation);
}
for (AnnotatedField field : annotatedFieldsForMapAlias) {
MapToAlias mapAnnotation = (MapToAlias) field.getAnnotation();
applyOnFieldWithAnnotation(sourceObject, targetObject, field.get(), mapAnnotation);
}
}

private <S, T> void applyOnFieldsAnnotatedByMappings(S sourceObject, T targetObject) {
Expand All @@ -37,6 +44,11 @@ private <S, T> void applyOnFieldsAnnotatedByMappings(S sourceObject, T targetObj
Map[] mapAnnotations = ((Mappings) field.getAnnotation()).value();
applyOnFieldWithAnnotations(sourceObject, targetObject, field.get(), mapAnnotations);
}
annotatedFields = annotationProvider.getFieldsAnnotatedBy(sourceObject, MAPPINGS_FOR_ALIASES_ANNOTATION_CLASS);
for (AnnotatedField field : annotatedFields) {
MapToAlias[] mapAnnotations = ((MappingsForAliases) field.getAnnotation()).value();
applyOnFieldWithAnnotations(sourceObject, targetObject, field.get(), mapAnnotations);
}
}

private <S, T> void applyOnFieldWithAnnotations(S sourceObject, T targetObject, Field field, Map... mapAnnotations) {
Expand All @@ -45,6 +57,12 @@ private <S, T> void applyOnFieldWithAnnotations(S sourceObject, T targetObject,
}
}

private <S, T> void applyOnFieldWithAnnotations(S sourceObject, T targetObject, Field field, MapToAlias... mapAnnotations) {
for (MapToAlias mapAnnotation : mapAnnotations) {
applyOnFieldWithAnnotation(sourceObject, targetObject, field, mapAnnotation);
}
}

private <S, T> void applyOnFieldWithAnnotation(S sourceObject, T targetObject, Field field, Map mapAnnotation) {
if (isMappingIntendedForTargetObject(targetObject, mapAnnotation)) {
String targetFieldName = getTargetFieldName(field, mapAnnotation);
Expand All @@ -61,24 +79,62 @@ private <S, T> void applyOnFieldWithAnnotation(S sourceObject, T targetObject, F
}
}

private <S, T> void applyOnFieldWithAnnotation(S sourceObject, T targetObject, Field field, MapToAlias mapAnnotation) {
if (isMappingIntendedForTargetObject(targetObject, mapAnnotation)) {
String targetFieldName = getTargetFieldName(field, mapAnnotation);
throwExceptionWhenFieldIsNotPresent(targetObject, targetFieldName);

String sourceFieldName = getSourceFieldName(field, mapAnnotation);
throwExceptionWhenFieldIsNotPresent(sourceObject, sourceFieldName);

Class<?> targetFieldType = Reflections.getFieldType(targetObject, targetFieldName);
Object sourceFieldValue = Reflections.getFieldValue(sourceObject, sourceFieldName);

sourceFieldValue = mapObjectToTargetType(sourceFieldValue, targetFieldType, mapAnnotation);
Reflections.setFieldValue(targetObject, targetFieldName, sourceFieldValue);
}
}

private <T> boolean isMappingIntendedForTargetObject(T targetObject, Map mapAnnotation) {
return Collections.containsAny(Reflections.getClasses(targetObject), mapAnnotation.of());
}

private <T> boolean isMappingIntendedForTargetObject(T targetObject, MapToAlias mapAnnotation) {
if (targetObject.getClass().getAnnotation(Alias.class) == null) {
return false;
}

return Arrays.asList(mapAnnotation.of()).contains(targetObject.getClass().getAnnotation(Alias.class).value());
}

private String getTargetFieldName(Field field, Map mapAnnotation) {
if (mapAnnotation.to().isEmpty()) {
return field.getName();
}
return mapAnnotation.to();
}

private String getTargetFieldName(Field field, MapToAlias mapAnnotation) {
if (mapAnnotation.to().isEmpty()) {
return field.getName();
}
return mapAnnotation.to();
}

private String getSourceFieldName(Field field, Map mapAnnotation) {
if (mapAnnotation.fromNested().isEmpty()) {
return field.getName();
}
return field.getName() + NESTING_CHARACTER + mapAnnotation.fromNested();
}

private String getSourceFieldName(Field field, MapToAlias mapAnnotation) {
if (mapAnnotation.fromNested().isEmpty()) {
return field.getName();
}
return field.getName() + NESTING_CHARACTER + mapAnnotation.fromNested();
}

private void throwExceptionWhenFieldIsNotPresent(Object object, String fieldName) {
if (!Reflections.isFieldPresent(object, fieldName)) {
throw new MappingException("%s does not contain field '%s'. Perhaps you have misspelled field name in @Map annotation?",
Expand All @@ -95,4 +151,14 @@ private Object mapObjectToTargetType(Object object, Class<?> targetType, Map map
.usingTypeConvertion()
.map(object);
}

private Object mapObjectToTargetType(Object object, Class<?> targetType, MapToAlias mapAnnotation) {
return MapperBuilder.toType(targetType)
.arrayElementsTo(mapAnnotation.elementsAs())
.collectionElementsTo(mapAnnotation.elementsAs())
.mapKeysAndValuesTo(mapAnnotation.keysAs(), mapAnnotation.valuesAs())
.usingAnnotations()
.usingTypeConvertion()
.map(object);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package pl.jsolve.oven.annotationdriven.annotation;

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

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
public @interface Alias {
public String value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Map {
public @interface Map {

String fromNested() default "";

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package pl.jsolve.oven.annotationdriven.annotation;

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MapToAlias {

String fromNested() default "";

String to() default "";

String[] of() default "";

Class<?> keysAs() default Object.class;

Class<?> valuesAs() default Object.class;

Class<?> elementsAs() default Object.class;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package pl.jsolve.oven.annotationdriven.annotation;

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


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MappableToAlias {

public String[] value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package pl.jsolve.oven.annotationdriven.annotation;

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MappingsForAliases {
MapToAlias[] value();

}
36 changes: 18 additions & 18 deletions src/test/java/pl/jsolve/oven/ComplexMapperTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import org.junit.Test;
import pl.jsolve.oven.complex.ComplexMapper;
import pl.jsolve.oven.complex.MappingStrategy;
import pl.jsolve.oven.stub.hero.Hero;
import pl.jsolve.oven.stub.hero.HeroSnapshot;
import pl.jsolve.oven.stub.hero.HeroWithAlias;
import pl.jsolve.oven.stub.hero.HeroSnapshotWithAlias;

import static org.fest.assertions.Assertions.assertThat;
import static pl.jsolve.oven.stub.hero.HeroProfiledBuilder.aCaptainAmerica;
Expand All @@ -18,18 +18,18 @@ public class ComplexMapperTest {
@Test
public void shouldMapHeroToHeroSnapshot() {
// given
Hero captainAmerica = aCaptainAmerica().withId(ID).build();
ComplexMapper<Hero, HeroSnapshot> heroToHeroSnapshotMapper = new ComplexMapper<Hero, HeroSnapshot>(new MappingStrategy<Hero, HeroSnapshot>() {
HeroWithAlias captainAmerica = aCaptainAmerica().withId(ID).build();
ComplexMapper<HeroWithAlias, HeroSnapshotWithAlias> heroToHeroSnapshotMapper = new ComplexMapper<HeroWithAlias, HeroSnapshotWithAlias>(new MappingStrategy<HeroWithAlias, HeroSnapshotWithAlias>() {

@Override
public HeroSnapshot map(Hero source, HeroSnapshot target) {
public HeroSnapshotWithAlias map(HeroWithAlias source, HeroSnapshotWithAlias target) {
target.setName(source.getFirstName() + SPACE + source.getLastName());
return target;
}
});

// when
HeroSnapshot result = heroToHeroSnapshotMapper.map(captainAmerica);
HeroSnapshotWithAlias result = heroToHeroSnapshotMapper.map(captainAmerica);

// then
assertThat(result.getId()).as("id field has mapping annotations").isEqualTo(captainAmerica.getId());
Expand All @@ -39,16 +39,16 @@ public HeroSnapshot map(Hero source, HeroSnapshot target) {
@Test
public void shouldMapHeroToHeroSnapshotWithComplexIdMapping() {
// given
Hero captainAmerica = aCaptainAmerica().withId(ID).build();
ComplexMapper<Hero, HeroSnapshot> heroToHeroSnapshotMapper = new ComplexMapper<Hero, HeroSnapshot>(new MappingStrategy<Hero, HeroSnapshot>() {
HeroWithAlias captainAmerica = aCaptainAmerica().withId(ID).build();
ComplexMapper<HeroWithAlias, HeroSnapshotWithAlias> heroToHeroSnapshotMapper = new ComplexMapper<HeroWithAlias, HeroSnapshotWithAlias>(new MappingStrategy<HeroWithAlias, HeroSnapshotWithAlias>() {
@Override
public HeroSnapshot map(Hero source, HeroSnapshot target) {
public HeroSnapshotWithAlias map(HeroWithAlias source, HeroSnapshotWithAlias target) {
target.setId(source.getId() + ANY_NUMBER);
return target;
}
});
// when
HeroSnapshot result = heroToHeroSnapshotMapper.map(captainAmerica);
HeroSnapshotWithAlias result = heroToHeroSnapshotMapper.map(captainAmerica);

// then
assertThat(result.getId()).isEqualTo(captainAmerica.getId() + ANY_NUMBER);
Expand All @@ -57,24 +57,24 @@ public HeroSnapshot map(Hero source, HeroSnapshot target) {
@Test
public void shouldMapHeroSnapshotToHero() {
// given
HeroSnapshot heroSnapshot = new HeroSnapshot();
heroSnapshot.setId(ID);
heroSnapshot.setName("Johann Schmidt");
ComplexMapper<HeroSnapshot, Hero> heroSnapshotToHeroMapper = new ComplexMapper<HeroSnapshot, Hero>(new MappingStrategy<HeroSnapshot, Hero>() {
HeroSnapshotWithAlias heroSnapshotWithAlias = new HeroSnapshotWithAlias();
heroSnapshotWithAlias.setId(ID);
heroSnapshotWithAlias.setName("Johann Schmidt");
ComplexMapper<HeroSnapshotWithAlias, HeroWithAlias> heroSnapshotToHeroMapper = new ComplexMapper<HeroSnapshotWithAlias, HeroWithAlias>(new MappingStrategy<HeroSnapshotWithAlias, HeroWithAlias>() {

@Override
public Hero map(HeroSnapshot source, Hero target) {
public HeroWithAlias map(HeroSnapshotWithAlias source, HeroWithAlias target) {
target.setFirstName(source.getName().split(SPACE)[0]);
target.setLastName(source.getName().split(SPACE)[1]);
return target;
}
});

// when
Hero result = heroSnapshotToHeroMapper.map(heroSnapshot);
HeroWithAlias result = heroSnapshotToHeroMapper.map(heroSnapshotWithAlias);

// then
assertThat(result.getId()).as("id field has mapping annotations").isEqualTo(heroSnapshot.getId());
assertThat(result.getFirstName() + SPACE + result.getLastName()).isEqualTo(heroSnapshot.getName());
assertThat(result.getId()).as("id field has mapping annotations").isEqualTo(heroSnapshotWithAlias.getId());
assertThat(result.getFirstName() + SPACE + result.getLastName()).isEqualTo(heroSnapshotWithAlias.getName());
}
}
6 changes: 4 additions & 2 deletions src/test/java/pl/jsolve/oven/collection/stub/Exam.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package pl.jsolve.oven.collection.stub;

import pl.jsolve.oven.annotationdriven.annotation.Alias;
import pl.jsolve.oven.annotationdriven.annotation.Map;
import pl.jsolve.oven.annotationdriven.annotation.MappableTo;
import pl.jsolve.oven.annotationdriven.annotation.MappableToAlias;

@MappableTo(ExamSnapshot.class)
@Alias("Exam")
@MappableToAlias("ExamSnapshot")
public class Exam {

@Map
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package pl.jsolve.oven.collection.stub;

import pl.jsolve.oven.annotationdriven.annotation.Alias;
import pl.jsolve.oven.annotationdriven.annotation.Map;
import pl.jsolve.oven.annotationdriven.annotation.MappableTo;
import pl.jsolve.oven.annotationdriven.annotation.MappableToAlias;

@MappableTo(Exam.class)
@Alias("ExamSnapshot")
@MappableToAlias("Exam")
public class ExamSnapshot {

@Map
Expand Down
Loading