From 556d60ee49dbc16cef703c08234533f3390d38b9 Mon Sep 17 00:00:00 2001 From: Idan Elhalwani Date: Thu, 28 Apr 2022 12:06:57 +0300 Subject: [PATCH] Initial commit --- .gitignore | 112 ++++++ LICENSE | 7 + pom.xml | 305 +++++++++++++++ .../java/dev/krud/shapeshift/FieldMapper.java | 361 ++++++++++++++++++ .../annotation/DefaultMappingTarget.kt | 16 + .../shapeshift/annotation/MappedField.java | 57 +++ .../shapeshift/annotation/MappedFields.java | 26 ++ .../shapeshift/annotation/ObjectFieldPair.kt | 16 + .../shapeshift/dto/EntityStructureDTO.java | 45 +++ .../CommaDelimitedStringToListTransformer.kt | 36 ++ .../transformer/DateToLongTransformer.kt | 35 ++ .../transformer/DefaultTransformer.kt | 34 ++ .../transformer/LongToDateTransformer.kt | 36 ++ ...ngListToCommaDelimitedStringTransformer.kt | 34 ++ .../transformer/ToStringTransformer.kt | 34 ++ .../transformer/base/FieldTransformer.kt | 25 ++ .../krud/shapeshift/util/ReflectionUtils.kt | 33 ++ .../transformer/FieldMapperTests.kt | 68 ++++ .../transformer/TransformerUnitTests.kt | 139 +++++++ 19 files changed, 1419 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 pom.xml create mode 100644 src/main/java/dev/krud/shapeshift/FieldMapper.java create mode 100644 src/main/java/dev/krud/shapeshift/annotation/DefaultMappingTarget.kt create mode 100644 src/main/java/dev/krud/shapeshift/annotation/MappedField.java create mode 100644 src/main/java/dev/krud/shapeshift/annotation/MappedFields.java create mode 100644 src/main/java/dev/krud/shapeshift/annotation/ObjectFieldPair.kt create mode 100644 src/main/java/dev/krud/shapeshift/dto/EntityStructureDTO.java create mode 100644 src/main/java/dev/krud/shapeshift/transformer/CommaDelimitedStringToListTransformer.kt create mode 100644 src/main/java/dev/krud/shapeshift/transformer/DateToLongTransformer.kt create mode 100644 src/main/java/dev/krud/shapeshift/transformer/DefaultTransformer.kt create mode 100644 src/main/java/dev/krud/shapeshift/transformer/LongToDateTransformer.kt create mode 100644 src/main/java/dev/krud/shapeshift/transformer/StringListToCommaDelimitedStringTransformer.kt create mode 100644 src/main/java/dev/krud/shapeshift/transformer/ToStringTransformer.kt create mode 100644 src/main/java/dev/krud/shapeshift/transformer/base/FieldTransformer.kt create mode 100644 src/main/java/dev/krud/shapeshift/util/ReflectionUtils.kt create mode 100644 src/test/java/dev/krud/shapeshift/transformer/FieldMapperTests.kt create mode 100644 src/test/java/dev/krud/shapeshift/transformer/TransformerUnitTests.kt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d06f03c --- /dev/null +++ b/.gitignore @@ -0,0 +1,112 @@ +# Created by https://www.gitignore.io/api/eclipse,linux,windows,java,maven + +### Eclipse ### +*.pydevproject +.metadata +.gradle +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath + +# Eclipse Core +.project + +# Intellij +.idea +!.idea/copyright/profiles_settings.xml +!.idea/copyright/ShapeShift.xml +!.idea/codeStyles/codeStyleConfig.xml +!.idea/codeStyles/Project.xml +*.iml + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# Java annotation processor (APT) +.factorypath + +# PDT-specific +.buildpath + +# sbteclipse plugin +.target + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + + +### Linux ### +*~ + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + + +### Windows ### +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +### Java ### +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + + +### Maven ### +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +/.flattened-pom.xml diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e80646f --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright 2022 ShapeShift + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..5073d1b --- /dev/null +++ b/pom.xml @@ -0,0 +1,305 @@ + + + + 4.0.0 + dev.krud + shapeshift + shapeshift + 0.0.1-SNAPSHOT + + + 1.8 + 1.6.20 + + + 1.7.32 + + + + + MIT License + https://opensource.org/licenses/MIT + + + + + scm:git:git://github.com/krud-dev/shapeshift.git + scm:git:ssh://git@github.com/krud-dev/shapeshift.git + https://github.com/krud-dev/shapeshift + HEAD + + + + + ossrh + + + ossrh + https://s01.oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + org.codehaus.mojo + flatten-maven-plugin + 1.2.2 + + ossrh + + + + flatten + process-resources + + flatten + + + + flatten.clean + clean + + clean + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + ${gpg.keyname} + ${gpg.keyname} + + --pinentry-mode + loopback + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.2.0 + + false + + + + attach-javadocs + none + + jar + + + + + + org.jetbrains.dokka + dokka-maven-plugin + 0.10.1 + + + package + + javadocJar + + + + + + + + + + + + KRUD + admin@krud.dev + KRUD + https://www.krud.dev + + + + + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-reflect + ${kotlin.version} + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + org.junit.jupiter + junit-jupiter-api + 5.8.2 + test + + + com.nhaarman.mockitokotlin2 + mockito-kotlin + 2.2.0 + test + + + io.strikt + strikt-core + 0.31.0 + test + + + + + + + kotlin-maven-plugin + org.jetbrains.kotlin + ${kotlin.version} + + + compile + + compile + + + + ${project.basedir}/src/main/java + + + + + test-compile + + test-compile + + + + ${project.basedir}/src/test/java + + + + + kapt + + kapt + + + + ${project.basedir}/src/main/java + + + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-maven-noarg + ${kotlin.version} + + + + + jpa + spring + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + + default-compile + none + + + + default-testCompile + none + + + java-compile + compile + + compile + + + + java-test-compile + test-compile + + testCompile + + + + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + + **/*IntegrationTest* + + + + + + \ No newline at end of file diff --git a/src/main/java/dev/krud/shapeshift/FieldMapper.java b/src/main/java/dev/krud/shapeshift/FieldMapper.java new file mode 100644 index 0000000..3e6ac6a --- /dev/null +++ b/src/main/java/dev/krud/shapeshift/FieldMapper.java @@ -0,0 +1,361 @@ +/* + * Copyright KRUD 2022 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package dev.krud.shapeshift; + +import dev.krud.shapeshift.annotation.DefaultMappingTarget; +import dev.krud.shapeshift.annotation.MappedField; +import dev.krud.shapeshift.annotation.ObjectFieldPair; +import dev.krud.shapeshift.dto.EntityStructureDTO; +import dev.krud.shapeshift.transformer.DefaultTransformer; +import dev.krud.shapeshift.transformer.base.FieldTransformer; +import dev.krud.shapeshift.util.ReflectionUtils; +import kotlin.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Field; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class FieldMapper { + + private static final Map, Class> PRIMITIVES; + private static final String NODE_DELIMITER = "."; + private static Map, Class>, EntityStructureDTO> entityStructures = new HashMap<>(); + private static Map, Map> entityFieldMaps = new HashMap<>(); + + static { + PRIMITIVES = new HashMap<>(); + PRIMITIVES.put(boolean.class, Boolean.class); + PRIMITIVES.put(byte.class, Byte.class); + PRIMITIVES.put(short.class, Short.class); + PRIMITIVES.put(char.class, Character.class); + PRIMITIVES.put(int.class, Integer.class); + PRIMITIVES.put(long.class, Long.class); + PRIMITIVES.put(float.class, Float.class); + PRIMITIVES.put(double.class, Double.class); + } + + private Logger log = LoggerFactory.getLogger(getClass()); + private Map fieldTransformersByRef = new HashMap<>(); + private Map, FieldTransformer> fieldTransformersByType = new HashMap<>(); + private Map, Class>, FieldTransformer> defaultTransformers = new HashMap(); + private Map, Class>, FieldTransformer> defaultTransformersCache = new HashMap(); + + public void registerTransformer(String ref, FieldTransformer transformer) { + fieldTransformersByRef.put(ref, transformer); + fieldTransformersByType.put(transformer.getClass(), transformer); + if (transformer.isDefault()) { + registerDefaultTransformer(transformer); + } + } + + public void registerTransformer(Class clazz, FieldTransformer transformer) { + fieldTransformersByType.put(clazz, transformer); + if (transformer.isDefault()) { + registerDefaultTransformer(transformer); + } + } + + public void registerDefaultTransformer(FieldTransformer transformer) { + registerDefaultTransformer(transformer, transformer.fromType(), transformer.toType()); + } + + public void registerDefaultTransformer(FieldTransformer transformer, Class fromType, Class toType) { + Pair key = new Pair<>(boxPrimitiveClazz(fromType), boxPrimitiveClazz(toType)); + FieldTransformer existing = defaultTransformers.get(key); + if (existing != null) { + throw new IllegalStateException("Cannot register default transformer for pair [ " + key + " ] - already registered by [ " + existing.getClass().getName() + " ]"); + } + + defaultTransformers.put(key, transformer); + } + + public T processMappedFields(Object object, Class toClazz) { + T toObject = ReflectionUtils.instantiate(toClazz); + processMappedFields(object, toObject); + return toObject; + } + + public void processMappedFields(Object object, T toObject) { + + EntityStructureDTO es = getEntityStructure(object.getClass(), toObject.getClass()); + + for (MappedField typeAnnotation : es.getTypeAnnotations()) { + if (typeAnnotation.mapFrom().trim().isEmpty()) { + throw new RuntimeException("mapFrom can not be empty when used at a type level"); + } + + String fromPath = typeAnnotation.mapFrom(); + + if (fromPath.toLowerCase().startsWith(object.getClass().getSimpleName().toLowerCase() + NODE_DELIMITER)) { + fromPath = fromPath.substring(fromPath.indexOf(NODE_DELIMITER) + 1); + } + + String toPath = typeAnnotation.mapTo(); + + if (toPath.toLowerCase().startsWith(toObject.getClass().getSimpleName().toLowerCase() + NODE_DELIMITER)) { + toPath = toPath.substring(toPath.indexOf(NODE_DELIMITER) + 1); + } + + processMappedField(typeAnnotation, object, toObject, fromPath, toPath); + } + + for (Map.Entry> entry : es.getAnnotations().entrySet()) { + for (MappedField annotation : entry.getValue()) { + String fromPath = ""; + if (annotation.mapFrom().isEmpty()) { + fromPath = entry.getKey().getName(); + } else { + if (!annotation.mapFrom().startsWith(entry.getKey().getName() + NODE_DELIMITER)) { + fromPath = entry.getKey().getName() + NODE_DELIMITER + annotation.mapFrom(); + } else { + fromPath = annotation.mapFrom(); + } + } + processMappedField(annotation, object, toObject, fromPath, annotation.mapTo()); + } + } + } + + private void processMappedField(MappedField annotation, Object fromObject, Object toObject, String fromPath, String toPath) { + if (fromPath == null || fromPath.trim().isEmpty()) { + throw new RuntimeException("fromPath cannot be null or empty on class " + fromObject.getClass().getSimpleName()); + } + + ObjectFieldPair fromPair = getFieldByPath(fromPath, fromObject, SourceType.FROM); + if (fromPair == null) { + return; + } + + toPath = toPath.isEmpty() ? fromPair.getField().getName() : toPath; + ObjectFieldPair toPair = getFieldByPath(toPath, toObject, SourceType.TO); + + FieldTransformer transformer = getTransformer(annotation, fromPair, toPair); + mapField(fromPair, toPair, transformer); + } + + private ObjectFieldPair getFieldByPath(String path, Object object, SourceType type) { + if (object == null) { + if (type == SourceType.FROM) { + return null; + } + } + + + List nodes = Stream.of(path.split("\\" + NODE_DELIMITER)).collect(Collectors.toList()); + + + Field field = getField(nodes.get(0), object.getClass()); + Objects.requireNonNull(field, "Field " + nodes.get(0) + " not found on class " + object.getClass().getSimpleName()); + if (nodes.size() == 1) { + return new ObjectFieldPair(object, field, boxPrimitiveClazz(field.getType())); + } + + nodes.remove(0); + + field.setAccessible(true); + + Object subObject = ReflectionUtils.getValue(field, object); + + if (subObject == null && type == SourceType.TO) { + try { + subObject = ReflectionUtils.instantiate(field.getType()); + ReflectionUtils.setValue(field, object, subObject); + } catch (IllegalStateException e) { + throw new RuntimeException("Could not instantiate " + field.getName() + " of type " + field.getType().getSimpleName() + " on class " + object.getClass().getSimpleName()); + } + } + + return getFieldByPath(String.join(NODE_DELIMITER, nodes), subObject, type); + } + + private Class boxPrimitiveClazz(Class clazz) { + if (PRIMITIVES.containsKey(clazz)) { + return PRIMITIVES.get(clazz); + } + return clazz; + } + + + private void mapField(ObjectFieldPair fromPair, ObjectFieldPair toPair, FieldTransformer transformer) { + fromPair.getField().setAccessible(true); + toPair.getField().setAccessible(true); + + Object value = ReflectionUtils.getValue(fromPair.getField(), fromPair.getObject()); + if (transformer != null) { + value = transformer.transform(fromPair.getField(), toPair.getField(), value, fromPair.getObject(), toPair.getObject()); + } + + if (value != null) { + try { + ReflectionUtils.setValue(toPair.getField(), toPair.getObject(), value); + } catch (Exception e) { + IllegalStateException newException = new IllegalStateException("Could not map value " + fromPair.getField().getName() + " of class " + fromPair.getObject().getClass().getSimpleName() + " to " + toPair.getField().getName() + " of class" + toPair.getObject().getClass().getSimpleName()); + newException.initCause(e); + throw newException; + } + } + } + + private FieldTransformer getTransformer(MappedField annotation, ObjectFieldPair fromPair, ObjectFieldPair toPair) { + Pair transformationPair = new Pair(fromPair.getType(), toPair.getType()); + log.trace("Attempting to find transformer for transformation pair [ " + transformationPair + " ]"); + FieldTransformer transformer = null; + log.trace("Checking transformerRef field"); + if (!annotation.transformerRef().isEmpty()) { + log.trace("transformerRef is not empty with value [ " + annotation.transformerRef() + " ]"); + transformer = fieldTransformersByRef.get(annotation.transformerRef()); + log.trace("Found transformer of type [ " + transformer.getClass().getName() + " ]"); + } + + if (transformer == null) { + log.trace("Checking transformer field"); + if (annotation.transformer() == DefaultTransformer.class) { + log.trace("Transformer is DefaultTransformer, attempting to find a default transformer"); + Pair, Class> key = new Pair(fromPair.getType(), toPair.getType()); + + FieldTransformer defaultTransformer; + if (!defaultTransformersCache.containsKey(key)) { + defaultTransformer = getDefaultTransformer(key); + defaultTransformersCache.put(key, defaultTransformer); + } else { + defaultTransformer = defaultTransformersCache.get(key); + } + + if (defaultTransformer != null) { + log.trace("Found a default transformer of type [ " + defaultTransformer.getClass().getName() + " ]"); + return defaultTransformer; + } + + return null; + } + + transformer = fieldTransformersByType.get(annotation.transformer()); + if (transformer == null) { + transformer = ReflectionUtils.instantiate(annotation.transformer()); + fieldTransformersByType.put(annotation.transformer(), transformer); + } + } + + return transformer; + } + + private FieldTransformer getDefaultTransformer(Pair, Class> key) { + FieldTransformer defaultTransformer = defaultTransformers.get(key); + if (defaultTransformer == null) { + defaultTransformer = defaultTransformers.entrySet() + .stream() + .filter((entry) -> { + Pair, Class> pair = entry.getKey(); + Class first = pair.getFirst(); + Class second = pair.getSecond(); + return first.isAssignableFrom(key.getFirst()) && second.isAssignableFrom(key.getSecond()); + }) + .map(entry -> entry.getValue()) + .findFirst() + .orElse(null); + } + return defaultTransformer; + } + + private EntityStructureDTO getEntityStructure(Class fromClass, Class toClass) { + EntityStructureDTO entityStructureDTO = entityStructures.get(new Pair, Class>(fromClass, toClass)); + if (entityStructureDTO != null) { + return entityStructureDTO; + } + + Map> annotations = new HashMap<>(); + List typeAnnotations = new ArrayList<>(); + Class clazz = fromClass; + while (clazz != null) { + Field[] fields = clazz.getDeclaredFields(); + DefaultMappingTarget defaultMappingTarget = (DefaultMappingTarget) clazz.getDeclaredAnnotation(DefaultMappingTarget.class); + Class defaultFromClass = defaultMappingTarget == null ? Void.class : defaultMappingTarget.value(); + + typeAnnotations.addAll(Arrays.stream((MappedField[]) clazz.getDeclaredAnnotationsByType(MappedField.class)) + .filter(mappedField -> { + try { + return isOfType(defaultFromClass, mappedField.target(), toClass); + } catch (IllegalStateException e) { + throw new RuntimeException("Could not create entity structure for <" + fromClass.getSimpleName() + ", " + toClass.getSimpleName() + ">: " + e.getMessage()); + } + + }) + .collect(Collectors.toList()) + ); + + for (Field field : fields) { + List availableAnnotations = Arrays.asList(field.getDeclaredAnnotationsByType(MappedField.class)); + availableAnnotations = availableAnnotations.stream() + .filter(mappedField -> { + try { + return isOfType(defaultFromClass, mappedField.target(), toClass); + } catch (IllegalStateException e) { + throw new IllegalStateException("Could not create entity structure for <" + fromClass.getSimpleName() + ", " + toClass.getSimpleName() + ">: " + e.getMessage()); + } + + }) + .collect(Collectors.toList()); + + annotations.put(field, availableAnnotations); + } + + clazz = clazz.getSuperclass(); + } + + entityStructureDTO = new EntityStructureDTO(typeAnnotations, annotations); + + entityStructures.put(new Pair, Class>(fromClass, toClass), entityStructureDTO); + return entityStructureDTO; + } + + private Map getFieldsMap(Class clazz) { + if (entityFieldMaps.get(clazz) != null) { + return entityFieldMaps.get(clazz); + } + + Map fieldsMap = new HashMap<>(); + Class classToGetFields = clazz; + while (classToGetFields != null) { + Field[] fields = classToGetFields.getDeclaredFields(); + for (Field field : fields) { + fieldsMap.put(field.getName(), field); + } + classToGetFields = classToGetFields.getSuperclass(); + } + + entityFieldMaps.put(clazz, fieldsMap); + + return fieldsMap; + } + + private Field getField(String name, Class clazz) { + return getFieldsMap(clazz).get(name); + } + + private boolean isOfType(Class defaultFromClass, Class fromClass, Class toClass) { + if (fromClass == Void.class) { + if (defaultFromClass == Void.class) { + throw new IllegalStateException("No mapping target or default mapping target specified"); + } + + fromClass = defaultFromClass; + } + + return fromClass.isAssignableFrom(toClass); + } + + private enum SourceType { + TO, FROM + } +} diff --git a/src/main/java/dev/krud/shapeshift/annotation/DefaultMappingTarget.kt b/src/main/java/dev/krud/shapeshift/annotation/DefaultMappingTarget.kt new file mode 100644 index 0000000..e5522d8 --- /dev/null +++ b/src/main/java/dev/krud/shapeshift/annotation/DefaultMappingTarget.kt @@ -0,0 +1,16 @@ +/* + * Copyright KRUD 2022 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package dev.krud.shapeshift.annotation + +import kotlin.reflect.KClass + +@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS) +annotation class DefaultMappingTarget(val value: KClass<*>) \ No newline at end of file diff --git a/src/main/java/dev/krud/shapeshift/annotation/MappedField.java b/src/main/java/dev/krud/shapeshift/annotation/MappedField.java new file mode 100644 index 0000000..4b9aeb1 --- /dev/null +++ b/src/main/java/dev/krud/shapeshift/annotation/MappedField.java @@ -0,0 +1,57 @@ +/* + * Copyright KRUD 2022 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package dev.krud.shapeshift.annotation; + +import dev.krud.shapeshift.FieldMapper; +import dev.krud.shapeshift.transformer.DefaultTransformer; +import dev.krud.shapeshift.transformer.base.FieldTransformer; + +import java.lang.annotation.*; + +/** + * Indicates that an annotated field should be mapped to a field on a different model by the {@link FieldMapper} + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.TYPE}) +@Repeatable(MappedFields.class) +public @interface MappedField { + + /** + * The target class to map the field to + */ + Class target() default Void.class; + + + /** + * (Optional) The field name to map the value from. + * When used at the field level, allows for mapping of nested values + * If left empty at the type level, an exception will be thrown. Otherwise, the name of the field will be used. + */ + String mapFrom() default ""; + + /** + * (Optional) The field name to map the value to. + * If left empty, the name of the field will be used. + */ + String mapTo() default ""; + + /** + * The {@link FieldTransformer} to use on the value + */ + Class transformer() default DefaultTransformer.class; + + /** + * Bean name for a defined {@link FieldTransformer} bean to use on the value + * Supersedes {@link MappedField#transformer()} if specified + */ + String transformerRef() default ""; +} + diff --git a/src/main/java/dev/krud/shapeshift/annotation/MappedFields.java b/src/main/java/dev/krud/shapeshift/annotation/MappedFields.java new file mode 100644 index 0000000..b4e488e --- /dev/null +++ b/src/main/java/dev/krud/shapeshift/annotation/MappedFields.java @@ -0,0 +1,26 @@ +/* + * Copyright KRUD 2022 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package dev.krud.shapeshift.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Meta annotation to enable repeating {@link MappedField} + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.TYPE}) +public @interface MappedFields { + + MappedField[] value(); +} diff --git a/src/main/java/dev/krud/shapeshift/annotation/ObjectFieldPair.kt b/src/main/java/dev/krud/shapeshift/annotation/ObjectFieldPair.kt new file mode 100644 index 0000000..149a956 --- /dev/null +++ b/src/main/java/dev/krud/shapeshift/annotation/ObjectFieldPair.kt @@ -0,0 +1,16 @@ +/* + * Copyright KRUD 2022 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package dev.krud.shapeshift.annotation + +import java.lang.reflect.Field + + +data class ObjectFieldPair(val `object`: Any, val field: Field, val type: Class<*>) \ No newline at end of file diff --git a/src/main/java/dev/krud/shapeshift/dto/EntityStructureDTO.java b/src/main/java/dev/krud/shapeshift/dto/EntityStructureDTO.java new file mode 100644 index 0000000..ccc997b --- /dev/null +++ b/src/main/java/dev/krud/shapeshift/dto/EntityStructureDTO.java @@ -0,0 +1,45 @@ +/* + * Copyright KRUD 2022 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package dev.krud.shapeshift.dto; + +import dev.krud.shapeshift.annotation.MappedField; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.Map; + +public class EntityStructureDTO { + + private List typeAnnotations; + + private Map> annotations; + + public EntityStructureDTO(List typeAnnotations, Map> annotations) { + this.typeAnnotations = typeAnnotations; + this.annotations = annotations; + } + + public List getTypeAnnotations() { + return typeAnnotations; + } + + public void setTypeAnnotations(List typeAnnotations) { + this.typeAnnotations = typeAnnotations; + } + + public Map> getAnnotations() { + return annotations; + } + + public void setAnnotations(Map> annotations) { + this.annotations = annotations; + } +} diff --git a/src/main/java/dev/krud/shapeshift/transformer/CommaDelimitedStringToListTransformer.kt b/src/main/java/dev/krud/shapeshift/transformer/CommaDelimitedStringToListTransformer.kt new file mode 100644 index 0000000..2b22b27 --- /dev/null +++ b/src/main/java/dev/krud/shapeshift/transformer/CommaDelimitedStringToListTransformer.kt @@ -0,0 +1,36 @@ +/* + * Copyright KRUD 2022 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package dev.krud.shapeshift.transformer + +import dev.krud.shapeshift.transformer.base.FieldTransformer +import java.lang.reflect.Field + +class CommaDelimitedStringToListTransformer : FieldTransformer> { + override fun fromType(): Class { + return String::class.java + } + + override fun toType(): Class> { + return List::class.java + } + + override fun transform( + fromField: Field, + toField: Field, + originalValue: String?, + fromObject: Any, + toObject: Any + ): List<*>? { + originalValue ?: return null + return originalValue + .split(",") + } +} \ No newline at end of file diff --git a/src/main/java/dev/krud/shapeshift/transformer/DateToLongTransformer.kt b/src/main/java/dev/krud/shapeshift/transformer/DateToLongTransformer.kt new file mode 100644 index 0000000..d3f1677 --- /dev/null +++ b/src/main/java/dev/krud/shapeshift/transformer/DateToLongTransformer.kt @@ -0,0 +1,35 @@ +/* + * Copyright KRUD 2022 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package dev.krud.shapeshift.transformer + +import dev.krud.shapeshift.transformer.base.FieldTransformer +import java.lang.reflect.Field +import java.util.* + +open class DateToLongTransformer : FieldTransformer { + override fun fromType(): Class { + return Date::class.java + } + + override fun toType(): Class { + return Long::class.java + } + + override fun transform( + fromField: Field, + toField: Field, + originalValue: Date?, + fromObject: Any, + toObject: Any + ): Long? { + return originalValue?.time + } +} \ No newline at end of file diff --git a/src/main/java/dev/krud/shapeshift/transformer/DefaultTransformer.kt b/src/main/java/dev/krud/shapeshift/transformer/DefaultTransformer.kt new file mode 100644 index 0000000..4d4b2d9 --- /dev/null +++ b/src/main/java/dev/krud/shapeshift/transformer/DefaultTransformer.kt @@ -0,0 +1,34 @@ +/* + * Copyright KRUD 2022 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package dev.krud.shapeshift.transformer + +import dev.krud.shapeshift.transformer.base.FieldTransformer +import java.lang.reflect.Field + +class DefaultTransformer : FieldTransformer { + override fun fromType(): Class { + return Any::class.java + } + + override fun toType(): Class { + return Any::class.java + } + + override fun transform( + fromField: Field, + toField: Field, + originalValue: Any?, + fromObject: Any, + toObject: Any + ): Any? { + return originalValue + } +} \ No newline at end of file diff --git a/src/main/java/dev/krud/shapeshift/transformer/LongToDateTransformer.kt b/src/main/java/dev/krud/shapeshift/transformer/LongToDateTransformer.kt new file mode 100644 index 0000000..007523e --- /dev/null +++ b/src/main/java/dev/krud/shapeshift/transformer/LongToDateTransformer.kt @@ -0,0 +1,36 @@ +/* + * Copyright KRUD 2022 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package dev.krud.shapeshift.transformer + +import dev.krud.shapeshift.transformer.base.FieldTransformer +import java.lang.reflect.Field +import java.util.* + +class LongToDateTransformer : FieldTransformer { + override fun fromType(): Class { + return Long::class.java + } + + override fun toType(): Class { + return Date::class.java + } + + override fun transform( + fromField: Field, + toField: Field, + originalValue: Long?, + fromObject: Any, + toObject: Any + ): Date? { + originalValue ?: return null + return Date(originalValue) + } +} \ No newline at end of file diff --git a/src/main/java/dev/krud/shapeshift/transformer/StringListToCommaDelimitedStringTransformer.kt b/src/main/java/dev/krud/shapeshift/transformer/StringListToCommaDelimitedStringTransformer.kt new file mode 100644 index 0000000..87255c1 --- /dev/null +++ b/src/main/java/dev/krud/shapeshift/transformer/StringListToCommaDelimitedStringTransformer.kt @@ -0,0 +1,34 @@ +/* + * Copyright KRUD 2022 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package dev.krud.shapeshift.transformer + +import dev.krud.shapeshift.transformer.base.FieldTransformer +import java.lang.reflect.Field + +class StringListToCommaDelimitedStringTransformer : FieldTransformer, String> { + override fun fromType(): Class> { + return List::class.java + } + + override fun toType(): Class { + return String::class.java + } + + override fun transform( + fromField: Field, + toField: Field, + originalValue: List<*>?, + fromObject: Any, + toObject: Any + ): String? { + return originalValue?.joinToString(",") + } +} \ No newline at end of file diff --git a/src/main/java/dev/krud/shapeshift/transformer/ToStringTransformer.kt b/src/main/java/dev/krud/shapeshift/transformer/ToStringTransformer.kt new file mode 100644 index 0000000..72fb70b --- /dev/null +++ b/src/main/java/dev/krud/shapeshift/transformer/ToStringTransformer.kt @@ -0,0 +1,34 @@ +/* + * Copyright KRUD 2022 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package dev.krud.shapeshift.transformer + +import dev.krud.shapeshift.transformer.base.FieldTransformer +import java.lang.reflect.Field + +class ToStringTransformer : FieldTransformer { + override fun fromType(): Class { + return Any::class.java + } + + override fun toType(): Class { + return String::class.java + } + + override fun transform( + fromField: Field, + toField: Field, + originalValue: Any?, + fromObject: Any, + toObject: Any + ): String? { + return originalValue?.toString() + } +} \ No newline at end of file diff --git a/src/main/java/dev/krud/shapeshift/transformer/base/FieldTransformer.kt b/src/main/java/dev/krud/shapeshift/transformer/base/FieldTransformer.kt new file mode 100644 index 0000000..397ef29 --- /dev/null +++ b/src/main/java/dev/krud/shapeshift/transformer/base/FieldTransformer.kt @@ -0,0 +1,25 @@ +/* + * Copyright KRUD 2022 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package dev.krud.shapeshift.transformer.base + +import java.lang.reflect.Field + +interface FieldTransformer { + /** + * Whether or not the transformer is considered default for the [FromType]-[ToType] pair + */ + val isDefault: Boolean + get() = false + + fun fromType(): Class + fun toType(): Class + fun transform(fromField: Field, toField: Field, originalValue: FromType?, fromObject: Any, toObject: Any): ToType? +} \ No newline at end of file diff --git a/src/main/java/dev/krud/shapeshift/util/ReflectionUtils.kt b/src/main/java/dev/krud/shapeshift/util/ReflectionUtils.kt new file mode 100644 index 0000000..7ff0cd3 --- /dev/null +++ b/src/main/java/dev/krud/shapeshift/util/ReflectionUtils.kt @@ -0,0 +1,33 @@ +/* + * Copyright KRUD 2022 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +@file:JvmName("ReflectionUtils") + +package dev.krud.shapeshift.util + +import java.lang.reflect.Field + +fun Class.instantiate(): T { + try { + return this.newInstance() + } catch (e: Exception) { + throw RuntimeException(e) + } +} + +fun Field.getValue(target: Any): Any? { + return this.get(target) +} + +fun Field.setValue(target: Any, value: Any?) { + this.set(target, value) +} + + diff --git a/src/test/java/dev/krud/shapeshift/transformer/FieldMapperTests.kt b/src/test/java/dev/krud/shapeshift/transformer/FieldMapperTests.kt new file mode 100644 index 0000000..d3fdabf --- /dev/null +++ b/src/test/java/dev/krud/shapeshift/transformer/FieldMapperTests.kt @@ -0,0 +1,68 @@ +/* + * Copyright KRUD 2022 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package dev.krud.shapeshift.transformer + +import dev.krud.shapeshift.FieldMapper +import dev.krud.shapeshift.annotation.DefaultMappingTarget +import dev.krud.shapeshift.annotation.MappedField +import org.junit.jupiter.api.Test +import strikt.api.expectThat +import strikt.api.expectThrows +import strikt.assertions.isEqualTo +import java.util.* + +class FieldMapperTests { + + @Test + fun `verify exception is thrown if a default transformer pair is registered twice`() { + val fieldMapper = FieldMapper() + val defaultTransformer = object : DateToLongTransformer() { + override val isDefault: Boolean + get() = true + } + + expectThrows { + fieldMapper + .registerTransformer(defaultTransformer::class.java, defaultTransformer) + fieldMapper + .registerTransformer(defaultTransformer::class.java, defaultTransformer) + } + } + + @Test + internal fun `test implicit default transformer`() { + val fieldMapper = FieldMapper() + val defaultTransformer = object : DateToLongTransformer() { + override val isDefault: Boolean + get() = true + } + fieldMapper + .registerTransformer(defaultTransformer::class.java, defaultTransformer) + + val sourceClass = ExampleSourceClass(Date(1)) + val destinationClass = ExampleDestinationClass() + fieldMapper.processMappedFields(sourceClass, destinationClass) + + expectThat(destinationClass.dateAsLong) + .isEqualTo(1L) + } +} + +@DefaultMappingTarget(ExampleDestinationClass::class) +private class ExampleSourceClass( + @MappedField(mapTo = "dateAsLong") + var date: Date +) + +private class ExampleDestinationClass { + var dateAsLong: Long? = null +} + diff --git a/src/test/java/dev/krud/shapeshift/transformer/TransformerUnitTests.kt b/src/test/java/dev/krud/shapeshift/transformer/TransformerUnitTests.kt new file mode 100644 index 0000000..4011598 --- /dev/null +++ b/src/test/java/dev/krud/shapeshift/transformer/TransformerUnitTests.kt @@ -0,0 +1,139 @@ +/* + * Copyright KRUD 2022 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package dev.krud.shapeshift.transformer + +import org.junit.jupiter.api.Test +import strikt.api.expectThat +import strikt.assertions.isEqualTo +import java.lang.reflect.Field +import java.util.* + +class TransformerUnitTests { + @Test + fun testCommaDelimitedStringToListTransformer() { + val testString = "var1,var2,var3,var4" + val expectedOutcome = Arrays.asList("var1", "var2", "var3", "var4") + val transformer = CommaDelimitedStringToListTransformer() + val outcome: List = transformer.transform( + TestPojo.getField("testString"), + TestPojo.getField("testStringList"), + testString, + TestPojo.INSTANCE, + TestPojo.INSTANCE + ) as List + expectThat(outcome) + .isEqualTo(expectedOutcome) + } + + @Test + fun testStringListToCommaDelimitedStringTransformer() { + val testStringList = Arrays.asList("var1", "var2", "var3", "var4") + val expectedOutcome = "var1,var2,var3,var4" + val transformer = StringListToCommaDelimitedStringTransformer() + val outcome = transformer.transform( + TestPojo.getField("testStringList"), + TestPojo.getField("testString"), + testStringList, + TestPojo.INSTANCE, + TestPojo.INSTANCE + ) + expectThat(outcome) + .isEqualTo(expectedOutcome) + } + + @Test + fun testDateToLongTransformer() { + val testDate = Date(100000) + val expectedOutcome = testDate.time + val transformer = DateToLongTransformer() + val outcome = transformer.transform( + TestPojo.getField("testDate"), + TestPojo.getField("testLong"), + testDate, + TestPojo.INSTANCE, + TestPojo.INSTANCE + ) + expectThat(outcome) + .isEqualTo(expectedOutcome) + } + + @Test + fun testLongToDateTransformer() { + val testLong: Long = 100000 + val expectedOutcome = Date(testLong) + val transformer = LongToDateTransformer() + val outcome = transformer.transform( + TestPojo.getField("testLong"), + TestPojo.getField("testDate"), + testLong, + TestPojo.INSTANCE, + TestPojo.INSTANCE + ) + expectThat(outcome) + .isEqualTo(expectedOutcome) + } + + @Test + fun testToStringTransformer() { + val testInt = 130405 + val expectedOutcome = testInt.toString() + val transformer = ToStringTransformer() + val outcome = transformer.transform( + TestPojo.getField("testInt"), + TestPojo.getField("testString"), + testInt, + TestPojo.INSTANCE, + TestPojo.INSTANCE + ) + expectThat(outcome) + .isEqualTo(expectedOutcome) + } + + @Test + fun testDefaultTransformer() { + val outcome = DefaultTransformer().transform( + TestPojo.getField("testLong"), + TestPojo.getField("testLong"), + 1L, + TestPojo.INSTANCE, + TestPojo.INSTANCE + ) + expectThat(outcome) + .isEqualTo(1L) + } + + internal class TestPojo { + private val testString: String? = null + private val testStringList: List? = null + private val testLong: Long? = null + private val testDouble: Double? = null + private val testInt: Int? = null + private val testDate: Date? = null + private val testEnum = TestEnum.Third + + companion object { + val INSTANCE = TestPojo() + fun getField(name: String?): Field { + return try { + TestPojo::class.java.getDeclaredField(name) + } catch (e: Exception) { + throw RuntimeException(e) + } + } + } + } + + private enum class TestEnum { + First, + Second, + Third + } +} \ No newline at end of file