From 73f660ac429c30135aaeaed20920b4a34a304188 Mon Sep 17 00:00:00 2001 From: Wojciech Milczarek Date: Wed, 5 Aug 2015 11:52:40 +0200 Subject: [PATCH 1/3] #9 @MappableToAlias, @Alias - with additional unit tests --- .../AnnotationDrivenMapper.java | 10 ++- .../annotationdriven/annotation/Alias.java | 12 ++++ .../annotation/MappableToAlias.java | 14 ++++ .../pl/jsolve/oven/ComplexMapperTest.java | 36 +++++----- .../pl/jsolve/oven/collection/stub/Exam.java | 6 +- .../oven/collection/stub/ExamSnapshot.java | 5 +- .../simple/AnnotationDrivenMapperTest.java | 70 ++++++++++++++++--- .../oven/stub/hero/HeroDTOWithAlias.java | 18 +++++ .../oven/stub/hero/HeroProfiledBuilder.java | 12 ++-- .../oven/stub/hero/HeroSnapshotWithAlias.java | 30 ++++++++ .../jsolve/oven/stub/hero/HeroWithAlias.java | 56 +++++++++++++++ .../stub/hero/HeroWithAliasesBuilder.java | 30 ++++++++ .../pl/jsolve/oven/stub/person/Person.java | 2 + 13 files changed, 264 insertions(+), 37 deletions(-) create mode 100644 src/main/java/pl/jsolve/oven/annotationdriven/annotation/Alias.java create mode 100644 src/main/java/pl/jsolve/oven/annotationdriven/annotation/MappableToAlias.java create mode 100644 src/test/java/pl/jsolve/oven/stub/hero/HeroDTOWithAlias.java create mode 100644 src/test/java/pl/jsolve/oven/stub/hero/HeroSnapshotWithAlias.java create mode 100644 src/test/java/pl/jsolve/oven/stub/hero/HeroWithAlias.java create mode 100644 src/test/java/pl/jsolve/oven/stub/hero/HeroWithAliasesBuilder.java diff --git a/src/main/java/pl/jsolve/oven/annotationdriven/AnnotationDrivenMapper.java b/src/main/java/pl/jsolve/oven/annotationdriven/AnnotationDrivenMapper.java index 0fd445e..9470ff0 100644 --- a/src/main/java/pl/jsolve/oven/annotationdriven/AnnotationDrivenMapper.java +++ b/src/main/java/pl/jsolve/oven/annotationdriven/AnnotationDrivenMapper.java @@ -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; @@ -50,7 +52,13 @@ public static boolean isMappableToTargetClass(T object, Class 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 && Arrays.asList(mappableToAlias.value()).contains(targetClass.getAnnotation(Alias.class).value()); + mappable = mappable || (mappableTo != null && Arrays.asList(mappableTo.value()).contains(targetClass)) ; + + return mappable; } private static void applyAllMappings(T sourceObject, V targetObject) { diff --git a/src/main/java/pl/jsolve/oven/annotationdriven/annotation/Alias.java b/src/main/java/pl/jsolve/oven/annotationdriven/annotation/Alias.java new file mode 100644 index 0000000..b94955f --- /dev/null +++ b/src/main/java/pl/jsolve/oven/annotationdriven/annotation/Alias.java @@ -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) +public @interface Alias { + public String value(); +} diff --git a/src/main/java/pl/jsolve/oven/annotationdriven/annotation/MappableToAlias.java b/src/main/java/pl/jsolve/oven/annotationdriven/annotation/MappableToAlias.java new file mode 100644 index 0000000..95cf05d --- /dev/null +++ b/src/main/java/pl/jsolve/oven/annotationdriven/annotation/MappableToAlias.java @@ -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(); +} diff --git a/src/test/java/pl/jsolve/oven/ComplexMapperTest.java b/src/test/java/pl/jsolve/oven/ComplexMapperTest.java index 8dc47b3..a40b81d 100644 --- a/src/test/java/pl/jsolve/oven/ComplexMapperTest.java +++ b/src/test/java/pl/jsolve/oven/ComplexMapperTest.java @@ -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; @@ -18,18 +18,18 @@ public class ComplexMapperTest { @Test public void shouldMapHeroToHeroSnapshot() { // given - Hero captainAmerica = aCaptainAmerica().withId(ID).build(); - ComplexMapper heroToHeroSnapshotMapper = new ComplexMapper(new MappingStrategy() { + HeroWithAlias captainAmerica = aCaptainAmerica().withId(ID).build(); + ComplexMapper heroToHeroSnapshotMapper = new ComplexMapper(new MappingStrategy() { @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()); @@ -39,16 +39,16 @@ public HeroSnapshot map(Hero source, HeroSnapshot target) { @Test public void shouldMapHeroToHeroSnapshotWithComplexIdMapping() { // given - Hero captainAmerica = aCaptainAmerica().withId(ID).build(); - ComplexMapper heroToHeroSnapshotMapper = new ComplexMapper(new MappingStrategy() { + HeroWithAlias captainAmerica = aCaptainAmerica().withId(ID).build(); + ComplexMapper heroToHeroSnapshotMapper = new ComplexMapper(new MappingStrategy() { @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); @@ -57,13 +57,13 @@ public HeroSnapshot map(Hero source, HeroSnapshot target) { @Test public void shouldMapHeroSnapshotToHero() { // given - HeroSnapshot heroSnapshot = new HeroSnapshot(); - heroSnapshot.setId(ID); - heroSnapshot.setName("Johann Schmidt"); - ComplexMapper heroSnapshotToHeroMapper = new ComplexMapper(new MappingStrategy() { + HeroSnapshotWithAlias heroSnapshotWithAlias = new HeroSnapshotWithAlias(); + heroSnapshotWithAlias.setId(ID); + heroSnapshotWithAlias.setName("Johann Schmidt"); + ComplexMapper heroSnapshotToHeroMapper = new ComplexMapper(new MappingStrategy() { @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; @@ -71,10 +71,10 @@ public Hero map(HeroSnapshot source, Hero 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()); } } \ No newline at end of file diff --git a/src/test/java/pl/jsolve/oven/collection/stub/Exam.java b/src/test/java/pl/jsolve/oven/collection/stub/Exam.java index 3161cbc..c71f757 100644 --- a/src/test/java/pl/jsolve/oven/collection/stub/Exam.java +++ b/src/test/java/pl/jsolve/oven/collection/stub/Exam.java @@ -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 diff --git a/src/test/java/pl/jsolve/oven/collection/stub/ExamSnapshot.java b/src/test/java/pl/jsolve/oven/collection/stub/ExamSnapshot.java index b9c9751..23acb51 100644 --- a/src/test/java/pl/jsolve/oven/collection/stub/ExamSnapshot.java +++ b/src/test/java/pl/jsolve/oven/collection/stub/ExamSnapshot.java @@ -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 diff --git a/src/test/java/pl/jsolve/oven/simple/AnnotationDrivenMapperTest.java b/src/test/java/pl/jsolve/oven/simple/AnnotationDrivenMapperTest.java index b039543..fc93129 100644 --- a/src/test/java/pl/jsolve/oven/simple/AnnotationDrivenMapperTest.java +++ b/src/test/java/pl/jsolve/oven/simple/AnnotationDrivenMapperTest.java @@ -5,10 +5,7 @@ import pl.jsolve.oven.annotationdriven.AnnotationMapping; import pl.jsolve.oven.annotationdriven.exception.MappingException; import pl.jsolve.oven.simple.stub.*; -import pl.jsolve.oven.stub.hero.Hero; -import pl.jsolve.oven.stub.hero.HeroBuilder; -import pl.jsolve.oven.stub.hero.HeroDTO; -import pl.jsolve.oven.stub.hero.HeroSnapshot; +import pl.jsolve.oven.stub.hero.*; import pl.jsolve.oven.stub.person.Address; import pl.jsolve.oven.stub.person.City; import pl.jsolve.oven.stub.person.FieldOfStudy; @@ -25,7 +22,7 @@ import static pl.jsolve.oven.simple.stub.StudentWithBadlyAnnotatedFromNestedField.NOT_EXISTING_NESTED_FIELD; import static pl.jsolve.oven.simple.stub.StudentWithBadlyAnnotatedMapTo.NOT_EXISTING_FIELD; import static pl.jsolve.oven.simple.stub.StudentWithMapParsingIntToAnnotationMapping.MAP_PARSING_INT_TO_ANNOTATION_CLASS; -import static pl.jsolve.oven.stub.hero.HeroBuilder.aHero; +import static pl.jsolve.oven.stub.hero.HeroWithAliasesBuilder.aHero; import static pl.jsolve.sweetener.collection.Collections.newArrayList; import static pl.jsolve.sweetener.collection.Collections.newHashSet; import static pl.jsolve.sweetener.tests.assertion.ThrowableAssertions.assertThrowable; @@ -52,7 +49,7 @@ public void shouldMapHeroToHeroSnapshot() { @Test public void shouldMapHeroWithNullFieldsToHeroSnapshot() { // given - Hero hero = aHero().build(); + Hero hero = HeroBuilder.aHero().build(); // when HeroSnapshot result = AnnotationDrivenMapper.map(hero, HeroSnapshot.class); @@ -65,7 +62,7 @@ public void shouldMapHeroWithNullFieldsToHeroSnapshot() { @Test public void shouldMapHeroToHeroDTO() { // given - Hero hero = aHero().withId(ID).withNickname(NICKNAME).build(); + Hero hero = HeroBuilder.aHero().withId(ID).withNickname(NICKNAME).build(); // when HeroDTO result = AnnotationDrivenMapper.map(hero, HeroDTO.class); @@ -91,6 +88,61 @@ public void shouldMapHeroSnapshotToHero() { assertThat(result.getNickname()).as("nickanme field is not annotated for mapping").isNull(); } + @Test + public void shouldMapHeroWithAliasToHeroSnapshotWithAlias() { + // given + HeroWithAlias heroWithAlias = HeroWithAliasesBuilder.aHero().withId(ID).withNickname(NICKNAME).build(); + + // when + HeroSnapshotWithAlias result = AnnotationDrivenMapper.map(heroWithAlias, HeroSnapshotWithAlias.class); + + // then + assertThat(result.getId()).isEqualTo(heroWithAlias.getId()); + assertThat(result.getName()).isEqualTo(heroWithAlias.getNickname()); + } + + @Test + public void shouldMapHeroWithAliasAndNullFieldsToHeroSnapshotWithAlias() { + // given + HeroWithAlias heroWithAlias = HeroWithAliasesBuilder.aHero().build(); + + // when + HeroSnapshotWithAlias result = AnnotationDrivenMapper.map(heroWithAlias, HeroSnapshotWithAlias.class); + + // then + assertThat(result.getId()).isNull(); + assertThat(result.getName()).isNull(); + } + + @Test + public void shouldMapHeroWithAliasToHeroDTOWithAlias() { + // given + HeroWithAlias heroWithAlias = HeroWithAliasesBuilder.aHero().withId(ID).withNickname(NICKNAME).build(); + + // when + HeroDTOWithAlias result = AnnotationDrivenMapper.map(heroWithAlias, HeroDTOWithAlias.class); + + // then + assertThat(result.getId()).isEqualTo(heroWithAlias.getId()); + assertThat(result.getNickname()).isEqualTo(heroWithAlias.getNickname()); + } + + @Test + public void shouldMapHeroWithAliasSnapshotToHeroWithAlias() { + // given + HeroSnapshotWithAlias heroSnapshotWithAlias = new HeroSnapshotWithAlias(); + heroSnapshotWithAlias.setId(ID); + + // when + HeroWithAlias result = AnnotationDrivenMapper.map(heroSnapshotWithAlias, HeroWithAlias.class); + + // then + assertThat(result.getId()).isEqualTo(heroSnapshotWithAlias.getId()); + assertThat(result.getFirstName()).as("firstName field is not annotated for mapping").isNull(); + assertThat(result.getLastName()).as("lastName field is not annotated for mapping").isNull(); + assertThat(result.getNickname()).as("nickanme field is not annotated for mapping").isNull(); + } + @Test public void shouldThrowExceptionWhenMappingToUnmappableObject() { // when @@ -98,13 +150,13 @@ public void shouldThrowExceptionWhenMappingToUnmappableObject() { @Override public void operate() throws Exception { - AnnotationDrivenMapper.map(new Hero(), Person.class); + AnnotationDrivenMapper.map(new HeroWithAlias(), Person.class); } }); // then assertThrowable(caughtException).withMessageContaining( - Hero.class + " is not mappable to " + Person.class).isThrown(); + HeroWithAlias.class + " is not mappable to " + Person.class).isThrown(); } @Test diff --git a/src/test/java/pl/jsolve/oven/stub/hero/HeroDTOWithAlias.java b/src/test/java/pl/jsolve/oven/stub/hero/HeroDTOWithAlias.java new file mode 100644 index 0000000..1dbacad --- /dev/null +++ b/src/test/java/pl/jsolve/oven/stub/hero/HeroDTOWithAlias.java @@ -0,0 +1,18 @@ +package pl.jsolve.oven.stub.hero; + +import pl.jsolve.oven.annotationdriven.annotation.Alias; + +@Alias("HeroDTO") +public class HeroDTOWithAlias { + + private Long id; + private String nickname; + + public Long getId() { + return id; + } + + public String getNickname() { + return nickname; + } +} \ No newline at end of file diff --git a/src/test/java/pl/jsolve/oven/stub/hero/HeroProfiledBuilder.java b/src/test/java/pl/jsolve/oven/stub/hero/HeroProfiledBuilder.java index cfdbea2..1ab5503 100644 --- a/src/test/java/pl/jsolve/oven/stub/hero/HeroProfiledBuilder.java +++ b/src/test/java/pl/jsolve/oven/stub/hero/HeroProfiledBuilder.java @@ -1,23 +1,23 @@ package pl.jsolve.oven.stub.hero; -import static pl.jsolve.oven.stub.hero.HeroBuilder.aHero; +import static pl.jsolve.oven.stub.hero.HeroWithAliasesBuilder.aHero; import pl.jsolve.sweetener.builder.Builder; -public class HeroProfiledBuilder extends Builder { +public class HeroProfiledBuilder extends Builder { - public static HeroBuilder aCaptainAmerica() { + public static HeroWithAliasesBuilder aCaptainAmerica() { return aHero().withFirstName("Steve").withLastName("Rogers").withNickname("captainAmerica"); } - public static HeroBuilder aRedScull() { + public static HeroWithAliasesBuilder aRedScull() { return aHero().withFirstName("Johann").withLastName("Schmidt").withNickname("redScull"); } - public static HeroBuilder anIronMan() { + public static HeroWithAliasesBuilder anIronMan() { return aHero().withFirstName("Anthony").withLastName("Stark").withNickname("ironMan"); } - public static HeroBuilder aHulk() { + public static HeroWithAliasesBuilder aHulk() { return aHero().withFirstName("Bruce").withLastName("Banner").withNickname("hulk"); } } diff --git a/src/test/java/pl/jsolve/oven/stub/hero/HeroSnapshotWithAlias.java b/src/test/java/pl/jsolve/oven/stub/hero/HeroSnapshotWithAlias.java new file mode 100644 index 0000000..3180f02 --- /dev/null +++ b/src/test/java/pl/jsolve/oven/stub/hero/HeroSnapshotWithAlias.java @@ -0,0 +1,30 @@ +package pl.jsolve.oven.stub.hero; + +import pl.jsolve.oven.annotationdriven.annotation.Alias; +import pl.jsolve.oven.annotationdriven.annotation.Map; +import pl.jsolve.oven.annotationdriven.annotation.MappableToAlias; + +@Alias("HeroSnapshot") +@MappableToAlias("Hero") +public class HeroSnapshotWithAlias { + + @Map + private Long id; + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} \ No newline at end of file diff --git a/src/test/java/pl/jsolve/oven/stub/hero/HeroWithAlias.java b/src/test/java/pl/jsolve/oven/stub/hero/HeroWithAlias.java new file mode 100644 index 0000000..26ddbbf --- /dev/null +++ b/src/test/java/pl/jsolve/oven/stub/hero/HeroWithAlias.java @@ -0,0 +1,56 @@ +package pl.jsolve.oven.stub.hero; + +import pl.jsolve.oven.annotationdriven.annotation.Alias; +import pl.jsolve.oven.annotationdriven.annotation.Map; +import pl.jsolve.oven.annotationdriven.annotation.MappableToAlias; +import pl.jsolve.oven.annotationdriven.annotation.Mappings; + +@Alias("Hero") +@MappableToAlias({ "HeroSnapshot", "HeroDTO" }) +public class HeroWithAlias { + + @Map + private Long id; + private String firstName; + private String lastName; + @Mappings({ + @Map(to = "name", of = HeroSnapshotWithAlias.class), + @Map(to = "nickname", of = HeroDTOWithAlias.class), + }) + private String nickname; + + public HeroWithAlias() { + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getNickname() { + return nickname; + } + + public void setNickname(String nickname) { + this.nickname = nickname; + } +} diff --git a/src/test/java/pl/jsolve/oven/stub/hero/HeroWithAliasesBuilder.java b/src/test/java/pl/jsolve/oven/stub/hero/HeroWithAliasesBuilder.java new file mode 100644 index 0000000..02e7e5e --- /dev/null +++ b/src/test/java/pl/jsolve/oven/stub/hero/HeroWithAliasesBuilder.java @@ -0,0 +1,30 @@ +package pl.jsolve.oven.stub.hero; + +import pl.jsolve.sweetener.builder.Builder; + +public class HeroWithAliasesBuilder extends Builder { + + public static HeroWithAliasesBuilder aHero() { + return new HeroWithAliasesBuilder(); + } + + public HeroWithAliasesBuilder withFirstName(String firstName) { + getBuiltObject().setFirstName(firstName); + return this; + } + + public HeroWithAliasesBuilder withLastName(String lastName) { + getBuiltObject().setLastName(lastName); + return this; + } + + public HeroWithAliasesBuilder withNickname(String nickname) { + getBuiltObject().setNickname(nickname); + return this; + } + + public HeroWithAliasesBuilder withId(Long id) { + getBuiltObject().setId(id); + return this; + } +} diff --git a/src/test/java/pl/jsolve/oven/stub/person/Person.java b/src/test/java/pl/jsolve/oven/stub/person/Person.java index aa5eec7..018d419 100644 --- a/src/test/java/pl/jsolve/oven/stub/person/Person.java +++ b/src/test/java/pl/jsolve/oven/stub/person/Person.java @@ -1,8 +1,10 @@ package pl.jsolve.oven.stub.person; +import pl.jsolve.oven.annotationdriven.annotation.Alias; import pl.jsolve.oven.annotationdriven.annotation.Map; import pl.jsolve.oven.annotationdriven.annotation.Mappings; +@Alias("WrongAlias") public class Person { @Map From a11ef6db4da6c9f21b1ac646566434c6e5e83479 Mon Sep 17 00:00:00 2001 From: Wojciech Milczarek Date: Wed, 5 Aug 2015 12:33:34 +0200 Subject: [PATCH 2/3] #9 @MappableToAlias, @Alias - Few more unit tests --- .../AnnotationDrivenMapper.java | 6 ++-- .../simple/AnnotationDrivenMapperTest.java | 34 ++++++++++++++++++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/main/java/pl/jsolve/oven/annotationdriven/AnnotationDrivenMapper.java b/src/main/java/pl/jsolve/oven/annotationdriven/AnnotationDrivenMapper.java index 9470ff0..825f034 100644 --- a/src/main/java/pl/jsolve/oven/annotationdriven/AnnotationDrivenMapper.java +++ b/src/main/java/pl/jsolve/oven/annotationdriven/AnnotationDrivenMapper.java @@ -54,8 +54,10 @@ public static boolean isMappableToTargetClass(T object, Class targetCl MappableTo mappableTo = object.getClass().getAnnotation(MappableTo.class); MappableToAlias mappableToAlias = object.getClass().getAnnotation(MappableToAlias.class); - boolean mappable = - mappableToAlias != null && Arrays.asList(mappableToAlias.value()).contains(targetClass.getAnnotation(Alias.class).value()); + 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; diff --git a/src/test/java/pl/jsolve/oven/simple/AnnotationDrivenMapperTest.java b/src/test/java/pl/jsolve/oven/simple/AnnotationDrivenMapperTest.java index fc93129..1999a95 100644 --- a/src/test/java/pl/jsolve/oven/simple/AnnotationDrivenMapperTest.java +++ b/src/test/java/pl/jsolve/oven/simple/AnnotationDrivenMapperTest.java @@ -144,7 +144,7 @@ public void shouldMapHeroWithAliasSnapshotToHeroWithAlias() { } @Test - public void shouldThrowExceptionWhenMappingToUnmappableObject() { + public void shouldThrowExceptionWhenMappingToAliasUnmappableObjectWhitWrongAlias() { // when MappingException caughtException = tryToCatch(MappingException.class, new ExceptionalOperation() { @@ -159,6 +159,38 @@ public void operate() throws Exception { HeroWithAlias.class + " is not mappable to " + Person.class).isThrown(); } + @Test + public void shouldThrowExceptionWhenMappingToAliasUnmappableObjectWhitoutAnyAlias() { + // when + MappingException caughtException = tryToCatch(MappingException.class, new ExceptionalOperation() { + + @Override + public void operate() throws Exception { + AnnotationDrivenMapper.map(new HeroWithAlias(), Student.class); + } + }); + + // then + assertThrowable(caughtException).withMessageContaining( + HeroWithAlias.class + " is not mappable to " + Student.class).isThrown(); + } + + @Test + public void shouldThrowExceptionWhenMappingToUnmappableObject() { + // when + MappingException caughtException = tryToCatch(MappingException.class, new ExceptionalOperation() { + + @Override + public void operate() throws Exception { + AnnotationDrivenMapper.map(new Hero(), Student.class); + } + }); + + // then + assertThrowable(caughtException).withMessageContaining( + Hero.class + " is not mappable to " + Student.class).isThrown(); + } + @Test public void shouldThrowExceptionWhenTargetFieldDoesNotExist() { // when From 18c0e3da0ece0b7603fcc126b68c37c9c6a84f6f Mon Sep 17 00:00:00 2001 From: wojciech milczarek Date: Mon, 10 Aug 2015 14:30:03 +0200 Subject: [PATCH 3/3] #9 @MappableToAlias, @Alias -@MapToAlias for mapping fields by aliases --- .../MapAnnotationMapping.java | 74 ++++++++++++++++++- .../annotationdriven/annotation/Alias.java | 4 +- .../oven/annotationdriven/annotation/Map.java | 2 +- .../annotation/MapToAlias.java | 23 ++++++ .../annotation/MappingsForAliases.java | 13 ++++ .../simple/AnnotationDrivenMapperTest.java | 17 ++++- .../java/pl/jsolve/oven/stub/hero/Hero.java | 14 ++-- .../pl/jsolve/oven/stub/hero/HeroDTO.java | 3 + .../pl/jsolve/oven/stub/hero/HeroSecond.java | 55 ++++++++++++++ .../oven/stub/hero/HeroSecondBuilder.java | 30 ++++++++ 10 files changed, 220 insertions(+), 15 deletions(-) create mode 100644 src/main/java/pl/jsolve/oven/annotationdriven/annotation/MapToAlias.java create mode 100644 src/main/java/pl/jsolve/oven/annotationdriven/annotation/MappingsForAliases.java create mode 100644 src/test/java/pl/jsolve/oven/stub/hero/HeroSecond.java create mode 100644 src/test/java/pl/jsolve/oven/stub/hero/HeroSecondBuilder.java diff --git a/src/main/java/pl/jsolve/oven/annotationdriven/MapAnnotationMapping.java b/src/main/java/pl/jsolve/oven/annotationdriven/MapAnnotationMapping.java index a713a35..6a3157b 100644 --- a/src/main/java/pl/jsolve/oven/annotationdriven/MapAnnotationMapping.java +++ b/src/main/java/pl/jsolve/oven/annotationdriven/MapAnnotationMapping.java @@ -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_ANNOTATION_CLASS = Map.class; + private static final Class MAP_TO_ALIAS_ANNOTATION_CLASS = MapToAlias.class; private static final Class MAPPINGS_ANNOTATION_CLASS = Mappings.class; + private static final Class MAPPINGS_FOR_ALIASES_ANNOTATION_CLASS = MappingsForAliases.class; private final AnnotationProvider annotationProvider = new AnnotationProvider(); @Override @@ -24,11 +26,16 @@ public void apply(S sourceObject, T targetObject) { } private void applyOnFieldsAnnotatedByMap(S sourceObject, T targetObject) { - List annotatedFields = annotationProvider.getFieldsAnnotatedBy(sourceObject, MAP_ANNOTATION_CLASS); - for (AnnotatedField field : annotatedFields) { + List annotatedFieldsForMapAlias = annotationProvider.getFieldsAnnotatedBy(sourceObject, MAP_TO_ALIAS_ANNOTATION_CLASS); + List 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 void applyOnFieldsAnnotatedByMappings(S sourceObject, T targetObject) { @@ -37,6 +44,11 @@ private 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 void applyOnFieldWithAnnotations(S sourceObject, T targetObject, Field field, Map... mapAnnotations) { @@ -45,6 +57,12 @@ private void applyOnFieldWithAnnotations(S sourceObject, T targetObject, } } + private void applyOnFieldWithAnnotations(S sourceObject, T targetObject, Field field, MapToAlias... mapAnnotations) { + for (MapToAlias mapAnnotation : mapAnnotations) { + applyOnFieldWithAnnotation(sourceObject, targetObject, field, mapAnnotation); + } + } + private void applyOnFieldWithAnnotation(S sourceObject, T targetObject, Field field, Map mapAnnotation) { if (isMappingIntendedForTargetObject(targetObject, mapAnnotation)) { String targetFieldName = getTargetFieldName(field, mapAnnotation); @@ -61,10 +79,34 @@ private void applyOnFieldWithAnnotation(S sourceObject, T targetObject, F } } + private 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 boolean isMappingIntendedForTargetObject(T targetObject, Map mapAnnotation) { return Collections.containsAny(Reflections.getClasses(targetObject), mapAnnotation.of()); } + private 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(); @@ -72,6 +114,13 @@ private String getTargetFieldName(Field field, Map mapAnnotation) { 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(); @@ -79,6 +128,13 @@ private String getSourceFieldName(Field field, Map mapAnnotation) { 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?", @@ -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); + } } diff --git a/src/main/java/pl/jsolve/oven/annotationdriven/annotation/Alias.java b/src/main/java/pl/jsolve/oven/annotationdriven/annotation/Alias.java index b94955f..dcc746d 100644 --- a/src/main/java/pl/jsolve/oven/annotationdriven/annotation/Alias.java +++ b/src/main/java/pl/jsolve/oven/annotationdriven/annotation/Alias.java @@ -6,7 +6,7 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) +@Target({ElementType.TYPE, ElementType.FIELD}) public @interface Alias { - public String value(); + public String value(); } diff --git a/src/main/java/pl/jsolve/oven/annotationdriven/annotation/Map.java b/src/main/java/pl/jsolve/oven/annotationdriven/annotation/Map.java index 23fe5e0..3982607 100644 --- a/src/main/java/pl/jsolve/oven/annotationdriven/annotation/Map.java +++ b/src/main/java/pl/jsolve/oven/annotationdriven/annotation/Map.java @@ -7,7 +7,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) -public @interface Map { +public @interface Map { String fromNested() default ""; diff --git a/src/main/java/pl/jsolve/oven/annotationdriven/annotation/MapToAlias.java b/src/main/java/pl/jsolve/oven/annotationdriven/annotation/MapToAlias.java new file mode 100644 index 0000000..2cafa68 --- /dev/null +++ b/src/main/java/pl/jsolve/oven/annotationdriven/annotation/MapToAlias.java @@ -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; +} diff --git a/src/main/java/pl/jsolve/oven/annotationdriven/annotation/MappingsForAliases.java b/src/main/java/pl/jsolve/oven/annotationdriven/annotation/MappingsForAliases.java new file mode 100644 index 0000000..b43ed1e --- /dev/null +++ b/src/main/java/pl/jsolve/oven/annotationdriven/annotation/MappingsForAliases.java @@ -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(); + +} diff --git a/src/test/java/pl/jsolve/oven/simple/AnnotationDrivenMapperTest.java b/src/test/java/pl/jsolve/oven/simple/AnnotationDrivenMapperTest.java index 1999a95..0dc8300 100644 --- a/src/test/java/pl/jsolve/oven/simple/AnnotationDrivenMapperTest.java +++ b/src/test/java/pl/jsolve/oven/simple/AnnotationDrivenMapperTest.java @@ -72,6 +72,19 @@ public void shouldMapHeroToHeroDTO() { assertThat(result.getNickname()).isEqualTo(hero.getNickname()); } + @Test + public void shouldMapHeroSecondToHeroDTO() { + // given + HeroSecond hero = HeroSecondBuilder.aHero().withId(ID).withNickname(NICKNAME).build(); + + // when + HeroDTO result = AnnotationDrivenMapper.map(hero, HeroDTO.class); + + // then + assertThat(result.getId()).isEqualTo(hero.getId()); + assertThat(result.getNickname()).isEqualTo(hero.getNickname()); + } + @Test public void shouldMapHeroSnapshotToHero() { // given @@ -389,8 +402,8 @@ public void shouldMapStudentWithCollectionsToStudentWithArrays() { public void shouldMapStudentWithArraysToStudentWithCollections() { // given StudentWithArrays studentWithArrays = new StudentWithArrays(null, null); - studentWithArrays.setGrades(new Integer[] { 4, 5 }); - studentWithArrays.setSubjects(new String[] { "Phisics", "Math" }); + studentWithArrays.setGrades(new Integer[]{4, 5}); + studentWithArrays.setSubjects(new String[]{"Phisics", "Math"}); // when StudentWithCollections studentWithCollections = AnnotationDrivenMapper.map(studentWithArrays, StudentWithCollections.class); diff --git a/src/test/java/pl/jsolve/oven/stub/hero/Hero.java b/src/test/java/pl/jsolve/oven/stub/hero/Hero.java index 24a26bf..7fef948 100644 --- a/src/test/java/pl/jsolve/oven/stub/hero/Hero.java +++ b/src/test/java/pl/jsolve/oven/stub/hero/Hero.java @@ -1,19 +1,21 @@ package pl.jsolve.oven.stub.hero; -import pl.jsolve.oven.annotationdriven.annotation.Map; -import pl.jsolve.oven.annotationdriven.annotation.MappableTo; -import pl.jsolve.oven.annotationdriven.annotation.Mappings; +import pl.jsolve.oven.annotationdriven.annotation.*; -@MappableTo({ HeroSnapshot.class, HeroDTO.class }) + +@MappableTo({HeroSnapshot.class, HeroDTO.class}) public class Hero { @Map private Long id; private String firstName; private String lastName; + + @MappingsForAliases({ + @MapToAlias(to = "nickname", of = "HeroDto") + }) @Mappings({ - @Map(to = "name", of = HeroSnapshot.class), - @Map(to = "nickname", of = HeroDTO.class), + @Map(to = "name", of = HeroSnapshot.class) }) private String nickname; diff --git a/src/test/java/pl/jsolve/oven/stub/hero/HeroDTO.java b/src/test/java/pl/jsolve/oven/stub/hero/HeroDTO.java index 601a93f..c1dcadc 100644 --- a/src/test/java/pl/jsolve/oven/stub/hero/HeroDTO.java +++ b/src/test/java/pl/jsolve/oven/stub/hero/HeroDTO.java @@ -1,5 +1,8 @@ package pl.jsolve.oven.stub.hero; +import pl.jsolve.oven.annotationdriven.annotation.Alias; + +@Alias("HeroDto") public class HeroDTO { private Long id; diff --git a/src/test/java/pl/jsolve/oven/stub/hero/HeroSecond.java b/src/test/java/pl/jsolve/oven/stub/hero/HeroSecond.java new file mode 100644 index 0000000..d1c9aed --- /dev/null +++ b/src/test/java/pl/jsolve/oven/stub/hero/HeroSecond.java @@ -0,0 +1,55 @@ +package pl.jsolve.oven.stub.hero; + +import pl.jsolve.oven.annotationdriven.annotation.*; + + +@MappableTo(HeroSnapshot.class) +@MappableToAlias("HeroDto") +public class HeroSecond { + + @Map + private Long id; + private String firstName; + private String lastName; + + @Mappings({ + @Map(to = "name", of = HeroSnapshot.class), + @Map(to = "nickname", of = HeroDTO.class) + }) + private String nickname; + + public HeroSecond() { + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getNickname() { + return nickname; + } + + public void setNickname(String nickname) { + this.nickname = nickname; + } +} diff --git a/src/test/java/pl/jsolve/oven/stub/hero/HeroSecondBuilder.java b/src/test/java/pl/jsolve/oven/stub/hero/HeroSecondBuilder.java new file mode 100644 index 0000000..f987cea --- /dev/null +++ b/src/test/java/pl/jsolve/oven/stub/hero/HeroSecondBuilder.java @@ -0,0 +1,30 @@ +package pl.jsolve.oven.stub.hero; + +import pl.jsolve.sweetener.builder.Builder; + +public class HeroSecondBuilder extends Builder { + + public static HeroSecondBuilder aHero() { + return new HeroSecondBuilder(); + } + + public HeroSecondBuilder withFirstName(String firstName) { + getBuiltObject().setFirstName(firstName); + return this; + } + + public HeroSecondBuilder withLastName(String lastName) { + getBuiltObject().setLastName(lastName); + return this; + } + + public HeroSecondBuilder withNickname(String nickname) { + getBuiltObject().setNickname(nickname); + return this; + } + + public HeroSecondBuilder withId(Long id) { + getBuiltObject().setId(id); + return this; + } +}