Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/issue #13 default mapping strategy #14

Merged
merged 21 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
51f3454
issue#13: test: Test if all three configurations can be set on a mapp…
N1cky94 Dec 19, 2023
7a949d4
issue#13: feat: Add the default mapping strategy
N1cky94 Dec 19, 2023
8019fd7
issue#13: refactor: Rename the configure mapping strategy test class …
N1cky94 Dec 19, 2023
cbc065e
issue#13: refactor: Rename the test class and internal method that te…
N1cky94 Dec 19, 2023
cf6fd25
issue#13: refactor: Bundle all tests that test the mapping with the L…
N1cky94 Dec 19, 2023
c984f5c
issue#13: refactor: Remove the test classes that have their tests bun…
N1cky94 Dec 19, 2023
e47d8fb
issue#13: test: Add id and record type to primitiveMother builder
N1cky94 Dec 19, 2023
14d97c7
issue#13: refactor: change name of ObjectMother build system to build…
N1cky94 Dec 19, 2023
b356f85
issue#13: refactor: Use object Mother to create standard record with …
N1cky94 Dec 19, 2023
815ad57
issue#13: test: Add primitive use case tests for the Default strategy
N1cky94 Dec 19, 2023
e87b240
issue#13: refactor: Connect unused id field to the record builder
N1cky94 Dec 19, 2023
4d70fca
issue#13: info: Add the documentation for the required implementation
N1cky94 Dec 19, 2023
087d785
issue#13: feat: Use Empty Field strategy to handle all occurrences of…
N1cky94 Dec 19, 2023
7f8a578
issue#13: refactor: change source name from capital S to small s
N1cky94 Dec 19, 2023
c62b45c
issue#13: refactor: Move all configuration from mapping strategy to t…
N1cky94 Dec 19, 2023
018906f
issue#13: refactor: remove duplicate code from the handle function fo…
N1cky94 Dec 19, 2023
c7bb5d4
issue#13: test: Add test to check Loose Mapping Stratigy for Empty fi…
N1cky94 Dec 19, 2023
0583857
issue#13: fix: Char was not available for mapping in the loose and de…
N1cky94 Dec 19, 2023
6ba61e8
issue#13: test: Provide test for the default strategy and test that r…
N1cky94 Dec 19, 2023
1dbe820
issue#13: refactor: Change names of tests in Empty Field Strategy Tes…
N1cky94 Dec 19, 2023
eb408fb
issue#13: test: Test Strict EmptyField usecase to throw exception eve…
N1cky94 Dec 19, 2023
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
32 changes: 23 additions & 9 deletions src/main/java/be/archilios/open/domainmapper/DomainMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,27 +28,41 @@ public <T, V> T map(V source, Class<T> targetClass) {
}
}

private <T, V> T tryMap(V Source, Class<T> targetClass) throws Exception {
private <T, V> T tryMap(V source, Class<T> targetClass) throws Exception {
T result = targetClass.getDeclaredConstructor().newInstance();

Field[] sourceFields = Source.getClass().getDeclaredFields();
Field[] sourceFields = source.getClass().getDeclaredFields();
Field[] targetFields = targetClass.getDeclaredFields();

mapFields(source, sourceFields, targetFields, result);
handleEmptyFields(targetFields, result);

if (getActiveMappingStrategy() == MappingStrategyPattern.STRICT) {
throw new MappingException("Could not map " + source.getClass().getSimpleName() + " to " + targetClass.getSimpleName() + " because of possible missing fields");
}

return result;
}

private static <T, V> void mapFields(V source, Field[] sourceFields, Field[] targetFields, T result) throws IllegalAccessException {
for (Field sourceField : sourceFields) {
for (Field targetField : targetFields) {
if (sourceField.getName().equals(targetField.getName())) {
sourceField.setAccessible(true);
targetField.setAccessible(true);
targetField.set(result, sourceField.get(Source));
targetField.set(result, sourceField.get(source));
}
}
}

if (getActiveMappingStrategy() == MappingStrategyPattern.STRICT) {
throw new MappingException("Could not map " + Source.getClass().getSimpleName() + " to " + targetClass.getSimpleName() + " because of possible missing fields");
}

private <T> void handleEmptyFields(Field[] targetFields, T result) throws IllegalAccessException {
for (Field targetField : targetFields) {
if (!targetField.canAccess(result)) {
targetField.setAccessible(true);
targetField.set(result, mappingStrategy.handleEmptyField(targetField.getType()));
}
}

return result;
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
package be.archilios.open.domainmapper.config;

import be.archilios.open.domainmapper.strategies.EmptyFieldStrategy;

public enum MappingStrategyPattern {
LOOSELY,
STRICT
LOOSELY(new EmptyFieldStrategy.EmptyFieldLooseStrategy()),
STRICT(new EmptyFieldStrategy.EmptyFieldStrictStrategy()),
DEFAULT(new EmptyFieldStrategy.EmptyFieldDefaultStrategy());

private final EmptyFieldStrategy emptyFieldStrategy;

MappingStrategyPattern(EmptyFieldStrategy emptyFieldStrategy) {
this.emptyFieldStrategy = emptyFieldStrategy;
}

public Object handleEmptyField(Class<?> clazz) {
return emptyFieldStrategy.handle(clazz);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package be.archilios.open.domainmapper.strategies;

import be.archilios.open.domainmapper.exceptions.MappingException;

public sealed interface EmptyFieldStrategy {

<T> T handle(Class<T> clazz);

final class EmptyFieldDefaultStrategy implements EmptyFieldStrategy {
@Override
public <T> T handle(Class<T> clazz) {
if (clazz.isPrimitive()) {
EmptyFieldStrategy looseStrategy = new EmptyFieldLooseStrategy();
return looseStrategy.handle(clazz);
}
throw new MappingException("Mapping not allowed because no default is know for empty field of type " + clazz.getSimpleName() + ", strategy DEFAULT requires default value to be declared when empty");
}
}

final class EmptyFieldLooseStrategy implements EmptyFieldStrategy {
@Override
public <T> T handle(Class<T> clazz) {
if (clazz.isPrimitive()) {
return switch (clazz.getSimpleName()) {
case "boolean" -> (T) Boolean.FALSE;
case "byte" -> (T) Byte.valueOf("0");
case "short" -> (T) Short.valueOf("0");
case "int" -> (T) Integer.valueOf("0");
case "long" -> (T) Long.valueOf("0");
case "float" -> (T) Float.valueOf("0");
case "double" -> (T) Double.valueOf("0");
case "char" -> (T) Character.valueOf('\u0000');
default -> throw new IllegalStateException("Unexpected value: " + clazz.getSimpleName());
};
}
return null;
}
}

final class EmptyFieldStrictStrategy implements EmptyFieldStrategy {
@Override
public <T> T handle(Class<T> clazz) {
throw new MappingException("Mapping not allowed because of empty field with type " + clazz.getSimpleName() + " and strategy STRICT");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package be.archilios.open.domainmapper;

import be.archilios.open.domainmapper.config.MappingStrategyPattern;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class ConfigureMappingStrategiesTest {

@Test
void DefaultsToLooseStrategy() {
DomainMapper mapper = new DomainMapper();

assertEquals(MappingStrategyPattern.LOOSELY, mapper.getActiveMappingStrategy());
}

@Test
void CanSetLooseStrategy() {
DomainMapper mapper = new DomainMapper(MappingStrategyPattern.LOOSELY);

assertEquals(MappingStrategyPattern.LOOSELY, mapper.getActiveMappingStrategy());
}

@Test
void CanSetStrictStrategy() {
DomainMapper mapper = new DomainMapper(MappingStrategyPattern.STRICT);

assertEquals(MappingStrategyPattern.STRICT, mapper.getActiveMappingStrategy());
}

@Test
void canSetDefaultStrategy() {
DomainMapper mapper = new DomainMapper(MappingStrategyPattern.DEFAULT);

assertEquals(MappingStrategyPattern.DEFAULT, mapper.getActiveMappingStrategy());
}

}

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package be.archilios.open.domainmapper;

import be.archilios.open.domainmapper.config.MappingStrategyPattern;
import be.archilios.open.domainmapper.data.PrimitiveOnlyData;
import be.archilios.open.domainmapper.data.PrimitiveOnlyDataMother;
import be.archilios.open.domainmapper.data.PrimitiveOnlyDataReceiver;
import be.archilios.open.domainmapper.data.PrimitiveOnlyRecord;
import be.archilios.open.domainmapper.exceptions.MappingException;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class MapWithDefaultStrategyTest {
private final DomainMapper mapper = new DomainMapper(MappingStrategyPattern.DEFAULT);
@Test
void primitiveObjectToPrimitiveObject() {
PrimitiveOnlyData source = PrimitiveOnlyDataMother.data().buildObject();

PrimitiveOnlyDataReceiver destination = mapper.map(source, PrimitiveOnlyDataReceiver.class);

assertEquals(source.getName(), destination.getName(), "Name should be mapped");
assertEquals(source.getAge(), destination.getAge(), "Age should be mapped");
assertEquals(source.isAdult(), destination.isAdult(), "Adult should be mapped");
assertEquals(0, destination.getId(), "Id should be mapped to default value");
}

@Test
void primitiveObjectToItself() {
final String changedName = "Dear";
PrimitiveOnlyData source = PrimitiveOnlyDataMother.data().withName(changedName).buildObject();

PrimitiveOnlyData destination = mapper.map(source, PrimitiveOnlyData.class);

assertEquals(changedName, destination.getName(), "Name should be correctly mapped");
}

@Test
void primitiveRecordToPrimitiveObject() {
PrimitiveOnlyRecord source = PrimitiveOnlyDataMother.data().buildRecord();

PrimitiveOnlyData destination = mapper.map(source, PrimitiveOnlyData.class);

assertEquals(source.name(), destination.getName(), "Name should be mapped");
assertEquals(source.age(), destination.getAge(), "Age should be mapped");
assertEquals(source.isAdult(), destination.isAdult(), "isAdult should be mapped");
assertEquals(0, destination.getHeight(), "Height should be mapped to default value");
}

@Test
void primitiveObjectToPrimitiveRecord() {
PrimitiveOnlyData source = PrimitiveOnlyDataMother.data().buildObject();

assertThrows(MappingException.class, () -> mapper.map(source, PrimitiveOnlyRecord.class));
}

@Test
void primitiveRecordToItself() {
final String changedName = "Dear";
PrimitiveOnlyRecord source = PrimitiveOnlyDataMother.data().withName(changedName).buildRecord();

assertThrows(MappingException.class, () -> mapper.map(source, PrimitiveOnlyRecord.class));
}
}
Loading