#74 Adding support for generating methods from abstract classes

This commit is contained in:
Gunnar Morling 2013-11-30 23:05:01 +01:00
parent 8be25e70e5
commit d38a72c534
10 changed files with 277 additions and 25 deletions

View File

@ -224,6 +224,11 @@ public class MappingProcessor extends AbstractProcessor {
return e;
}
@Override
public TypeElement visitTypeAsClass(TypeElement e, Void p) {
return e;
}
}, null
);
}

View File

@ -24,6 +24,9 @@ import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.annotation.Generated;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import org.mapstruct.ap.util.TypeFactory;
@ -35,8 +38,11 @@ import org.mapstruct.ap.util.TypeFactory;
*/
public class Mapper extends AbstractModelElement {
private static final String IMPLEMENTATION_SUFFIX = "Impl";
private final TypeFactory typeFactory;
private final String packageName;
private final boolean superTypeIsInterface;
private final String interfaceName;
private final String implementationName;
private final List<Annotation> annotations;
@ -44,9 +50,11 @@ public class Mapper extends AbstractModelElement {
private final List<MapperReference> referencedMappers;
private final Options options;
public Mapper(TypeFactory typeFactory, String packageName, String interfaceName, String implementationName,
List<MappingMethod> mappingMethods, List<MapperReference> referencedMappers, Options options) {
private Mapper(TypeFactory typeFactory, String packageName, boolean superTypeIsInterface, String interfaceName,
String implementationName, List<MappingMethod> mappingMethods,
List<MapperReference> referencedMappers, Options options) {
this.packageName = packageName;
this.superTypeIsInterface = superTypeIsInterface;
this.interfaceName = interfaceName;
this.implementationName = implementationName;
this.annotations = new ArrayList<Annotation>();
@ -56,6 +64,59 @@ public class Mapper extends AbstractModelElement {
this.typeFactory = typeFactory;
}
public static class Builder {
private TypeFactory typeFactory;
private TypeElement element;
private List<MappingMethod> mappingMethods;
private List<MapperReference> mapperReferences;
private Options options;
private Elements elementUtils;
public Builder element(TypeElement element) {
this.element = element;
return this;
}
public Builder mappingMethods(List<MappingMethod> mappingMethods) {
this.mappingMethods = mappingMethods;
return this;
}
public Builder mapperReferences(List<MapperReference> mapperReferences) {
this.mapperReferences = mapperReferences;
return this;
}
public Builder options(Options options) {
this.options = options;
return this;
}
public Builder typeFactory(TypeFactory typeFactory) {
this.typeFactory = typeFactory;
return this;
}
public Builder elementUtils(Elements elementUtils) {
this.elementUtils = elementUtils;
return this;
}
public Mapper build() {
return new Mapper(
typeFactory,
elementUtils.getPackageOf( element ).getQualifiedName().toString(),
element.getKind() == ElementKind.INTERFACE ? true : false,
element.getSimpleName().toString(),
element.getSimpleName() + IMPLEMENTATION_SUFFIX,
mappingMethods,
mapperReferences,
options
);
}
}
@Override
public SortedSet<Type> getImportTypes() {
SortedSet<Type> importedTypes = new TreeSet<Type>();
@ -121,6 +182,16 @@ public class Mapper extends AbstractModelElement {
return packageName;
}
/**
* Whether the mapper super-type is an interface or not.
*
* @return {@code true} if the mapper is generated from an interface, {@code false} when generated from an abstract
* class.
*/
public boolean isSuperTypeInterface() {
return superTypeIsInterface;
}
public String getInterfaceName() {
return interfaceName;
}

View File

@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import org.mapstruct.ap.model.Parameter;
import org.mapstruct.ap.model.Type;
@ -226,4 +227,11 @@ public class Method {
return null;
}
/**
* Whether an implementation of this method must be generated or not.
*/
public boolean requiresImplementation() {
return declaringMapper == null && executable.getModifiers().contains( Modifier.ABSTRACT );
}
}

View File

@ -72,8 +72,6 @@ import org.mapstruct.ap.util.TypeFactory;
*/
public class MapperCreationProcessor implements ModelElementProcessor<List<Method>, Mapper> {
private static final String IMPLEMENTATION_SUFFIX = "Impl";
private Elements elementUtils;
private Types typeUtils;
private Messager messager;
@ -109,15 +107,14 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
List<MappingMethod> mappingMethods = getMappingMethods( methods, unmappedTargetPolicy );
List<MapperReference> mapperReferences = getReferencedMappers( element );
return new Mapper(
typeFactory,
elementUtils.getPackageOf( element ).getQualifiedName().toString(),
element.getSimpleName().toString(),
element.getSimpleName() + IMPLEMENTATION_SUFFIX,
mappingMethods,
mapperReferences,
options
);
return new Mapper.Builder()
.element( element )
.mappingMethods( mappingMethods )
.mapperReferences( mapperReferences )
.options( options )
.typeFactory( typeFactory )
.elementUtils( elementUtils )
.build();
}
/**
@ -160,7 +157,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
List<MappingMethod> mappingMethods = new ArrayList<MappingMethod>();
for ( Method method : methods ) {
if ( method.getDeclaringMapper() != null ) {
if ( !method.requiresImplementation() ) {
continue;
}
@ -617,6 +614,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
*
* @param sourceType the source type
* @param targetType the target type
*
* @return {@code true} if the target type has a constructor accepting the given source type, {@code false}
* otherwise.
*/

View File

@ -24,6 +24,7 @@ import java.util.List;
import java.util.Map;
import javax.annotation.processing.Messager;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
@ -78,27 +79,26 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
* Retrieves the mapping methods declared by the given mapper type.
*
* @param element The type of interest
* @param implementationRequired Whether an implementation of this type must be generated or
* not. {@code true} if the type is the currently processed
* mapper interface, {@code false} if the given type is one
* @param mapperRequiresImplementation Whether an implementation of this type must be generated or not. {@code true}
* if the type is the currently processed mapper interface, {@code false} if the given type is one
* referred to via {@code Mapper#uses()}.
*
* @return All mapping methods declared by the given type
*/
private List<Method> retrieveMethods(TypeElement element, boolean implementationRequired) {
private List<Method> retrieveMethods(TypeElement element, boolean mapperRequiresImplementation) {
List<Method> methods = new ArrayList<Method>();
MapperPrism mapperPrism = implementationRequired ? MapperPrism.getInstanceOn( element ) : null;
MapperPrism mapperPrism = mapperRequiresImplementation ? MapperPrism.getInstanceOn( element ) : null;
for ( ExecutableElement executable : methodsIn( element.getEnclosedElements() ) ) {
Method method = getMethod( element, executable, implementationRequired );
Method method = getMethod( element, executable, mapperRequiresImplementation );
if ( method != null ) {
methods.add( method );
}
}
//Add all methods of used mappers in order to reference them in the aggregated model
if ( implementationRequired ) {
if ( mapperRequiresImplementation ) {
for ( TypeMirror usedMapper : mapperPrism.uses() ) {
methods.addAll(
retrieveMethods(
@ -112,12 +112,14 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
return methods;
}
private Method getMethod(TypeElement element, ExecutableElement method, boolean implementationRequired) {
private Method getMethod(TypeElement element, ExecutableElement method, boolean mapperRequiresImplementation) {
List<Parameter> parameters = executables.retrieveParameters( method );
Type returnType = executables.retrieveReturnType( method );
//add method with property mappings if an implementation needs to be generated
if ( implementationRequired ) {
boolean methodRequiresImplementation = method.getModifiers().contains( Modifier.ABSTRACT );
if ( mapperRequiresImplementation && methodRequiresImplementation ) {
List<Parameter> sourceParameters = extractSourceParameters( parameters );
Parameter targetParameter = extractTargetParameter( parameters );
Type resultType = selectResultType( returnType, targetParameter );
@ -144,7 +146,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
else if ( parameters.size() == 1 ) {
return
Method.forReferencedMethod(
typeFactory.getType( element ),
mapperRequiresImplementation ? null : typeFactory.getType( element ),
method,
parameters,
returnType

View File

@ -31,7 +31,7 @@ import ${importedType.fullyQualifiedName};
<#list annotations as annotation>
<#nt><@includeModel object=annotation/>
</#list>
public class ${implementationName} implements ${interfaceName} {
public class ${implementationName} <#if superTypeInterface>implements<#else>extends</#if> ${interfaceName} {
<#list referencedMappers as mapper>
<@includeModel object=mapper/>

View File

@ -0,0 +1,47 @@
/**
* Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* 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 org.mapstruct.ap.test.abstractclass;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.MapperTestBase;
import org.mapstruct.ap.testutil.WithClasses;
import org.testng.annotations.Test;
import static org.fest.assertions.Assertions.assertThat;
/**
* Test for the generation of implementation of abstract base classes.
*
* @author Gunnar Morling
*/
@WithClasses({ Source.class, Target.class, SourceTargetMapper.class })
public class AbstractClassTest extends MapperTestBase {
@Test
@IssueKey("64")
public void shouldCreateImplementationOfAbstractMethod() {
Source source = new Source();
Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source );
assertThat( target ).isNotNull();
assertThat( target.getSize() ).isEqualTo( Long.valueOf( 181 ) );
assertThat( target.getBirthday() ).isEqualTo( "26.04.1948" );
}
}

View File

@ -0,0 +1,41 @@
/**
* Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* 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 org.mapstruct.ap.test.abstractclass;
import java.util.Calendar;
public class Source {
private final int size;
private final Calendar birthday;
public Source() {
size = 181;
birthday = Calendar.getInstance();
birthday.set( 1948, 3, 26 );
}
public int getSize() {
return size;
}
public Calendar getBirthday() {
return birthday;
}
}

View File

@ -0,0 +1,39 @@
/**
* Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* 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 org.mapstruct.ap.test.abstractclass;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public abstract class SourceTargetMapper {
public static final SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
public abstract Target sourceToTarget(Source source);
protected String calendarToString(Calendar calendar) {
DateFormat format = new SimpleDateFormat( "dd.MM.yyyy" );
return format.format( calendar.getTime() );
}
}

View File

@ -0,0 +1,41 @@
/**
* Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* 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 org.mapstruct.ap.test.abstractclass;
public class Target {
private Long size;
private String birthday;
public Long getSize() {
return size;
}
public void setSize(Long size) {
this.size = size;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
}