Skip to content

Commit

Permalink
#53: Implement first support for abstract class mappers with embedded…
Browse files Browse the repository at this point in the history
… custom mapping methods
  • Loading branch information
slemesle committed Nov 11, 2015
1 parent bf04caf commit fc8d6d2
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 47 deletions.
Expand Up @@ -115,13 +115,13 @@ void emitCustomMappersFields(JavaWriter writer, boolean assign) throws IOExcepti

/**
* Adds a custom mapping method to the registry for later use at codegen.
*
* @param method
* @param method
* @param immutable
* @param ignoreAbstract
*/
private void pushCustomMapper(final TypeElement element, final MethodWrapper method, Boolean immutable) {
private void pushCustomMapper(final TypeElement element, final MethodWrapper method, Boolean immutable, boolean ignoreAbstract) {
MappingBuilder res = null;
String customMapperFieldName = buildMapperFieldName(element);
String customMapperFieldName = ignoreAbstract ? "this" : buildMapperFieldName(element);

InOutType inOutType = method.inOutType();
String methodCall = String.format("%s.%s", customMapperFieldName, method.getSimpleName());
Expand Down Expand Up @@ -163,24 +163,7 @@ private void collectCustomMappers() {

final TypeElement element = context.elements.getTypeElement(customMapper.replace(".class", ""));

final List<ExecutableElement> methods = ElementFilter.methodsIn(element.getEnclosedElements());
final HashMap<CustomMapperKey, CustomMapperEntry> customInOutTypes = new HashMap<CustomMapperKey, CustomMapperEntry>();
for (ExecutableElement method : methods) {
MethodWrapper methodWrapper = new MethodWrapper(method, context);
if (isValidCustomMapping(methodWrapper)) {

if (methodWrapper.isCustomMapper()) {
pushCustomMapper(element, methodWrapper, null);
addCustomInOutType(customInOutTypes, methodWrapper);
} else {
pushMappingInterceptor(element, methodWrapper);
}
mappingMethodCount++;
}
}

// Create defaults custom mappers if immutable or mutable is missing
addMissingMappings(customInOutTypes, element);
mappingMethodCount += collectCustomMethods(element, false);


if (mappingMethodCount == 0) {
Expand All @@ -202,12 +185,39 @@ private void collectCustomMappers() {

}

private void addMissingMappings(HashMap<CustomMapperKey, CustomMapperEntry> customInOutTypes, TypeElement element) {
private int collectCustomMethods(TypeElement element, boolean ignoreAbstract) {
int mappingMethodCount = 0;
final List<ExecutableElement> methods = ElementFilter.methodsIn(element.getEnclosedElements());
final HashMap<CustomMapperKey, CustomMapperEntry> customInOutTypes = new HashMap<CustomMapperKey, CustomMapperEntry>();
for (ExecutableElement method : methods) {
MethodWrapper methodWrapper = new MethodWrapper(method, context);
// We should ignore abstract methods if parsing an abstract mapper class
if (ignoreAbstract && methodWrapper.isAbstract()){
continue;
}
if (isValidCustomMapping(methodWrapper)) {

if (methodWrapper.isCustomMapper()) {
pushCustomMapper(element, methodWrapper, null, ignoreAbstract);
addCustomInOutType(customInOutTypes, methodWrapper);
} else {
pushMappingInterceptor(element, methodWrapper);
}
mappingMethodCount++;
}
}

// Create defaults custom mappers if immutable or mutable is missing
addMissingMappings(customInOutTypes, element, ignoreAbstract);
return mappingMethodCount;
}

private void addMissingMappings(HashMap<CustomMapperKey, CustomMapperEntry> customInOutTypes, TypeElement element, boolean ignoreAbstract) {
for (Map.Entry<CustomMapperKey, CustomMapperEntry> entry : customInOutTypes.entrySet()) {
if (entry.getValue().updateGraphMethod == null) {
pushCustomMapper(element, entry.getValue().immutableMethod, Boolean.TRUE);
pushCustomMapper(element, entry.getValue().immutableMethod, Boolean.TRUE, ignoreAbstract);
} else if (entry.getValue().immutableMethod == null) {
pushCustomMapper(element, entry.getValue().updateGraphMethod, Boolean.FALSE);
pushCustomMapper(element, entry.getValue().updateGraphMethod, Boolean.FALSE, ignoreAbstract);
}
}
}
Expand Down Expand Up @@ -313,6 +323,10 @@ public void addFields(List<TypeElement> childFields) {
}
}

public void addMappersElementMethods(TypeElement mapperInterface) {
collectCustomMethods(mapperInterface, true);
}

class CustomMapperKey {
final TypeMirror in;
final TypeMirror out;
Expand Down
Expand Up @@ -110,11 +110,7 @@ public void build() throws IOException {
if (mapper.ioC == IoC.SPRING){
writer.emitAnnotation("org.springframework.stereotype.Service");
}
if (mapper.isFinalMappers()) {
writer.beginType(adapterName, "class", EnumSet.of(PUBLIC, FINAL), null, strippedTypeName);
} else {
writer.beginType(adapterName, "class", EnumSet.of(PUBLIC), null, strippedTypeName);
}
openClassBlock(writer, adapterName, strippedTypeName);
writer.emitEmptyLine();
firstMethod = false;
}
Expand All @@ -135,6 +131,21 @@ public void build() throws IOException {
mapper.reportUnused();
}

private void openClassBlock(JavaWriter writer, String adapterName, String strippedTypeName) throws IOException {
String[] interfaceName = new String[]{strippedTypeName};
String className = strippedTypeName;
Set<Modifier> modifiers = EnumSet.of(PUBLIC);
if (mapper.isAbstractClass()){
interfaceName = new String[]{};
} else {
className = null;
}
if (mapper.isFinalMappers()) {
modifiers = EnumSet.of(PUBLIC, FINAL);
}
writer.beginType(adapterName, "class", modifiers, className, interfaceName);
}

private void buildConstructor(JavaWriter writer, String adapterName) throws IOException {
mapper.emitSourceFields(writer);
mapper.emitCustomMappersFields(writer, false);
Expand Down
Expand Up @@ -17,6 +17,7 @@
package fr.xebia.extras.selma.codegen;

import fr.xebia.extras.selma.Mapper;
import fr.xebia.extras.selma.SelmaConstants;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
Expand Down Expand Up @@ -69,14 +70,15 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
types = processingEnv.getTypeUtils();
populateAllMappers(roundEnv);

try {
generateMappingClassses();
} catch (IOException e) {
e.printStackTrace();
StringWriter writer = new StringWriter();
e.printStackTrace(new PrintWriter(writer));
error(writer.toString(), null);
}
try {
generateMappingClassses();
} catch (IOException e) {
e.printStackTrace();
StringWriter writer = new StringWriter();
e.printStackTrace(new PrintWriter(writer));
error(writer.toString(), null);
}
remainingMapperTypes.clear();
}
return res;
}
Expand All @@ -93,7 +95,8 @@ private void generateMappingClassses() throws IOException {
private void populateAllMappers(RoundEnvironment roundEnv) {

for (Element element : roundEnv.getElementsAnnotatedWith(Mapper.class)) {
if (!isValidMapperUse(element)) {
boolean abstractClass = isAbstractClass(element);
if (isSelmaGenerated(element) || !isValidMapperUse(element)) {
continue;
} else {
TypeElement typeElement = (TypeElement) element;
Expand All @@ -102,7 +105,10 @@ private void populateAllMappers(RoundEnvironment roundEnv) {

for (Element method : methods) {
ExecutableElement executableElement = (ExecutableElement) method;

// We should only process abstract method as mapper to implement
if (abstractClass && !isAbstractMethod(executableElement)) {
continue;
}
if (isValidMapperMethod(executableElement)) {
putMapper(element, executableElement);
}
Expand All @@ -112,6 +118,14 @@ private void populateAllMappers(RoundEnvironment roundEnv) {
}
}

private boolean isAbstractMethod(ExecutableElement executableElement) {
return executableElement.getModifiers().contains(Modifier.ABSTRACT);
}

private boolean isSelmaGenerated(Element element) {
return (""+element.getSimpleName()).endsWith(SelmaConstants.MAPPER_CLASS_SUFFIX);
}

private boolean isValidMapperMethod(ExecutableElement executableElement) {

if (exclusions.contains(executableElement)) {
Expand Down Expand Up @@ -165,14 +179,26 @@ private void putMapper(Element element, ExecutableElement executableElement) {

private boolean isValidMapperUse(Element element) {
boolean res = true;
if (element.getKind() != ElementKind.INTERFACE) {
error(element, "@Mapper can only be used on interface not on %s", element.getKind());
if (element.getKind() != ElementKind.INTERFACE && !isAbstractClass(element)) {
error(element, "@Mapper can only be used on interface or public abstract class");
res = false;
}

return res;
}

private boolean isAbstractClass(Element element) {
boolean res = false;
if (element.getKind() == ElementKind.CLASS) {
TypeElement typeElement = (TypeElement) element;
res = typeElement.getModifiers().contains(Modifier.ABSTRACT) &&
typeElement.getModifiers().contains(Modifier.PUBLIC) &&
!typeElement.getModifiers().contains(Modifier.FINAL);

}
return res;
}

private void error(String msg, Element element) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, element);
}
Expand Down
Expand Up @@ -22,13 +22,13 @@
import fr.xebia.extras.selma.IoC;
import fr.xebia.extras.selma.Mapper;

import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import java.io.IOException;
import java.util.List;

import static fr.xebia.extras.selma.IgnoreMissing.ALL;
import static fr.xebia.extras.selma.IgnoreMissing.DEFAULT;
import static fr.xebia.extras.selma.IgnoreMissing.NONE;
import static fr.xebia.extras.selma.IgnoreMissing.*;

/**
* Class used to wrap the Mapper Annotation
Expand All @@ -54,6 +54,7 @@ public class MapperWrapper {
private final IgnoreMissing ignoreMissing;
final IoC ioC;
private final CollectionMappingStrategy collectionMappingStrategy;
private final boolean abstractClass;

public MapperWrapper(MapperGeneratorContext context, TypeElement mapperInterface) {
this.context = context;
Expand All @@ -72,7 +73,7 @@ public MapperWrapper(MapperGeneratorContext context, TypeElement mapperInterface

IgnoreMissing missing = IgnoreMissing.valueOf(mapper.getAsString(WITH_IGNORE_MISSING));
if (missing == DEFAULT) {
if (configuration.isIgnoreMissingProperties()){
if (configuration.isIgnoreMissingProperties()) {
ignoreMissing = ALL;
} else {
ignoreMissing = NONE;
Expand All @@ -91,6 +92,9 @@ public MapperWrapper(MapperGeneratorContext context, TypeElement mapperInterface
// Here we collect custom mappers
customMappers = new CustomMapperWrapper(mapper, context);
mappingRegistry.customMappers(customMappers);
if (mapperInterface.getModifiers().contains(Modifier.ABSTRACT)){
customMappers.addMappersElementMethods(mapperInterface);
}

enumMappers = new EnumMappersWrapper(withEnums(), context, mapperInterface);
mappingRegistry.enumMappers(enumMappers);
Expand All @@ -100,6 +104,8 @@ public MapperWrapper(MapperGeneratorContext context, TypeElement mapperInterface

source = new SourceWrapper(mapper, context);

abstractClass = mapperInterface.getModifiers().contains(Modifier.ABSTRACT) &&
mapperInterface.getKind() == ElementKind.CLASS;
}


Expand Down Expand Up @@ -166,6 +172,7 @@ public CustomMapperWrapper customMappers() {

/**
* Method used to collect dependencies from mapping methods that need fields and constructor init
*
* @param maps maps annotation we want to collect
*/
public void collectMaps(MapsWrapper maps) {
Expand All @@ -185,11 +192,15 @@ public void emitSourceAssigns(JavaWriter writer) throws IOException {
source.emitAssigns(writer);
}

public IgnoreMissing ignoreMissing(){
public IgnoreMissing ignoreMissing() {
return ignoreMissing;
}

public boolean allowCollectionGetter() {
return collectionMappingStrategy == CollectionMappingStrategy.ALLOW_GETTER;
}

public boolean isAbstractClass() {
return abstractClass;
}
}
Expand Up @@ -203,4 +203,7 @@ public boolean hasFields() {
}


public boolean isAbstract() {
return method.getModifiers().contains(Modifier.ABSTRACT);
}
}
@@ -0,0 +1,58 @@
/*
* Copyright 2013 Séven Le Mesle
*
* 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 fr.xebia.extras.selma.it.custom.mapper;

import fr.xebia.extras.selma.Mapper;
import fr.xebia.extras.selma.Maps;
import fr.xebia.extras.selma.beans.*;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;

/**
* Created by slemesle on 19/11/14.
*/
@Mapper(
withIgnoreFields = {"fr.xebia.extras.selma.beans.PersonIn.male", "fr.xebia.extras.selma.beans.PersonOut.biography"}
)
public abstract class AbstractMapperWithCustom {

public static final int NUMBER_INCREMENT = 10000;

public abstract PersonOut asPersonOut(PersonIn in);

public abstract CityOut asCityOut(CityIn in);

/**
* Custom mapper inside the Mapper class
*/
public AddressOut mapAddress(AddressIn addressIn){
AddressOut res= null;
if (addressIn != null){
res = new AddressOut();
res.setCity(this.asCityOut(addressIn.getCity()));
res.setExtras(addressIn.getExtras() == null ? null : new ArrayList<String>(addressIn.getExtras()));
res.setNumber(addressIn.getNumber() + NUMBER_INCREMENT);
res.setPrincipal(addressIn.isPrincipal());
res.setStreet(addressIn.getStreet());
}
return res;
}


}

0 comments on commit fc8d6d2

Please sign in to comment.