Adding initial support for native mappers (work in progress)

This commit is contained in:
Gunnar Morling 2012-06-10 22:40:28 +02:00
parent 069e4f3a80
commit df25e12b0d
13 changed files with 621 additions and 197 deletions

View File

@ -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 "";
}

View File

@ -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<Void, Void> {
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<Void, Void> {
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<Void, Void> {
);
}
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<MapperMethod> retrieveMethods(TypeElement element) {
List<MapperMethod> methods = new ArrayList<MapperMethod>();
@ -112,7 +128,7 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
Type returnType = retrieveReturnType( oneMethod );
Parameter parameter = retrieveParameter( oneMethod );
List<Binding> bindings = retrieveBindings( oneMethod );
Map<String, Binding> bindings = retrieveBindings( oneMethod );
methods.add(
new MapperMethod(
oneMethod.getSimpleName().toString(),
@ -126,9 +142,11 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
return methods;
}
private List<Binding> retrieveBindings(ExecutableElement method) {
private Map<String, Binding> retrieveBindings(ExecutableElement method) {
List<Binding> bindings = new ArrayList<Binding>();
Map<String, Binding> bindings = new LinkedHashMap<String, Binding>();
retrieveDefaultBindings( method, bindings );
for ( AnnotationMirror annotationMirror : method.getAnnotationMirrors() ) {
@ -137,76 +155,92 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
.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<Binding> retrieveBindings(AnnotationMirror annotationMirror) {
private void retrieveDefaultBindings(ExecutableElement method, Map<String, Binding> bindings) {
List<Binding> bindings = new ArrayList<Binding>();
Element returnTypeElement = typeUtils.asElement( method.getReturnType() );
for ( Entry<? extends ExecutableElement, ? extends AnnotationValue> oneAttribute : annotationMirror.getElementValues()
.entrySet() ) {
Set<String> writableTargetProperties = new LinkedHashSet<String>();
if ( oneAttribute.getKey().getSimpleName().contentEquals( "value" ) ) {
List<? extends AnnotationValue> 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<String> readableSourceProperties = new LinkedHashSet<String>();
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<String, Binding> bindings) {
List<? extends AnnotationValue> values = getAnnotationValueListValue( annotationMirror, "value" );
for ( AnnotationValue oneAnnotationValue : values ) {
AnnotationMirror oneAnnotation = oneAnnotationValue.accept(
new AnnotationRetrievingVisitor(),
null
);
retrieveBinding( oneAnnotation, bindings );
}
}
private void retrieveBinding(AnnotationMirror annotationMirror, Map<String, Binding> 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<? extends ExecutableElement, ? extends AnnotationValue> 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<? extends TypeMirror> converterTypeParameters = getTypeParameters(
converterTypeMirror,
CONVERTER_TYPE
);
converterType = getType( typeUtils.asElement( converterTypeMirror ) );
List<? extends TypeMirror> 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<Void, Void> {
);
}
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<? extends ExecutableElement, ? extends AnnotationValue> 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<? extends ExecutableElement, ? extends AnnotationValue> oneAttribute : annotationMirror.getElementValues()
.entrySet() ) {
if ( oneAttribute.getKey().getSimpleName().contentEquals( attributeName ) ) {
return oneAttribute.getValue().accept( new TypeRetrievingVisitor(), null );
}
}
return null;
}
private List<? extends AnnotationValue> getAnnotationValueListValue(AnnotationMirror annotationMirror, String attributeName) {
for ( Entry<? extends ExecutableElement, ? extends AnnotationValue> 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<List<? extends TypeMirror>, Void> {
@Override

View File

@ -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;
}
}

View File

@ -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<MapperMethod> mapperMethods;
private final List<Converter> converters;
public Mapper(String packageName, String implementationType, String interfaceType, List<MapperMethod> mapperMethods) {
public Mapper(String mapperType, String packageName, String implementationType, String interfaceType, List<MapperMethod> mapperMethods) {
this.mapperType = mapperType;
this.packageName = packageName;
this.implementationType = implementationType;
this.interfaceType = interfaceType;
@ -43,7 +41,7 @@ public class Mapper {
List<Converter> converters = new ArrayList<Converter>();
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;
}

View File

@ -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<Binding> bindings;
public MapperMethod(String name, Type returnType, Parameter parameter, List<Binding> bindings) {
/**
* Bindings for the properties of this method's converted object. Keyed by
* property name of the source type.
*/
private final Map<String, Binding> bindings;
public MapperMethod(String name, Type returnType, Parameter parameter, Map<String, Binding> bindings) {
this.name = name;
this.returnType = returnType;
this.parameter = parameter;
@ -43,7 +48,17 @@ public class MapperMethod {
return parameter;
}
public List<Binding> getBindings() {
public Map<String, Binding> getBindings() {
return bindings;
}
public boolean getNonDefaultBindingExisting() {
for ( Binding oneBinding : bindings.values() ) {
if ( !oneBinding.isDefault() ) {
return true;
}
}
return false;
}
}

View File

@ -55,5 +55,4 @@ public class DozerModelWriter implements ModelWriter {
throw new RuntimeException( e );
}
}
}

View File

@ -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 );
}
}
}

View File

@ -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 )</#if> )
</#list>
;

View File

@ -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}() );
</#if>
</#list>
return convertedObject;
}
</#list>
}

View File

@ -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<JavaFileObject> diagnostics;
private JavaCompiler compiler;
private String sourceDir;
private String classOutputDir;
private String sourceOutputDir;
private List<File> 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<JavaFileObject> diagnostics, File... sourceFiles) {
StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, null );
Iterable<? extends JavaFileObject> 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.<String>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();
}
}

View File

@ -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<File> classPath;
private List<String> 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<File>();
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<JavaFileObject> diagnostics, File... sourceFiles) {
StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, null );
Iterable<? extends JavaFileObject> 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.<String>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();
}
}

View File

@ -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<JavaFileObject> diagnostics;
public NativeCarMapperTest() {
super( "maple.jar" );
}
@BeforeMethod
public void generateMapperImplementation() {
diagnostics = new DiagnosticCollector<JavaFileObject>();
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 );
}
}

View File

@ -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);
}