diff --git a/core/src/main/java/de/moapa/maple/Mapper.java b/core/src/main/java/de/moapa/maple/Mapper.java index f6d49cc45..0da816f81 100644 --- a/core/src/main/java/de/moapa/maple/Mapper.java +++ b/core/src/main/java/de/moapa/maple/Mapper.java @@ -15,6 +15,26 @@ */ package de.moapa.maple; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks an interface as mapper interface and activates the generation of a + * mapper implementation for that interface. + * + * @author Gunnar Morling + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.SOURCE) public @interface Mapper { + /** + * Specifies the type of the mapper implementation to be generated. + * Currently supported values are {@code native} and {@code dozer}. + * + * @return The type of the mapper implementation to be generated. + */ + String value() default ""; } diff --git a/processor/src/main/java/de/moapa/maple/ap/MapperGenerationVisitor.java b/processor/src/main/java/de/moapa/maple/ap/MapperGenerationVisitor.java index d1566977d..a139ebee4 100644 --- a/processor/src/main/java/de/moapa/maple/ap/MapperGenerationVisitor.java +++ b/processor/src/main/java/de/moapa/maple/ap/MapperGenerationVisitor.java @@ -18,8 +18,12 @@ package de.moapa.maple.ap; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; @@ -28,6 +32,7 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementKindVisitor6; import javax.lang.model.util.Elements; @@ -43,6 +48,7 @@ import de.moapa.maple.ap.model.Parameter; import de.moapa.maple.ap.model.Type; import de.moapa.maple.ap.writer.DozerModelWriter; import de.moapa.maple.ap.writer.ModelWriter; +import de.moapa.maple.ap.writer.NativeModelWriter; import static javax.lang.model.util.ElementFilter.methodsIn; @@ -50,12 +56,13 @@ public class MapperGenerationVisitor extends ElementKindVisitor6 { private final static String IMPLEMENTATION_SUFFIX = "Impl"; + private final static String MAPPER_ANNOTATION = "de.moapa.maple.Mapper"; private final static String MAPPING_ANNOTATION = "de.moapa.maple.Mapping"; - private final static String MAPPINGS_ANNOTATION = "de.moapa.maple.Mappings"; - private final static String CONVERTER_TYPE = "de.moapa.maple.converter.Converter"; + private final static String DEFAULT_MAPPER_TYPE = "dozer"; + private final ProcessingEnvironment processingEnvironment; private final Types typeUtils; @@ -90,13 +97,14 @@ public class MapperGenerationVisitor extends ElementKindVisitor6 { throw new RuntimeException( e ); } - ModelWriter modelWriter = new DozerModelWriter(); + ModelWriter modelWriter = model.getMapperType() + .equals( "native" ) ? new NativeModelWriter() : new DozerModelWriter(); modelWriter.writeModel( sourceFile, model ); } private Mapper retrieveModel(TypeElement element) { - return new Mapper( + retrieveMapperType( element ), elementUtils.getPackageOf( element ).getQualifiedName().toString(), element.getSimpleName() + IMPLEMENTATION_SUFFIX, element.getSimpleName().toString(), @@ -104,6 +112,14 @@ public class MapperGenerationVisitor extends ElementKindVisitor6 { ); } + private String retrieveMapperType(TypeElement element) { + + AnnotationMirror mapperAnnotation = getAnnotation( element, MAPPER_ANNOTATION ); + String mapperType = getStringValue( mapperAnnotation, "value" ); + + return mapperType != null && !mapperType.isEmpty() ? mapperType : DEFAULT_MAPPER_TYPE; + } + private List retrieveMethods(TypeElement element) { List methods = new ArrayList(); @@ -112,7 +128,7 @@ public class MapperGenerationVisitor extends ElementKindVisitor6 { Type returnType = retrieveReturnType( oneMethod ); Parameter parameter = retrieveParameter( oneMethod ); - List bindings = retrieveBindings( oneMethod ); + Map bindings = retrieveBindings( oneMethod ); methods.add( new MapperMethod( oneMethod.getSimpleName().toString(), @@ -126,9 +142,11 @@ public class MapperGenerationVisitor extends ElementKindVisitor6 { return methods; } - private List retrieveBindings(ExecutableElement method) { + private Map retrieveBindings(ExecutableElement method) { - List bindings = new ArrayList(); + Map bindings = new LinkedHashMap(); + + retrieveDefaultBindings( method, bindings ); for ( AnnotationMirror annotationMirror : method.getAnnotationMirrors() ) { @@ -137,76 +155,92 @@ public class MapperGenerationVisitor extends ElementKindVisitor6 { .accept( new NameDeterminationVisitor(), null ); if ( MAPPING_ANNOTATION.equals( annotationName ) ) { - bindings.add( retrieveBinding( annotationMirror ) ); + retrieveBinding( annotationMirror, bindings ); } else if ( MAPPINGS_ANNOTATION.equals( annotationName ) ) { - bindings.addAll( retrieveBindings( annotationMirror ) ); + retrieveBindings( annotationMirror, bindings ); } } return bindings; } - private List retrieveBindings(AnnotationMirror annotationMirror) { + private void retrieveDefaultBindings(ExecutableElement method, Map bindings) { - List bindings = new ArrayList(); + Element returnTypeElement = typeUtils.asElement( method.getReturnType() ); - for ( Entry oneAttribute : annotationMirror.getElementValues() - .entrySet() ) { + Set writableTargetProperties = new LinkedHashSet(); - if ( oneAttribute.getKey().getSimpleName().contentEquals( "value" ) ) { - List values = oneAttribute.getValue() - .accept( new AnnotationValueListRetrievingVisitor(), null ); + //collect writable properties of the target type + for ( ExecutableElement oneMethod : methodsIn( returnTypeElement.getEnclosedElements() ) ) { + if ( oneMethod.getSimpleName().toString().startsWith( "set" ) && + oneMethod.getParameters().size() == 1 ) { - for ( AnnotationValue oneAnnotationValue : values ) { - AnnotationMirror oneAnnotation = oneAnnotationValue.accept( - new AnnotationRetrievingVisitor(), - null - ); - bindings.add( retrieveBinding( oneAnnotation ) ); - } + writableTargetProperties.add( oneMethod.getSimpleName().toString().substring( 3 ) ); } } - return bindings; + //collect readable properties of the source type + Element parameterElement = typeUtils.asElement( method.getParameters().get( 0 ).asType() ); + + Set readableSourceProperties = new LinkedHashSet(); + + for ( ExecutableElement oneMethod : methodsIn( parameterElement.getEnclosedElements() ) ) { + if ( oneMethod.getSimpleName().toString().startsWith( "get" ) && + oneMethod.getParameters().isEmpty() && + oneMethod.getReturnType().getKind() != TypeKind.VOID ) { + + readableSourceProperties.add( oneMethod.getSimpleName().toString().substring( 3 ) ); + } + } + + writableTargetProperties.retainAll( readableSourceProperties ); + + for ( String oneWritableProperty : writableTargetProperties ) { + bindings.put( oneWritableProperty, new Binding( oneWritableProperty, oneWritableProperty ) ); + } + } - private Binding retrieveBinding(AnnotationMirror annotationMirror) { + private void retrieveBindings(AnnotationMirror annotationMirror, Map bindings) { + + List values = getAnnotationValueListValue( annotationMirror, "value" ); + + for ( AnnotationValue oneAnnotationValue : values ) { + AnnotationMirror oneAnnotation = oneAnnotationValue.accept( + new AnnotationRetrievingVisitor(), + null + ); + retrieveBinding( oneAnnotation, bindings ); + } + } + + private void retrieveBinding(AnnotationMirror annotationMirror, Map bindings) { + + String sourcePropertyName = getStringValue( annotationMirror, "source" ); + String targetPropertyName = getStringValue( annotationMirror, "target" ); + TypeMirror converterTypeMirror = getTypeMirrorValue( annotationMirror, "converter" ); - String sourcePropertyName = null; - String targetPropertyName = null; Type converterType = null; Type sourceType = null; Type targetType = null; - for ( Entry oneAttribute : annotationMirror.getElementValues() - .entrySet() ) { + if ( converterTypeMirror != null ) { + converterType = getType( typeUtils.asElement( converterTypeMirror ) ); - if ( oneAttribute.getKey().getSimpleName().contentEquals( "source" ) ) { - sourcePropertyName = oneAttribute.getValue().accept( new StringValueRetrievingVisitor(), null ); - } - else if ( oneAttribute.getKey().getSimpleName().contentEquals( "target" ) ) { - targetPropertyName = oneAttribute.getValue().accept( new StringValueRetrievingVisitor(), null ); - } - else if ( oneAttribute.getKey().getSimpleName().contentEquals( "converter" ) ) { - TypeMirror converterTypeMirror = oneAttribute.getValue() - .accept( - new TypeRetrievingVisitor(), null - ); + List converterTypeParameters = getTypeParameters( + converterTypeMirror, + CONVERTER_TYPE + ); - converterType = getType( typeUtils.asElement( converterTypeMirror ) ); - - List converterTypeParameters = getTypeParameters( - converterTypeMirror, - CONVERTER_TYPE - ); - - sourceType = getType( typeUtils.asElement( converterTypeParameters.get( 0 ) ) ); - targetType = getType( typeUtils.asElement( converterTypeParameters.get( 1 ) ) ); - } + sourceType = getType( typeUtils.asElement( converterTypeParameters.get( 0 ) ) ); + targetType = getType( typeUtils.asElement( converterTypeParameters.get( 1 ) ) ); } - return new Binding( sourceType, sourcePropertyName, targetType, targetPropertyName, converterType ); + bindings.put( + sourcePropertyName, + new Binding( sourceType, sourcePropertyName, targetType, targetPropertyName, converterType ) + ); } private Type getType(Element sourceTypeElement) { @@ -261,6 +295,60 @@ public class MapperGenerationVisitor extends ElementKindVisitor6 { ); } + private AnnotationMirror getAnnotation(TypeElement element, String annotationName) { + + for ( AnnotationMirror annotationMirror : element.getAnnotationMirrors() ) { + + if ( annotationName.equals( + annotationMirror.getAnnotationType().asElement().accept( new NameDeterminationVisitor(), null ) + ) ) { + + return annotationMirror; + } + } + + return null; + } + + private String getStringValue(AnnotationMirror annotationMirror, String attributeName) { + + for ( Entry oneAttribute : annotationMirror.getElementValues() + .entrySet() ) { + + if ( oneAttribute.getKey().getSimpleName().contentEquals( attributeName ) ) { + return oneAttribute.getValue().accept( new StringValueRetrievingVisitor(), null ); + } + } + + return null; + } + + private TypeMirror getTypeMirrorValue(AnnotationMirror annotationMirror, String attributeName) { + + for ( Entry oneAttribute : annotationMirror.getElementValues() + .entrySet() ) { + + if ( oneAttribute.getKey().getSimpleName().contentEquals( attributeName ) ) { + return oneAttribute.getValue().accept( new TypeRetrievingVisitor(), null ); + } + } + + return null; + } + + private List getAnnotationValueListValue(AnnotationMirror annotationMirror, String attributeName) { + + for ( Entry oneAttribute : annotationMirror.getElementValues() + .entrySet() ) { + + if ( oneAttribute.getKey().getSimpleName().contentEquals( "value" ) ) { + return oneAttribute.getValue().accept( new AnnotationValueListRetrievingVisitor(), null ); + } + } + + return null; + } + private static class TypeParameterDeterminationVisitor extends TypeKindVisitor6, Void> { @Override diff --git a/processor/src/main/java/de/moapa/maple/ap/model/Binding.java b/processor/src/main/java/de/moapa/maple/ap/model/Binding.java index 475e9335d..bf4f2d79c 100644 --- a/processor/src/main/java/de/moapa/maple/ap/model/Binding.java +++ b/processor/src/main/java/de/moapa/maple/ap/model/Binding.java @@ -18,15 +18,15 @@ package de.moapa.maple.ap.model; public class Binding { private final Type sourceType; - private final String sourceProperty; - private final Type targetType; - private final String targetProperty; - private final Type converterType; + public Binding(String sourceProperty, String targetProperty) { + this( null, sourceProperty, null, targetProperty, null ); + } + public Binding(Type sourceType, String sourceProperty, Type targetType, String targetProperty, Type converterType) { this.sourceType = sourceType; @@ -55,4 +55,8 @@ public class Binding { public Type getConverterType() { return converterType; } + + public boolean isDefault() { + return !sourceProperty.equals( targetProperty ) || ( sourceType != null && !sourceType.equals( targetType ) ) ? false : true; + } } diff --git a/processor/src/main/java/de/moapa/maple/ap/model/Mapper.java b/processor/src/main/java/de/moapa/maple/ap/model/Mapper.java index 186cbaf85..4e084a69a 100644 --- a/processor/src/main/java/de/moapa/maple/ap/model/Mapper.java +++ b/processor/src/main/java/de/moapa/maple/ap/model/Mapper.java @@ -20,17 +20,15 @@ import java.util.List; public class Mapper { + private final String mapperType; private final String packageName; - private final String implementationType; - private final String interfaceType; - private final List mapperMethods; - private final List converters; - public Mapper(String packageName, String implementationType, String interfaceType, List mapperMethods) { + public Mapper(String mapperType, String packageName, String implementationType, String interfaceType, List mapperMethods) { + this.mapperType = mapperType; this.packageName = packageName; this.implementationType = implementationType; this.interfaceType = interfaceType; @@ -43,7 +41,7 @@ public class Mapper { List converters = new ArrayList(); for ( MapperMethod oneMapperMethod : mapperMethods ) { - for ( Binding oneBinding : oneMapperMethod.getBindings() ) { + for ( Binding oneBinding : oneMapperMethod.getBindings().values() ) { if ( oneBinding.getConverterType() != null ) { converters.add( new Converter( @@ -59,6 +57,10 @@ public class Mapper { return converters; } + public String getMapperType() { + return mapperType; + } + public String getPackageName() { return packageName; } diff --git a/processor/src/main/java/de/moapa/maple/ap/model/MapperMethod.java b/processor/src/main/java/de/moapa/maple/ap/model/MapperMethod.java index 7029aae22..cfce25484 100644 --- a/processor/src/main/java/de/moapa/maple/ap/model/MapperMethod.java +++ b/processor/src/main/java/de/moapa/maple/ap/model/MapperMethod.java @@ -15,16 +15,21 @@ */ package de.moapa.maple.ap.model; -import java.util.List; +import java.util.Map; public class MapperMethod { private final String name; private final Type returnType; private final Parameter parameter; - private final List bindings; - public MapperMethod(String name, Type returnType, Parameter parameter, List bindings) { + /** + * Bindings for the properties of this method's converted object. Keyed by + * property name of the source type. + */ + private final Map bindings; + + public MapperMethod(String name, Type returnType, Parameter parameter, Map bindings) { this.name = name; this.returnType = returnType; this.parameter = parameter; @@ -43,7 +48,17 @@ public class MapperMethod { return parameter; } - public List getBindings() { + public Map getBindings() { return bindings; } + + public boolean getNonDefaultBindingExisting() { + for ( Binding oneBinding : bindings.values() ) { + if ( !oneBinding.isDefault() ) { + return true; + } + } + + return false; + } } diff --git a/processor/src/main/java/de/moapa/maple/ap/writer/DozerModelWriter.java b/processor/src/main/java/de/moapa/maple/ap/writer/DozerModelWriter.java index a823508b9..6098721ca 100644 --- a/processor/src/main/java/de/moapa/maple/ap/writer/DozerModelWriter.java +++ b/processor/src/main/java/de/moapa/maple/ap/writer/DozerModelWriter.java @@ -55,5 +55,4 @@ public class DozerModelWriter implements ModelWriter { throw new RuntimeException( e ); } } - } diff --git a/processor/src/main/java/de/moapa/maple/ap/writer/NativeModelWriter.java b/processor/src/main/java/de/moapa/maple/ap/writer/NativeModelWriter.java new file mode 100644 index 000000000..bda756496 --- /dev/null +++ b/processor/src/main/java/de/moapa/maple/ap/writer/NativeModelWriter.java @@ -0,0 +1,58 @@ +/** + * Copyright 2012 Gunnar Morling (http://www.gunnarmorling.de/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.moapa.maple.ap.writer; + +import java.io.BufferedWriter; +import javax.tools.JavaFileObject; + +import de.moapa.maple.ap.model.Mapper; +import freemarker.template.Configuration; +import freemarker.template.DefaultObjectWrapper; +import freemarker.template.Template; + +public class NativeModelWriter implements ModelWriter { + + private final static String TEMPLATE_NAME = "native-mapper-implementation.ftl"; + + private static final Configuration configuration; + + static { + + configuration = new Configuration(); + configuration.setClassForTemplateLoading( NativeModelWriter.class, "/" ); + configuration.setObjectWrapper( new DefaultObjectWrapper() ); + + } + + @Override + public void writeModel(JavaFileObject sourceFile, Mapper model) { + + try { + BufferedWriter writer = new BufferedWriter( sourceFile.openWriter() ); + + Template template = configuration.getTemplate( TEMPLATE_NAME ); + template.process( model, writer ); + writer.flush(); + writer.close(); + } + catch ( RuntimeException e ) { + throw e; + } + catch ( Exception e ) { + throw new RuntimeException( e ); + } + } +} diff --git a/processor/src/main/resources/dozer-mapper-implementation.ftl b/processor/src/main/resources/dozer-mapper-implementation.ftl index f3a9ebbce..ed34facfd 100644 --- a/processor/src/main/resources/dozer-mapper-implementation.ftl +++ b/processor/src/main/resources/dozer-mapper-implementation.ftl @@ -35,11 +35,11 @@ public class ${implementationType} implements ${interfaceType} { BeanMappingBuilder builder = null; <#list mapperMethods as oneMethod> - <#if oneMethod.bindings?has_content> + <#if oneMethod.nonDefaultBindingExisting> builder = new BeanMappingBuilder() { protected void configure() { mapping( ${oneMethod.parameter.type.name}.class, ${oneMethod.returnType.name}.class ) - <#list oneMethod.bindings as oneBinding> + <#list oneMethod.bindings?values as oneBinding> .fields( "${oneBinding.sourceProperty}", "${oneBinding.targetProperty}"<#if oneBinding.converterType??>, customConverter( ${oneBinding.converterType.name}DozerAdapter.class ) ) ; diff --git a/processor/src/main/resources/native-mapper-implementation.ftl b/processor/src/main/resources/native-mapper-implementation.ftl new file mode 100644 index 000000000..26ff28c97 --- /dev/null +++ b/processor/src/main/resources/native-mapper-implementation.ftl @@ -0,0 +1,38 @@ +<#-- + + Copyright 2012 Gunnar Morling (http://www.gunnarmorling.de/) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> +package ${packageName}; + +public class ${implementationType} implements ${interfaceType} { + + <#list mapperMethods as oneMethod> + public ${oneMethod.returnType.name} ${oneMethod.name}(${oneMethod.parameter.type.name} ${oneMethod.parameter.name}) { + + ${oneMethod.returnType.name} convertedObject = new ${oneMethod.returnType.name}(); + + <#list oneMethod.bindings?values as oneBinding> + <#if oneBinding.converterType??> + convertedObject.set${oneBinding.targetProperty?cap_first}( new ${oneBinding.converterType.name}().from( ${oneMethod.parameter.name}.get${oneBinding.sourceProperty?cap_first}() ) ); + <#else> + convertedObject.set${oneBinding.targetProperty?cap_first}( ${oneMethod.parameter.name}.get${oneBinding.sourceProperty?cap_first}() ); + + + + return convertedObject; + } + +} diff --git a/processor/src/test/java/de/moapa/maple/ap/test/CarMapperTest.java b/processor/src/test/java/de/moapa/maple/ap/test/CarMapperTest.java index 2af0f3042..6e6e5b69d 100644 --- a/processor/src/test/java/de/moapa/maple/ap/test/CarMapperTest.java +++ b/processor/src/test/java/de/moapa/maple/ap/test/CarMapperTest.java @@ -16,68 +16,24 @@ package de.moapa.maple.ap.test; import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; import javax.tools.DiagnosticCollector; -import javax.tools.JavaCompiler; -import javax.tools.JavaCompiler.CompilationTask; import javax.tools.JavaFileObject; -import javax.tools.StandardJavaFileManager; -import javax.tools.StandardLocation; -import javax.tools.ToolProvider; -import de.moapa.maple.ap.MappingProcessor; import de.moapa.maple.ap.test.model.Car; import de.moapa.maple.ap.test.model.CarDto; import de.moapa.maple.ap.test.model.CarMapper; import de.moapa.maple.ap.test.model.IntToStringConverter; -import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import static org.fest.assertions.Assertions.assertThat; -public class CarMapperTest { +public class CarMapperTest extends MapperTestBase { private DiagnosticCollector diagnostics; - private JavaCompiler compiler; - private String sourceDir; - private String classOutputDir; - private String sourceOutputDir; - private List classPath; - - @BeforeClass - public void setup() throws Exception { - - compiler = ToolProvider.getSystemJavaCompiler(); - - String basePath = getBasePath(); - - sourceDir = basePath + "/src/test/java"; - classOutputDir = basePath + "/target/compilation-tests/classes"; - sourceOutputDir = basePath + "/target/compilation-tests/generated-sources/mapping"; - - String testDependenciesDir = basePath + "/target/test-dependencies/"; - classPath = Arrays.asList( - new File( testDependenciesDir, "maple.jar" ), - new File( testDependenciesDir, "dozer.jar" ), - new File( testDependenciesDir, "slf4j-api.jar" ), - new File( testDependenciesDir, "slf4j-jdk14.jar" ) - ); - - createOutputDirs(); - - Thread.currentThread().setContextClassLoader( - new URLClassLoader( - new URL[] { new File( classOutputDir ).toURI().toURL() }, - Thread.currentThread().getContextClassLoader() - ) - ); + public CarMapperTest() { + super( "maple.jar", "dozer.jar", "slf4j-api.jar", "slf4j-jdk14.jar" ); } @BeforeMethod @@ -167,86 +123,4 @@ public class CarMapperTest { assertThat( car ).isNotNull(); assertThat( car.getYearOfManufacture() ).isEqualTo( 1980 ); } - - private File[] getSourceFiles(Class... clazz) { - - File[] sourceFiles = new File[clazz.length]; - - for ( int i = 0; i < clazz.length; i++ ) { - - sourceFiles[i] = new File( - sourceDir + - File.separator + - clazz[i].getName().replace( ".", File.separator ) + - ".java" - ); - } - - - return sourceFiles; - } - - public boolean compile(DiagnosticCollector diagnostics, File... sourceFiles) { - - StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, null ); - - Iterable compilationUnits = fileManager.getJavaFileObjects( sourceFiles ); - - try { - fileManager.setLocation( StandardLocation.CLASS_PATH, classPath ); - fileManager.setLocation( StandardLocation.CLASS_OUTPUT, Arrays.asList( new File( classOutputDir ) ) ); - fileManager.setLocation( StandardLocation.SOURCE_OUTPUT, Arrays.asList( new File( sourceOutputDir ) ) ); - } - catch ( IOException e ) { - throw new RuntimeException( e ); - } - - CompilationTask task = compiler.getTask( - null, - fileManager, - diagnostics, - Collections.emptyList(), - null, - compilationUnits - ); - task.setProcessors( Arrays.asList( new MappingProcessor() ) ); - - return task.call(); - } - - private String getBasePath() { - - try { - return new File( "." ).getCanonicalPath(); - } - catch ( IOException e ) { - throw new RuntimeException( e ); - } - } - - private void createOutputDirs() { - - File directory = new File( classOutputDir ); - deleteDirectory( directory ); - directory.mkdirs(); - - directory = new File( sourceOutputDir ); - deleteDirectory( directory ); - directory.mkdirs(); - } - - private void deleteDirectory(File path) { - if ( path.exists() ) { - File[] files = path.listFiles(); - for ( int i = 0; i < files.length; i++ ) { - if ( files[i].isDirectory() ) { - deleteDirectory( files[i] ); - } - else { - files[i].delete(); - } - } - } - path.delete(); - } } diff --git a/processor/src/test/java/de/moapa/maple/ap/test/MapperTestBase.java b/processor/src/test/java/de/moapa/maple/ap/test/MapperTestBase.java new file mode 100644 index 000000000..7cccf89ea --- /dev/null +++ b/processor/src/test/java/de/moapa/maple/ap/test/MapperTestBase.java @@ -0,0 +1,160 @@ +/** + * Copyright 2012 Gunnar Morling (http://www.gunnarmorling.de/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.moapa.maple.ap.test; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaCompiler; +import javax.tools.JavaCompiler.CompilationTask; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; +import javax.tools.ToolProvider; + +import de.moapa.maple.ap.MappingProcessor; +import org.testng.annotations.BeforeClass; + +public abstract class MapperTestBase { + + private JavaCompiler compiler; + private String sourceDir; + private String classOutputDir; + private String sourceOutputDir; + private List classPath; + private List libraries; + + public MapperTestBase(String... libraries) { + this.libraries = Arrays.asList( libraries ); + } + + @BeforeClass + public void setup() throws Exception { + + compiler = ToolProvider.getSystemJavaCompiler(); + + String basePath = getBasePath(); + + sourceDir = basePath + "/src/test/java"; + classOutputDir = basePath + "/target/compilation-tests/classes"; + sourceOutputDir = basePath + "/target/compilation-tests/generated-sources/mapping"; + + String testDependenciesDir = basePath + "/target/test-dependencies/"; + + classPath = new ArrayList(); + for ( String oneLibrary : libraries ) { + classPath.add( new File( testDependenciesDir, oneLibrary ) ); + } + + createOutputDirs(); + + Thread.currentThread().setContextClassLoader( + new URLClassLoader( + new URL[] { new File( classOutputDir ).toURI().toURL() }, + Thread.currentThread().getContextClassLoader() + ) + ); + } + + protected File[] getSourceFiles(Class... clazz) { + + File[] sourceFiles = new File[clazz.length]; + + for ( int i = 0; i < clazz.length; i++ ) { + + sourceFiles[i] = new File( + sourceDir + + File.separator + + clazz[i].getName().replace( ".", File.separator ) + + ".java" + ); + } + + + return sourceFiles; + } + + protected boolean compile(DiagnosticCollector diagnostics, File... sourceFiles) { + + StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, null ); + + Iterable compilationUnits = fileManager.getJavaFileObjects( sourceFiles ); + + try { + fileManager.setLocation( StandardLocation.CLASS_PATH, classPath ); + fileManager.setLocation( StandardLocation.CLASS_OUTPUT, Arrays.asList( new File( classOutputDir ) ) ); + fileManager.setLocation( StandardLocation.SOURCE_OUTPUT, Arrays.asList( new File( sourceOutputDir ) ) ); + } + catch ( IOException e ) { + throw new RuntimeException( e ); + } + + CompilationTask task = compiler.getTask( + null, + fileManager, + diagnostics, + Collections.emptyList(), + null, + compilationUnits + ); + task.setProcessors( Arrays.asList( new MappingProcessor() ) ); + + return task.call(); + } + + private String getBasePath() { + + try { + return new File( "." ).getCanonicalPath(); + } + catch ( IOException e ) { + throw new RuntimeException( e ); + } + } + + private void createOutputDirs() { + + File directory = new File( classOutputDir ); + deleteDirectory( directory ); + directory.mkdirs(); + + directory = new File( sourceOutputDir ); + deleteDirectory( directory ); + directory.mkdirs(); + } + + private void deleteDirectory(File path) { + if ( path.exists() ) { + File[] files = path.listFiles(); + for ( int i = 0; i < files.length; i++ ) { + if ( files[i].isDirectory() ) { + deleteDirectory( files[i] ); + } + else { + files[i].delete(); + } + } + } + path.delete(); + } + +} \ No newline at end of file diff --git a/processor/src/test/java/de/moapa/maple/ap/test/NativeCarMapperTest.java b/processor/src/test/java/de/moapa/maple/ap/test/NativeCarMapperTest.java new file mode 100644 index 000000000..9edca46a4 --- /dev/null +++ b/processor/src/test/java/de/moapa/maple/ap/test/NativeCarMapperTest.java @@ -0,0 +1,131 @@ +/** + * Copyright 2012 Gunnar Morling (http://www.gunnarmorling.de/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.moapa.maple.ap.test; + +import java.io.File; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaFileObject; + +import de.moapa.maple.ap.test.model.Car; +import de.moapa.maple.ap.test.model.CarDto; +import de.moapa.maple.ap.test.model.IntToStringConverter; +import de.moapa.maple.ap.test.model.NativeCarMapper; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.fest.assertions.Assertions.assertThat; + +public class NativeCarMapperTest extends MapperTestBase { + + private DiagnosticCollector diagnostics; + + public NativeCarMapperTest() { + super( "maple.jar" ); + } + + @BeforeMethod + public void generateMapperImplementation() { + + diagnostics = new DiagnosticCollector(); + File[] sourceFiles = getSourceFiles( + Car.class, + CarDto.class, + NativeCarMapper.class, + IntToStringConverter.class + ); + + boolean compilationSuccessful = compile( diagnostics, sourceFiles ); + + assertThat( compilationSuccessful ).describedAs( "Compilation failed: " + diagnostics.getDiagnostics() ) + .isTrue(); + } + + @Test + public void shouldProvideMapperInstance() throws Exception { + + assertThat( NativeCarMapper.INSTANCE ).isNotNull(); + } + + @Test + public void shouldMapAttributeByName() { + + //given + Car car = new Car( "Morris", 2, 1980 ); + + //when + CarDto carDto = NativeCarMapper.INSTANCE.carToCarDto( car ); + + //then + assertThat( carDto ).isNotNull(); + assertThat( carDto.getMake() ).isEqualTo( car.getMake() ); + } + + @Test + public void shouldMapAttributeWithCustomMapping() { + + //given + Car car = new Car( "Morris", 2, 1980 ); + + //when + CarDto carDto = NativeCarMapper.INSTANCE.carToCarDto( car ); + + //then + assertThat( carDto ).isNotNull(); + assertThat( carDto.getSeatCount() ).isEqualTo( car.getNumberOfSeats() ); + } + + @Test(enabled = false) + public void shouldConsiderCustomMappingForReverseMapping() { + + //given + CarDto carDto = new CarDto( "Morris", 2, "1980" ); + + //when + Car car = NativeCarMapper.INSTANCE.carDtoToCar( carDto ); + + //then + assertThat( car ).isNotNull(); + assertThat( car.getNumberOfSeats() ).isEqualTo( carDto.getSeatCount() ); + } + + @Test + public void shouldApplyConverter() { + + //given + Car car = new Car( "Morris", 2, 1980 ); + + //when + CarDto carDto = NativeCarMapper.INSTANCE.carToCarDto( car ); + + //then + assertThat( carDto ).isNotNull(); + assertThat( carDto.getManufacturingYear() ).isEqualTo( "1980" ); + } + + @Test(enabled = false) + public void shouldApplyConverterForReverseMapping() { + + //given + CarDto carDto = new CarDto( "Morris", 2, "1980" ); + + //when + Car car = NativeCarMapper.INSTANCE.carDtoToCar( carDto ); + + //then + assertThat( car ).isNotNull(); + assertThat( car.getYearOfManufacture() ).isEqualTo( 1980 ); + } +} diff --git a/processor/src/test/java/de/moapa/maple/ap/test/model/NativeCarMapper.java b/processor/src/test/java/de/moapa/maple/ap/test/model/NativeCarMapper.java new file mode 100644 index 000000000..5a9a4195d --- /dev/null +++ b/processor/src/test/java/de/moapa/maple/ap/test/model/NativeCarMapper.java @@ -0,0 +1,35 @@ +/** + * Copyright 2012 Gunnar Morling (http://www.gunnarmorling.de/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.moapa.maple.ap.test.model; + +import de.moapa.maple.Mapper; +import de.moapa.maple.Mappers; +import de.moapa.maple.Mapping; +import de.moapa.maple.Mappings; + +@Mapper("native") +public interface NativeCarMapper { + + NativeCarMapper INSTANCE = Mappers.getMapper( NativeCarMapper.class ); + + @Mappings({ + @Mapping(source = "numberOfSeats", target = "seatCount"), + @Mapping(source = "yearOfManufacture", target = "manufacturingYear", converter = IntToStringConverter.class) + }) + CarDto carToCarDto(Car car); + + Car carDtoToCar(CarDto carDto); +}